Variance in attributes based on count.index in terraform - terraform

I'm using Hashicorp terraform to create a MySQL cluster on AWS. I created a module named mysql and want to tag the first instance created as the master. However, per terraform documentation:
Modules don't currently support the count parameter.
How do I work around this problem? Currently, I have these in my files:
$ cat project/main.tf
module "mysql_cluster" {
source = "./modules/mysql"
cluster_role = "${count.index == "0" ? "master" : "slave"}"
}
$ cat project/modules/mysql/main.tf
..
resource "aws_instance" "mysql" {
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${var.cluster_role}"
}
count = 3
}
This throws an error:
$ project git:(master) ✗ terraform plan
Error: module "mysql_cluster": count variables are only valid within resources
I have the necessary variables declared in the variables.tf files in my mysql module and root module. How do I work around this problem? Thanks in advance for any help!

The way you have count in the module resource would infer that you want 3 modules created, rather than 3 resources within the module created. You can stipulate the count from the module resource but any logic using count.index needs to sit within the module.
main.tf
module "mysql_cluster" {
source = "./modules/mysql"
instance_count = 3
}
mysql.tf
resource "aws_instance" "mysql" {
count = "${var.instance_count}"
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${count.index == "0" ? "master" : "slave"}"
}
}

Since Terraform 0.13 you can use either for_each or count to create multiple instances of a module.
variable "regions" {
type = map(object({
region = string
network = string
subnetwork = string
ip_range_pods = string
ip_range_services = string
}))
}
module "kubernetes_cluster" {
source = "terraform-google-modules/kubernetes-engine/google"
for_each = var.regions
project_id = var.project_id
name = each.key
region = each.value.region
network = each.value.network
subnetwork = each.value.subnetwork
ip_range_pods = each.value.ip_range_pods
ip_range_services = each.value.ip_range_services
}
Code snipped from
https://github.com/hashicorp/terraform/tree/guide-v0.13-beta/module-repetition
Official documentation
https://www.terraform.io/docs/configuration/modules.html

module doesn't have count. It is available at resources only.

Related

Getting Outputs of Resources from Reused Modules

I have the following code that will call a module and make target groups for me based off of the information I pass it in the locals variables. This works just fine, my issue being trying to get the arn of each target group it creates in an output.
locals {
targetgroups_beta = {
targetgroup1 = {
name = "example",
path = "/",
environment = "Beta"
}
targetgroup2 = {
name = "example2",
path = "/",
environment = "Beta"
}
}
}
module "target-groups"{
for_each = local.targetgroups_beta
source = ".//modules/targetgroups"
name-beta = each.value.name
path-beta = each.value.path
environment-beta = each.value.environment
vpc-id = "${module.vpc.vpc_id}"
}
The resource name in the module it is calling is target-group, so based off of what I read I should be able to refer to it by something like this:
output{
value = "${aws_lb_target_group.target-group[0].arn}"
}
When I try this I receive the following when running a terraform plan:
"Because aws_lb_target_group.target-group does not have "count" or "for_each" set, references to it must not include an index key. Remove the bracketed index to refer to the single instance of this resource."
My understanding of this is the module that the for_each is calling isn't running a for_each, so I cannot reference the resources in this way. If I do ""${aws_lb_target_group.target-group.arn}" that reference works technically, but includes the arn for every target group and I plan on adding a lot more. Is there a way to take each of these arns out of this list as its own output?
Code in the module that it is calling for reference:
resource "aws_lb_target_group" "target-group" {
name = "example-${var.name-beta}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc-id
deregistration_delay = 5
tags = {
Environment = "${var.environment-beta}"
}
health_check{
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 10
interval = 15
path = var.path-beta
}
}
If I correctly understand, you are using for_each in your target-groups module. If so to get the outputs, you would have to use something like the following in your main.tf file:
module.target-groups[*].arn
The for_each will create multiple modules, not multiple resources in a single module.
Here is good info on using for_each and count with modules in terraform 0.13.
Update for one module
If you want to use only one module, you can do the following:
module "target-groups"{
target_groups_to_create = local.targetgroups_beta
source = ".//modules/targetgroups"
name-beta = each.value.name
path-beta = each.value.path
environment-beta = each.value.environment
vpc-id = "${module.vpc.vpc_id}"
}
Then in the module:
variable "target_groups_to_create" {}
resource "aws_lb_target_group" "target-group" {
for_each = var.target_groups_to_create
name = "example-${each.value.name}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc-id
deregistration_delay = 5
tags = {
Environment = "${each.value.environment}"
}
health_check{
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 10
interval = 15
path = each.value.path
}
}

Terraform: How can I have one common output for all declared modules?

I have got something like this in my terraform file:
main.tf
module "airflow_tenant_one" {
source = "../modules/airflow_tenant"
name = "one-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "one-airflow.${var.domain_name}"
}
module "airflow_tenant_two" {
source = "../modules/airflow_tenant"
name = "two-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "two-airflow.${var.domain_name}"
}
How can I do one common output for all declared modules?
Currently, it looks like:
outputs.tf
output "chart_name_one" {
description = "The name of the chart"
value = module.airflow_tenant_one.chart_name
}
output "chart_name_two" {
description = "The name of the chart"
value = module.airflow_tenant_two.chart_name
}
I asked because in a future it is possible that I will add more modules in my main.tf file. Will be better to have one output declaration for all of them.
The module blocks you shared seem like they are all systematically configured based on some client names, so you can potentially do this using a single module block with for_each if you are using Terraform 0.13.0 or later.
locals {
tenants = toset([
"one",
"two",
])
}
module "airflow_tenant" {
for_each = local.tenants
name = "${each.key}-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "${each.key}-airflow.${var.domain_name}"
}
output "tenant_chart_names" {
value = {
for name, tenant in module.airflow_tenant : name => tenant.chart_name
}
}
The above will cause there to be one instance of the airflow_tenant module per element of local.tenants, with addresses like this:
module.airflow_tenant["one"]
module.airflow_tenant["two"]
The for_each makes the module behave as a map of instances when you refer to it elsewhere, which is why we're able to project that map in output "tenant_chart_names" to derive a map from tenant name to chart names.
You can add and remove elements of local.tenants over time, in which case Terraform will understand that as an intent to either create a instances or destroy an instances of all of the objects described inside that module.
You can read more about this feature in Multiple Instances of a Module.

