snippetterraformMinor
Terraform conditionally create VPC
Viewed 0 times
conditionallyvpccreateterraform
Problem
I am in the process of creating a terraform template, part of which can create a VPC.
The user has the option to specify the parameter
I am now trying to use a
How can I update this to use the variable
Ideally I'd do something like:
This won't work as resource names need to be unique.
Other resources in the template reference this VPC.
The user has the option to specify the parameter
create-vpc. This will be passed to the module https://github.com/terraform-aws-modules/terraform-aws-vpc which will create the VPC based on this parameter i.e.module "vpc" {
source = "terraform-aws-modules/vpc/aws"
create_vpc = var.create-vpc
# ... omitted
}I am now trying to use a
data source to share the VPC with the rest of my template i.e.data "aws_vpc" "myvpc" {
id = "${module.vpc.vpc_id}"
}How can I update this to use the variable
vpc-id instead of the module output if the VPC hasn't been created?Ideally I'd do something like:
data "aws_vpc" "myvpc" {
count = "${var.vpc-id != "" ? 1 : 0}"
id = "${module.vpc.vpc_id}"
}
data "aws_vpc" "myvpc" {
count = "${var.vpc-id != "" ? 0 : 1}"
id = var.vpc-id
}This won't work as resource names need to be unique.
Other resources in the template reference this VPC.
Solution
The way I'm understanding your question is that you're writing a module that takes an optional
If I understood that correctly, here's one way to get that done.
First, we'll declare the
From this we will derive our decision about whether to create the VPC, which I'll write as a local value here mainly for exposition purposes but you might choose to inline it at the point of use in practice depending on what feels most readable for you:
We can now use that value both with the module and with a
We can then write another local value that chooses which of those two locations to derive other VPC-related values, such as the
You can then use
The above is the most direct answer to your question. I also wanted to share a different approach that is based on the patterns described in the Module Composition section of Terraform's docs, which might be appropriate for you if you're building a shared module intended to be called by another Terraform module.
A different way to approach this problem using composition is to say that your module never creates its own VPC, and always expects one to be provided to it. This is an example of the dependency inversion principle. Here's how that might look for your particular use-case...
In your module, you can declare an input variable called
Your own module's expressions would then get the VPC id as
The calling module can then decide between several different ways of providing the value for that variable:
In other words, this lets your new module focus on whatever new problem it is trying to solve while being decoupled from the problem of creating a VPC, allowing the calling module to obtain that VPC information in whatever way is appropriate. The appropriate way to obtain the VPC might change over time, and this composition approach means that callers can have more control over how they manage the transition between different approaches, independent of the implementation of your new module.
Module composition isn't always the best option, but I wanted to point it out because the Terraform documentation specifically recommends considering this approach and recommends against having many levels of nested module calls.
vpc_id as an argument, and either uses it if specified or creates a new VPC itself if not specified. You then need to use the VPC id elsewhere in your configuration, regardless of whether it was provided by the caller directly or if it was created by the VPC module.If I understood that correctly, here's one way to get that done.
First, we'll declare the
vpc_id variable for your own module that the rest of this will use to make its decisions.variable "vpc_id" {
type = string
default = null # optional with no default value
}From this we will derive our decision about whether to create the VPC, which I'll write as a local value here mainly for exposition purposes but you might choose to inline it at the point of use in practice depending on what feels most readable for you:
locals {
create_vpc = var.vpc_id != null ? true : false
}We can now use that value both with the module and with a
data resource so that we'll either create a new VPC or retrieve data about the existing one:module "vpc" {
source = "terraform-aws-modules/vpc/aws"
create_vpc = local.create_vpc
# ...
}
data "aws_vpc" "selected" {
count = local.create_vpc ? 0 : 1
id = var.vpc_id
}We can then write another local value that chooses which of those two locations to derive other VPC-related values, such as the
id and cidr_block from:locals {
vpc = (
local.create_vpc ?
{
id = module.vpc.vpc_id
cidr_block = module.vpc.vpc_cidr_block
} :
{
id = data.aws_vpc.selected.id
cidr_block = data.aws_vpc.selected.cidr_block
}
)
}You can then use
local.vpc.id and local.vpc.cidr_block elsewhere in the configuration to get the appropriate values to use regardless of whether this module created its own VPC or not.The above is the most direct answer to your question. I also wanted to share a different approach that is based on the patterns described in the Module Composition section of Terraform's docs, which might be appropriate for you if you're building a shared module intended to be called by another Terraform module.
A different way to approach this problem using composition is to say that your module never creates its own VPC, and always expects one to be provided to it. This is an example of the dependency inversion principle. Here's how that might look for your particular use-case...
In your module, you can declare an input variable called
vpc that has the same structure as the final local.vpc that we created in the previous approach:variable "vpc" {
type = object({
id = string
cidr_block = string
})
# This time the VPC is required. The caller decides how to obtain it.
}Your own module's expressions would then get the VPC id as
var.vpc.id instead of local.vpc.id as in the previous approach.The calling module can then decide between several different ways of providing the value for that variable:
# Using the terraform-aws-modules/vpc/aws module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
# ...
}
module "your_module" {
source = "./modules/your-module"
vpc = {
id = module.vpc.vpc_id
cidr_block = module.vpc.vpc_cidr_block
}
}# Directly using the aws_vpc resource type
resource "aws_vpc" "example" {
# ...
}
module "your_module" {
source = "./modules/your-module"
vpc = aws_vpc.example
}# Using the aws_vpc data source to find an existing VPC
data "aws_vpc" "example" {
tags = {
Environment = "STAGE"
}
}
module "your_module" {
source = "./modules/your-module"
vpc = data.aws_vpc.example
}In other words, this lets your new module focus on whatever new problem it is trying to solve while being decoupled from the problem of creating a VPC, allowing the calling module to obtain that VPC information in whatever way is appropriate. The appropriate way to obtain the VPC might change over time, and this composition approach means that callers can have more control over how they manage the transition between different approaches, independent of the implementation of your new module.
Module composition isn't always the best option, but I wanted to point it out because the Terraform documentation specifically recommends considering this approach and recommends against having many levels of nested module calls.
Code Snippets
variable "vpc_id" {
type = string
default = null # optional with no default value
}locals {
create_vpc = var.vpc_id != null ? true : false
}module "vpc" {
source = "terraform-aws-modules/vpc/aws"
create_vpc = local.create_vpc
# ...
}
data "aws_vpc" "selected" {
count = local.create_vpc ? 0 : 1
id = var.vpc_id
}locals {
vpc = (
local.create_vpc ?
{
id = module.vpc.vpc_id
cidr_block = module.vpc.vpc_cidr_block
} :
{
id = data.aws_vpc.selected.id
cidr_block = data.aws_vpc.selected.cidr_block
}
)
}variable "vpc" {
type = object({
id = string
cidr_block = string
})
# This time the VPC is required. The caller decides how to obtain it.
}Context
StackExchange DevOps Q#11399, answer score: 2
Revisions (0)
No revisions yet.