Terraform dynamically add VPC ID into Route53 Zone - terraform

I'm going to make use of this Terraform registry module to create the VPC private hosted zones.
I've defined my parent module variable just like below, So I can use variables depends on my workspace to pick the appropriate Zone attributes and it works pretty well.
Now what I want to do is, I want to add the VPC_ID into the resource block from my data source output (data.aws_vpc.this.id), so I can use the variable block like in domain2.my. The reason is, if I use the variable, I want to hardcode the VPC ID (Just like in domain1.my). So I need to avoid hardcoded VPC ID and able add the VPC ID dynamically from data source and also should be able to add additional VPC IDs just like shown in the domain3.my
Can anyone help to solve this problem? Thank you!
variables.tf
variable "zone_name" {
description = "Domain name of The Zone"
default = {
dev = {
"domain1.my" = {
comment = "DEV Private Hosted Zone"
vpc = [
{
vpc_id = "xxxxx"
}
]
}
}
stg = {
"domain2.my" = {
comment = "STG Private Hosted Zone"
vpc = []
}
}
uat = {
"domain3.my" = {
comment = "UAT Private Hosted Zone"
vpc = [
{
vpc_id = "VPC ID"
},
{
vpc_id = "Another VPC ID"
}
]
}
}
}
main.tf
#source = "../../../../tf-modules/aws/route53/zones/"
module "zones" {
source = "terraform-aws-modules/route53/aws//modules/zones"
version = "~> 1.0"
zones = var.zone_name[local.workspace]
tags = {
ManagedBy = "Terraform"
}
}
data "aws_vpc" "this" {
tags = {
env = local.workspace
}
}

Related

Terraform 1.2.0: Referencing resources and object mapping

I have deployed a cloud run application for currently two domains with a load balancer, which is already running. Now this setup needs to be rolled out to other domains. Because the resource setup is always the same, I face some issues:
I want to prevent repeating code (which is managed through a for_each)
Still there are some domain-specific values to cover, which i tried through a mapping table
Referencing resources, which are created with for_each in another resource
The first issue I solved like this, which seems to work:
Old:
resource "google_cloud_run_service" "cr_domain1" {
name = "cr-domain1"
location = "europe-west6"
project = "my_project"
template {
...
}
}
resource "google_cloud_run_service" "cr_domain2" {
name = "cr-domain2"
location = "europe-southwest1"
project = "my_project"
template {
...
}
}
New:
resource "google_cloud_run_service" "cr" {
for_each = toset( ["domain1", "domain2"] )
name = "cr-${each_key}"
location = "tdb" # This is my second issue
project = "my_project"
template {
...
}
}
Regarding second issue I still need domain-specific location setup, which I tried to solve like this, but I am getting errors:
variable "cr_location" {
type = list(object({
domain1 = string
domain2 = string
}))
default = [{
domain1 = "europe-west6"
domain2 = "europe-southwest1"
}]
}
resource "google_cloud_run_service" "cr" {
for_each = toset( ["domain1", "domain2"] )
name = "cr-${each_key}"
location = "${var.cr_location[0]}.${each.key}"
project = "my_project"
template {
...
}
}
Error is "Cannot include the given value in a string template: string required". But I have already declared it as a string in my variable "cr_location". Any idea what's the issue here? The expected output should be:
location = "europe-west6" # For domain1
location = "europe-southwest1" # For domain2
Also regarding issue 3 I do not understand how to referencing resources, which are created with for_each in another resource. So before my for_each in the cloud run resource block (see issue 1) I had this 2 resources:
resource "google_cloud_run_service" "cr_domain1"
resource "google_cloud_run_service" "cr_domain2"
Now I only have resource "google_cloud_run_service" "cr". But in my loadbalancer.tf I still have to references to the old namings (last coderow within "service"):
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = toset( ["domain1", "domain2"] )
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = "${var.cr_location[0]}.${each.key}" # Here same issues as issue 2
cloud_run {
service = google_cloud_run_service.cr_domain1.name # Old reference
}
}
So if there is no "cr_domain1" anymore how do I reference to this resource? My issue is that I have to create over 20 resources like that and I couldn't figure it out how to do it. I appreciate any guideline here.
What I would suggest here is to try and refactor the variable because it is making a lot of things harder than they should be. So I would go for this kind of a variable definition:
variable "cr_location" {
type = map(string)
default = {
domain1 = "europe-west6"
domain2 = "europe-southwest1"
}
}
Then, the rest should be easy to create:
resource "google_cloud_run_service" "cr" {
for_each = var.cr_location
name = "cr-${each.key}"
location = each.value
project = "my_project"
template {
...
}
}
And for the network endpoint resource:
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = var.cr_location
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = each.value
cloud_run {
service = google_cloud_run_service.cr[each.key].name
}
}
You could even try resource chaining with for_each [1] to make sure you are doing this for all the Cloud Run resources created:
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = google_cloud_run_service.cr
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = each.value.location
cloud_run {
service = each.value.name
}
}
[1] https://www.terraform.io/language/meta-arguments/for_each#chaining-for_each-between-resources