Terraform output defined are empty [duplicate]

I'm trying to setup some IaC for a new project using Hashicorp Terraform on AWS. I'm using modules because I want to be able to reuse stuff across multiple environments (staging, prod, dev, etc.)
I'm struggling to understand where I have to set an output variable within a module, and how I then use that in another module. Any pointers to this would be greatly appreciated!
I need to use some things created in my VPC module (subnet IDs) when creating EC2 machines. My understanding is that you can't reference something from one module in another, so I am trying to use an output variable from the VPC module.
I have the following in my site main.tf
module "myapp-vpc" {
source = "dev/vpc"
aws_region = "${var.aws_region}"
}
module "myapp-ec2" {
source = "dev/ec2"
aws_region = "${var.aws_region}"
subnet_id = "${module.vpc.subnetid"}
}
dev/vpc simply sets some values and uses my vpc module:
module "vpc" {
source = "../../modules/vpc"
aws_region = "${var.aws_region}"
vpc-cidr = "10.1.0.0/16"
public-subnet-cidr = "10.1.1.0/24"
private-subnet-cidr = "10.1.2.0/24"
}
In my vpc main.tf, I have the following at the very end, after the aws_vpc and aws_subnet resources (showing subnet resource):
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.main.id}"
map_public_ip_on_launch = true
availability_zone = "${var.aws_region}a"
cidr_block = "${var.public-subnet-cidr}"
}
output "subnetid" {
value = "${aws_subnet.public.id}"
}
When I run terraform plan I get the following error message:
Error: module 'vpc': "subnetid" is not a valid output for module "vpc"
Outputs need to be passed up through each module explicitly each time.
For example if you wanted to output a variable to the screen from a module nested below another module you would need something like this:
child-module.tf
output "child_foo" {
value = "foobar"
}
parent-module.tf
module "child" {
source = "path/to/child"
}
output "parent_foo" {
value = "${module.child.child_foo}"
}
main.tf
module "parent" {
source = "path/to/parent"
}
output "main_foo" {
value = "${module.parent.parent_foo}"
}

terraform setting hostname on multiple aws instances

I'm wanting to understand how user data can be used to set hostnames for 2 or more ec2 instances that terrafrom creates. Below is my instance.tf which creates 2 instances.
resource "aws_instance" "example" {
count = 2
ami = "${lookup(var.AMIS, var.aws_region)}"
instance_type = "t2.micro"
tags = {Name = "rb-${count.index}"}
# the VPC subnet
subnet_id = "${aws_subnet.dee-main-public-1.id}"
# the security group
vpc_security_group_ids = ["${aws_security_group.allow-ssh.id}"]
# the public SSH key
key_name = "${aws_key_pair.mykeypair.key_name}"
}
resource "aws_key_pair" "mykeypair" {
key_name = "mykeypair"
public_key = "${file("${var.PATH_TO_PUBLIC_KEY}")}"
}
How do I set the hostnames for those 2 instances. i.e web1.example.com, web2.example.com
I understand cloudinit or remote-exec can be used for this but struggling to come up with the code as I'm still a beginner. Really appreciate If I can get some help to come up to speed. Many thanks in advance.
-B
You can do it with data resource for template file which allows you to use count function. The below syntax will allow you use the hostname variable with the count function.
data "template_file" "init" {
count = 2
template = file("$path")
vars = {
hostname = "web${count.index}.example.com"
}
}
resource "aws_instance" "master" {
ami = "$ami"
count = 2
instance_type = t2.medium
user_data = data.template_file.init[count.index].rendered
}

concatenate two variables in terraform

i'm trying to create a kubernetes cluster from kops using terraform,following code is a part of infrastructure, i'm trying to create the name concatenate with two variables, and i'm getting illegal char error line two, error happens because im trying to define the name with concatenate variables. is it possible in terraform?
resource "aws_autoscaling_group" "master-kubernetes" {
name = "master-"${var.zone}".masters."${var.cluster_name}""
launch_configuration = "${aws_launch_configuration.master-kubernetes.id}"
max_size = 1
min_size = 1
vpc_zone_identifier = ["${aws_subnet.subnet-kubernetes.id}"]
With latest terraform 0.12.x terraform format doc , you could do better like:
resource "aws_autoscaling_group" "master-kubernetes" {
name = format("master-%s.masters.%s", var.zone, var.cluster_name)
}
Try this:
resource "aws_autoscaling_group" "master-kubernetes" {
name = "master-${var.zone}.masters.${var.cluster_name}"
# ... other params ...
}
I would say rather concatenating at resource level use the locals first define a variable in locals and then you can utilize it at resource level
Locals declaration
locals {
rds_instance_name = "${var.env}-${var.rds_name}"
}
Resource Level declaration
resource "aws_db_instance" "default_mssql" {
count = var.db_create ? 1 : 0
name = local.rds_instance_name
........
}
This is as simple as its need to be ....

Resources