I have a VPC cidr in a map variable, which is defined in Terraform. What I am trying to do is to use a specific value in that map variable in order to dynamically create a subnet in Terraform. Any advice how this would be accomplished would be very helpful. Below is how my variables are defined:
VPC CIDR
variable "vpc_cidr" {
default {
us-east-1 = "192.1.0.0/16"
us-west-1 = "192.2.0.0/16"
us-west-2 = "192.3.0.0/16"
}
}
AWS Subnet
resource "aws_subnet" "public_subnets" {
count = "${length(local.availability_zone_names)}"
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${cidrsubnet("pulling aws vpc cidr from map variable", newbits, netnum)}"
availability_zone = "${local.availability_zone_names[count.index]}"
map_public_ip_on_launch = true
}
To be honest I am not fully sure about your syntax of defining the variable. I'd rather put it like this (although your version might be also correct):
variable "vpc_cidr" {
type = map
default = {
"us-east-1" = "192.1.0.0/16"
"us-west-1" = "192.2.0.0/16"
"us-west-2" = "192.3.0.0/16"
}
}
and then:
cidr_block = "${cidrsubnet(var.vpc_cidr[YOUR_CURRENT_REGION], newbits, netnum)}"
Not sure if you have YOUR_CURRENT_REGION defined as a variable somewhere. Otherwise you probably need to extract from a data source:
data "aws_region" "current_region" {}
and use current_region instead of YOUR_CURRENT_REGION
Related
I create modules for creating AWS vpc, security group and EC2. I make EC2 module depend on vpc and the security group module.
but I get that error:
no matching EC2 Security Group found
on .terraform/modules/main_ec2/modules/EC2Module/main.tf line 26, in data "aws_security_group" "security_group_id":
26: data "aws_security_group" "security_group_id" {
EC2 module
data "aws_subnet" "subnet_id" {
vpc_id = var.vpc_id
count = length(var.subnet_name)
depends_on = [var.subnet_id_depends_on]
filter {
name = "tag:Name"
values = ["VPC0${var.vpc_number}-${element(var.subnet_name, count.index)}"]
}
}
data "aws_security_group" "security_group_id" {
count = length(var.Server_Group_Name)
depends_on = [var.security_group_depends_on]
filter {
name = "tag:Name"
values = ["${local.name}-SG-${var.Server_Group_Name[count.index]}-${format("%02d", count.index + 1)}"]
}
}
resource "aws_instance" "create_instance" {
ami = "${element(data.aws_ami_ids.ami_id.*.id, count.index)}"
instance_type = var.EC2_Type[count.index]
subnet_id = "${element(data.aws_subnet.subnet_id.*.id, count.index)}"
associate_public_ip_address = "true"
vpc_security_group_ids = [ data.aws_security_group.security_group_id[count.index].id ]
key_name = "${local.name}-KP-${var.Server_Name[count.index]}"
depends_on = [var.EC2_depends_on]
}
sg module
resource "aws_security_group" "SGS" {
count = length(var.Server_Group_Name)
name = "${local.name}-SG-${var.Server_Group_Name[count.index]}-${format("%02d", count.index + 1)}"
description = "${local.name}-SG-${var.Server_Group_Name[count.index]}-${format("%02d", count.index + 1)}"
vpc_id = var.vpc_id
tags = {
name = "tag:Name"
values = "${local.name}-SG-${var.Server_Group_Name[count.index]}-${format("%02d", count.index + 1)}"
}
varibles.tf
variable "subnet_id_depends_on" {
type = list(string)
default = ["module.vpc_main"]
}
variable "security_group_depends_on" {
type = list(string)
default = ["module.sg"]
}
variable "EC2_depends_on" {
type = list(string)
default = [ "module.vpc_main", "module.sg", "module.key_pair" ]
}
for example, I call modules like that:
module "main_ec2" {
source = "/home/reham/Data/projectes/terraform//modules/EC2Module"
subnet_id_depends_on = var.subnet_id_depends_on
security_group_depends_on = var.security_group_depends_on
ami_name = var.ami_name
EC2_depends_on = var.EC2_depends_on
}
Outputs defined in the sg module:
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.vpc.id
}
output "primary_public_subnets_id" {
description = "public subnet ID"
value = aws_subnet.primary_public_subnets.*.id
}
output "secondary_public_subnets_id" {
description = "public subnet ID"
value = aws_subnet.secondary_public_subnets.*.id
}
output "primary_private_subnets_id" {
description = "public subnet ID"
value = aws_subnet.primary_private_subnets.*.id
}
output "security-groups" {
value = aws_security_group.SGS[*].id
}
what must I do to solve this problem?
There is more than one issue with the code and it is a result of probably not understanding how the module outputs work [1]. The second part of the issue is that there are many explicit dependencies defined with depends_on which are not required to be there since implicit dependencies are good enough [2]. Furthermore, as per comments, data sources in the EC2 module are not required as information about VPC, subnets and security groups can be provided with the outputs. Additionally, the EC2 instance resource is using count, but there is no count meta-argument there, so it will not work even if all the inputs are fixed. So here are my suggestions:
Redefine variables in the EC2 module (no need for depdends_on) and remove data sources (except possibly the one for the AMI)
Decide which variable you are going to use for the count meta-argument in the EC2 instance resource
I am going to give you an example with one subnet and security groups and then you can build from there. The EC2 module fixes:
resource "aws_instance" "create_instance" {
ami = data.aws_ami_ids.ami_id[0].id
instance_type = var.EC2_Type
subnet_id = var.subnet_id
associate_public_ip_address = "true"
vpc_security_group_ids = var.security_group_ids
key_name = "${local.name}-KP-${var.Server_Name[0]}"
}
In EC2 module, the variables would have to be changed to:
variable "subnet_id" {}
variable "security_group_ids" {}
Then, in the root module:
module "main_ec2" {
source = "/home/reham/Data/projectes/terraform//modules/EC2Module"
subnet_id = module.sg.primary_public_subnets_id[0]
security_group_ids = module.sg.security-groups
ami_name = var.ami_name
}
Referencing the module outputs in the EC2 module will make sure that the SG module is created first (that is an implicit dependency). At best this should give you a result for one EC2 instance as it seems there are many resources that rely on using the count meta-argument and that code is not in the question.
[1] https://www.terraform.io/language/values/outputs#accessing-child-module-outputs
[2] https://www.terraform.io/language/resources/behavior#resource-dependencies
I have created subnet as map variable for availability zone and CIDR block and
variable "public_subnets_list" {
type = map(any)
description = "Public Subnets"
default = {
"ap-south-1a" = "10.0.1.0/24"
"ap-south-1b" = "10.0.2.0/24"
}
}
This works fine for creating subnets under my custom VPC with below code
resource "aws_subnet" "public_subnet" {
depends_on = [
aws_vpc.terraform_vpc
]
for_each = tomap(var.public_subnets_list)
availability_zone = each.key
cidr_block = each.value
vpc_id = aws_vpc.terraform_vpc.id
tags = {
Name = "Public_Subnet_${each.key}"
}
}
How do I retrieve and display the subnet id created for respective AZs from output which i get from aws_subnet.public_subnet[*]
From the Terraform documentation, splat expressions do not work with resources that use the for_each argument.
To retrieve a list of subnet IDs using your Terraform configuration, you can do the following:
output "subnet_ids" {
value = [for subnet in aws_subnet.public_subnet : subnet.id]
}
Created a list of ranges as per below
subnet_names = ["subnet-lister", "subnet-kryten", "subnet-rimmer", "subnet-cat", "subnet-holly",]
subnet_cidrs = ["192.2.128.0/18", "192.2.0.0/17", "192.2.208.0/20", "192.2.192.0/20", "192.2.224.0/20",]
With this in the subnets.tf
resource "google_compute_subnetwork" "subnet" {
name = "${var.subnet_names}-subnet"
ip_cidr_range = var.subnet_cidrs
network = var.network_name
region = var.subnet_region
And the below in variables.tf (for the module)
variable "subnet_names" {
description = "The name to use for Subnet "
type = list(string)
}
variable "subnet_cidrs" {
description = "The cidr range for for Subnets"
type = list(string)
}
But getting the following message from Terraform.
Error: Incorrect attribute value type
on ..\..\..\Test-Modules\red\dwarf\subnets.tf line 3, in resource "google_compute_subnetwork" "subnet":
3: ip_cidr_range = var.subnet_cidrs
Inappropriate value for attribute "ip_cidr_range": string required.
I'm pretty new to this, can you help me work out what I am going wrong. I've seem someone else use a list for the cidr range (mind you that was for AWS). Does GCP not support this?
It looks like what you're trying to do is actually create several subnets. For that, you should use a map variable and a loop.
variable "subnets" {
type = map(string)
}
resource "google_compute_subnetwork" "subnet" {
for_each = var.subnets
name = each.key
ip_cidr_range = each.value
...
}
Then you can provide the subnets like:
subnets = {
subnet-lister = "192.2.128.0/18",
subnet-kryten = "192.2.0.0/17",
...
}
According to the Terraform Docs, the ip_cidr_range takes only one CIDR-Block, not a list. So you need to create one resource per subnet like this:
resource "google_compute_subnetwork" "subnet" {
count = length(var.subnet_names)
name = "${var.subnet_names[count.index]}-subnet"
ip_cidr_range = var.subnet_cidrs[count.index]
network = var.network_name
region = var.subnet_region
...
I would also recommend to restructure your data a bit, so you can use for_each instead of count (look at Ben's answer). This behaves better in case you later change your configuration and for example insert a new subnet, as described nicely in this post.
I'm trying to create multiple subnets from one resource block and I get the following error
Error: aws_subnet.private: cidr_block must be a single value, not a list
main.tf
resource "aws_subnet" "private" {
vpc_id = "${aws_vpc.vpcname.id}"
cidr_block = "${var.private_subnet}"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
tags {
Name = "${var.private}"
Environment = "${terraform.workspace}"
}
}
variable.tf
variable "private_subnet" {
type = "list"
default = []
}
dev.tfvars
private_subnet = ["10.0.2.0/24", "10.0.3.0/24"]
You have to create multiple aws_subnet resources by utilitizing the count argument to create one resource for each entry in your var.private_subnet list:
resource "aws_subnet" "private" {
count = "${length(var.private_subnet)}"
vpc_id = "${aws_vpc.vpcname.id}"
cidr_block = "${var.private_subnet[count.index]}"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
}
This expands the single aws_subnet resource into two, each with slightly different values based on the enumeration of count when each resource block is evaluated by terraform.
private_subnet is a list, so you should pick a single element, e.g.
cidr_block = "${element(var.private_subnet,count.index)}"
also add data module to get the availability zones for a region
data "aws_availability_zones" "available" {}
e.g.
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
I have a module which uses ec2 to create a specific instance, and now I need to assign the created instances each a static IP address from a pre-defined range.
So I define a map like this:
#map variable to hold IP addresses
variable "dw_manager_ips" {
default = {
"0" = "10.31.0.10"
"1" = "10.31.0.11"
"2" = "10.31.0.12"
"3" = "10.31.0.13"
"4" = "10.31.0.14"
}
}
and in my main .tf file I use the module as follows, attempting to assign a static IP address using a counter:
module "ec2-hadoop-node" {
source = "../modules/ec2"
ami_id = "${var.dw_manager["ami"]}"
instance_type = "${var.dw_manager["instance_type"]}"
aws_region = "${var.region}"
availability_zone = "eu-west-1a"
associate_public_ip_address = true
role = "dw-manager"
env = "${var.environment}"
vpc = "${var.vpc_id}"
security_group_ids = "${var.aws_security_group_id["sg_id"]}"
key_name = "${var.key_name}"
subnet_id = "${var.default_subnet}"
number_of_instances = "${var.dw_manager["count"]}"
private_ip = "${lookup(var.dw_manager_ips,count.index)}"
}
However, it seems this is not valid syntax:
"module root: resource 'aws_route53_record.nodes' config: *.public_ip is not a valid output for module ec2-hadoop-node"
How would I either a) do this the right way if counter is not the right method or b) work around any limitation in Terraform modules on using count if there is one?
NOTE: I can get this to work when not using modules.
Thanks in advance for any help!