Nested Dynamic block on counted resource not working as expected

Need your kind help. I am stuck with the following resource creation. Using Terraform v1.0.6
I need to create appropriate subnets dynamically in two VPCs
variables.tf
vpc_resource_networks = {
pnw-01 = [
[
{
subnet_name = "wb-01"
subnet_ip = "10.58.72.0/25"
description = "WEB01"
index = 0
},
{
subnet_name = "wb-02"
subnet_ip = "10.58.72.128/25"
description = "WEB02"
index = 1
}
],
[
{
subnet_name = "wb-01"
subnet_ip = "10.58.80.0/25"
description = "WEB01"
index = 0
},
{
subnet_name = "web-02"
subnet_ip = "10.58.72.128/25"
description = "WEB02"
index = 1
}
]
]
}
main.tf
locals {
wlb_net = element(keys(var.vpc_resource_networks), 0)
}
resource "aws_subnet" "wlb" {
count = length(module.aws_vpc_app_resource)
vpc_id = element(module.aws_vpc_app_resource.*.vpc_id, count.index)
dynamic "subnet_group" {
for_each = var.vpc_resource_networks[local.wlb_net][count.index]
content {
dynamic "subnet" {
for_each = subnet_group.value
content {
cidr_block = subnet.subnet_ip
availability_zone = element(var.azs, subnet.index)
tags = {
Name = subnet.subnet_name
}
}
}
}
}
I intend to create subnets dynamically which is var.vpc_resource_networks.pnw01[0] should be on one vpc and other index on another VPC.
The above block returns
dynamic “subnet_group” {
Blocks of type “subnet_group” are not expected here.
Please assist
Looking at the resource definition of aws_subnet, I can see that, as the error message suggests, there's no property for a "subnet_group".
There are several different resource types that are subnet groups though for different services; such as DMS, DocumentDB, DAX, Elasticache, MemoryDB, Neptune, RDS, and Redshift. Search the term "subnet_group" on the left panel within the provider page.
Perhaps an AWS expert can comment here but I believe you're trying to do two things in one motion here.
First you should create the subnets and define their ranges, then you should create subnet groups that need access to different subnets for a particular service.
Here's some more information on subnets and subnet groups.

loop over aws provider to create ressources in every aws account

I have a list of objects in Terraform called users and the structure is the following:
variable "accounts" {
type = list(object({
id = string #used in assume_role
email = string
is_enabled = bool
}))
}
What I am trying to achieve now is to create a simple S3 bucket in each of those AWS accounts (if the is_enabled is true). I was able to do it for a single account but I am not sure if there is a way to loop over a provider?
Code for a single account - main.tf
provider "aws" {
alias = "new_account"
region = "eu-west-3"
assume_role {
role_arn = "arn:aws:iam::${aws_organizations_account.this.id}:role/OrganizationAccountAccessRole"
session_name = "new_account_creation"
}
}
resource "aws_s3_bucket" "bucket" {
provider = aws.new_account
bucket = "new-account-bucket-${aws_organizations_account.this.id}"
acl = "private"
}
You need to define one provider for each aws account you want to use:
Create a module (i.e. a directory), where your bucket configuration lives:
├── main.tf
└── module
└── bucket.tf
bucket.tf should contain the resource definition: resource "aws_s3_bucket" "bucket" {...}
In main.tf , define multiple aws providers and call the module with each of them:
provider "aws" {
alias = "account1"
region = "eu-west-1"
...
}
provider "aws" {
alias = "account2"
region = "us-west-1"
...
}
module "my_module" {
source = "./module"
providers = {
aws.account1 = aws.account1
aws.account2 = aws.account2
}
}
I guess you could also get fancy by creating a variable containing the providers, and pass it to the module invocation (you could probably also use a filter on the list to take into account the is_enabled flag)
More details about providers: https://www.terraform.io/docs/language/modules/develop/providers.html
Found what I was looking for here: https://github.com/hashicorp/terraform/issues/19932
Thanks Bryan Karaffa
## Just some data... a list(map())
locals {
aws_accounts = [
{ "aws_account_id": "123456789012", "foo_value": "foo", "bar_value": "bar" },
{ "aws_account_id": "987654321098", "foo_value": "foofoo", "bar_value": "barbar" },
]
}
## Here's the proposed magic... `provider.for_each`
provider "aws" {
for_each = local.aws_accounts
alias = each.value.aws_account_id
assume_role {
role_arn = "arn:aws:iam::${each.value.aws_account_id}:role/TerraformAccessRole"
}
}
## Modules reference the provider dynamically using `each.value.aws_account_id`
module "foo" {
source = "./foo"
for_each = local.aws_accounts
providers = {
aws = "aws.${each.value.aws_account_id}"
}
foo = each.value.foo_value
}
module "bar" {
source = "./bar"
for_each = local.aws_accounts
providers = {
aws = "aws.${each.value.aws_account_id}"
}
bar = each.value.bar_value
}

Default DNS records in every zone managed via terraform (eg. MX records)

I'm looking for a way to manage cloudflare zones and records with terraform and create some default records (eg. MX) in every zone that is managed via terraform, something like this:
resource "cloudflare_zone" "example_net" {
type = "full"
zone = "example.net"
}
resource "cloudflare_zone" "example_com" {
type = "full"
zone = "example.com"
}
resource "cloudflare_record" "mxrecord"{
for_each=cloudflare_zone.*
name = "${each.value.zone}"
priority = "1"
proxied = "false"
ttl = "1"
type = "MX"
value = "mail.foo.bar"
zone_id = each.value.id
}
Does anyone have a clue for me how to achieve this (and if this is even possible...)?
Thanks a lot!
You could create a module responsible for the zone resource, e.g.:
# modules/cf_zone/main.tf
resource "cloudflare_zone" "cf_zone" {
type = "full"
zone = var.zone_name
}
resource "cloudflare_record" "mxrecord"{
name = "${cloudflare_zone.cf_zone.name}"
priority = "1"
proxied = "false"
ttl = "1"
type = "MX"
value = "mail.foo.bar"
zone_id = "${cloudflare_zone.cf_zone.id}"
}
# main.tf
module "example_net" {
source = "./modules/cf_zone"
zone_name = "example_net"
}
module "example_com" {
source = "./modules/cf_zone"
zone_name = "example_com"
}
This would give you an advantage on creation of default resources and settings per zone (DNS entries, security settings, page rules, etc.). It is also a good way to keep all the default values in a single place for review.
You can ready more about terraform modules here.
This is easy to do if you use a module, as was correctly noted in the other answer, but you don't have to create one, you can use this module.
Then your configuration will look like this:
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
}
}
}
variable "cloudflare_api_token" {
type = string
sensitive = true
description = "The Cloudflare API token."
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
locals {
domains = [
"example.com",
"example.net"
]
mx = "mail.foo.bar"
}
module "domains" {
source = "registry.terraform.io/alex-feel/zone/cloudflare"
version = "1.8.0"
for_each = toset(local.domains)
zone = each.value
records = [
{
record_name = "mx_1"
type = "MX"
value = local.mx
priority = 1
}
]
}
You can find an example of using this module that matches your question here.

how to conditionally create aws_eip and aws_eip_association?

I need to be able to conditionally create an EIP and associate it to an instance:
resource "aws_eip" "gateway" {
vpc = true
tags = {
Name = "${var.project_id}-gateway"
Project = "${var.project_id}"
user = "${var.user}"
}
}
resource "aws_eip_association" "eip_assoc_gateway" {
instance_id = aws_instance.gateway.id
allocation_id = aws_eip.gateway.id
}
resource "aws_instance" "gateway" {
...
}
Unfortunately, aws_eip and aws_eip_association don't appear to support the count attribute, so I'm not clear if this is even possible?
Any ideas?
As mentioned in comment, count is supported by all Terraform primitive resources. Example for aws_eip below:
resource "aws_eip" "eip" {
instance = "${element(aws_instance.manager.*.id,count.index)}"
count = "${var.eip_count}"
vpc = true
}

Resources