Is there an equivalent to omit in Terraform? - amazon-web-services

I'm working on my Terraform code, and I would like to use the same code whether or a variable is set or equal "".
But if I pass the variable with "" in the aws_vpc module, there is an exception raised, saying a valid cidr is required.
If it were Ansible, I'd use the | omit filter which omit the parameter and don't pass it to the module if the variable is not set.
There is the code.
module "server_vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "servers_vpc"
cidr = var.main_cidr
secondary_cidr = [var.secondary_cidr]
azs = var.azs
public_subnets = local.public_subnets
private_subnets = var.private_subnets
enable_nat_gateway = true
create_igw = true
}
On this code, if the variable is equal to "" (default), then public_subnets equals [var.main_cidr], if the variable is set, public_subnets equals [var.main_cidr, var.secondary_cidr].
But the part that didn't work like I'd like is the line with secondary_cidr = [var.secondary_cidr].
When the variable contains a valid cidr, it works, but when the variable is not set and equals to the default "", Terraform raise an error from the aws vpc module explaining that a valid cidr is needed.
So I would like a way to create this vpc with the module, but not set the parameter secondary_cidr if the variable equals "".
I hope I was clear :)

According to the documentation for this specific module secondary_cidr is not a required argument. In that case you don't have to provide an empty string. You either leave out secondary_cidr entirely or you assign null to it. Since secondary_cidr expects an array, most likely an empty array [] will work as well.

Related

Terraform - Check If Variable Ends With String & Remove

We've set up our TF GKE code so that the user can specify either the region or zone for the cluster.
However, we need to then check this variable and remove the zone suffix (if it exists) for the deployment of static IP addresses.
We have the following variable:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = contains(["europe-west2", "europe-west2-a", "europe-west2-b", "europe-west2-c", "us-east4", "us-east4-a", "us-east4-b", "us-east4-c", "europe-west1", "europe-west1-a", "europe-west1-b", "europe-west1-c" ], var.k8s_cluster_location)
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
If, for example, the variable is "europe-west2-a", we need to remove "-a" to acquire the parent region.
Would we need to incorporate a Regex check? Or could we use something like StartsWith()/EndsWith()?
I would definitely recommend the regular expression solution here as you suggest:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = can(regex("(?:europe-west[12])|(?:us-east4)", var.k8s_cluster_location))
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
Note that if you are using Terraform 1.3.x, then you can also use the var.k8s_cluster_location value in the error_message instead of "Given GCP location".
For your other suggestion of startswith(), you would need to do something like anytrue(startswith(var.k8s_cluster_location, "europe-west1"), startswith(var.k8s_cluster_location, "europe-west2"), startswith(var.k8s_cluster_location, "us-east4")), but that feels slightly messier to me.

Terraform variables defaults and description for nested values

Is there a option to set a defaults and description for nested variables in terraform?
Example:
variable "example_page_rule"{
type = list(object({
level = string,
tags = string,
prefix = string
}))
}
This can be achieved ?
variable "example_page_rule"{
type = list(object({
level = string, default = "1", description = "debug level 0-7"
tags = string, default = {}, description = "tags"
prefix = string, default = "abc", description = "some description"
}))
}
You could set default values for your map with new, experimental feature defaults of TF. Its different syntax, but same outcome. There are no descriptions though.
If you don't want to use the experiential feature, you would have to develop custom code for handling default values, like any other TF code that does it these days. One example of this is here.

Terraform internal variable in resource definition

I have this question that I am not sure how to formulate. So let use an example. I have the following resource definition for was subnets based in an input parameter variable:
resource "aws_subnet" "monitoring_subnetwork" {
count = length(var.monitoring_subnets)
vpc_id = module.vpc.vpc_id
cidr_block = var.monitoring_subnets[count.index]
availability_zone= "${data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)]}"
tags = {
Name = "Monitoring private-1${replace(
data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)],
data.aws_availability_zones.available.id, "")}"
}
}
I want to simplify this code to make it more readable and maintainable.
I use a count.index to get an availability zone using round-robin, based on index % len_of_array, and the result of this mod is calculated twice (in other cases even three times).
I wonder if I could define an internal variable inside the resource, something like this:
zone_index = count.index % length(data.aws_availability_zones.available.names)
And reuse this index in the parts of the code where this operation is repeated.
Any thoughts? Also, any other recommendation to simplify this configuration would be appreciated :)
Sadly you can't do this. There are no custom functions in terraform. But there is already a github issue for that, so maybe in future there will be added:
Extending terraform with custom functions
So you have to keep repeating that computation.

terraform giving error: unsupported argument in module when running terraform plan?

I am getting the Error: Unsupported argument, when I run terraform plan in version 12.24.
Error: Unsupported argument
on .terraform/modules/app/main.tf line 261, in resource "aws_db_instance" "db_instance":
261: timeouts = {
An argument named "timeouts" is not expected here. Did you mean to define a
block of type "timeouts"?
This is the code in tf file:
timeouts = {
create = "${var.db_instance_create_timeout}"
update = "${var.db_instance_update_timeout}"
delete = "${var.db_instance_delete_timeout}"
}
I am not sure how to fix this error.
above error was fixed by removing "=" after timeouts.
I am also getting more errors, that need solutions:
Error: Unsupported argument
on .terraform/modules/rds/main.tf line 150, in resource "aws_db_parameter_group" "db_parameter_group":
150: parameter = concat(var.parameters, local.parameters[local.parameter_lookup])
An argument named "parameter" is not expected here. Did you mean to define a
block of type "parameter"?
Code in tf file:
parameter = concat(var.parameters, local.parameters[local.parameter_lookup])
how to fix this?
I am copying the solution that worked for me from github , credits to hashicorp member bflad :
In Terraform 0.12 (or higher), the configuration language parser is stricter about the distinction between arguments and configuration blocks. This error:
An argument named "XXX" is not expected here. Did you mean to
define a block of type "XXX"?
Generally means the = (equals sign) needs to be removed from an argument assignment so it parses correctly as a configuration block, e.g.
root_block_device {
This distinction in HCL syntax may seem trivial, but under the hood this stricter type checking allowed for consistency with JSON syntax. More information about this change can be found in the Terraform 0.12 Upgrade Guide. Speaking of which, in that guide it does point to the helpful terraform 0.12upgrade command, which should automatically fix issues like these across your Terraform configurations when upgrading from Terraform 0.11. 👍
Error
An argument named "secret_environment_variables" is not expected here.
Did you mean to define a block of type "secret_environment_variables"?
Problem
main.tf
resource "google_cloudfunctions_function" "this" {
secret_environment_variables = var.secret_environment_variables
}
variables.tf
variable "secret_environment_variables" {
type = any
default = {}
description = "Secret environment variables configuration."
}
Solution
resource "google_cloudfunctions_function" "this" {
secret_environment_variables {
key = var.secret_environment_variables_key
secret = var.secret_environment_variables_secret
version = var.secret_environment_variables_version
}
}
variable "secret_environment_variables_key" {
type = string
default = null
nullable = true
description = "Name of the environment variable."
}
variable "secret_environment_variables_secret" {
type = string
default = null
nullable = true
description = "ID of the secret in secret manager (not the full resource name)."
}
variable "secret_environment_variables_version" {
type = string
default = null
nullable = true
description = "Version of the secret (version number or the string `latest`). It is recommended to use a numeric version for secret environment variables as any updates to the secret value is not reflected until new clones start."
}

How to create an RDS instance from the most recent snapshot or from scratch

In terraform, is there a way to conditionally create an RDS instance from the most recent snapshot of a given database or to create an empty database depending on the value of a parameter?
I tried something like that:
variable "db_snapshot_source" {
default = ""
}
data "aws_db_snapshot" "last_snap" {
count = "${var.db_snapshot_source == "" ? 0 : 1}"
most_recent = true
db_instance_identifier = "${var.db_snapshot_source}"
}
resource "aws_db_instance" "db" {
[...]
snapshot_identifier = "${var.db_snapshot_source == "" ? "" : data.aws_db_snapshot.last_snap.db_snapshot_identifier}"
}
Unfortunately, it does not work because TF seems to dereference data.aws_db_snapshot.last_snap even if the ternary is false. I get the following error message: * aws_db_instance.db: Resource 'data.aws_db_snapshot.last_snap' not found for variable 'data.aws_db_snapshot.last_snap.db_snapshot_identifier'.
How can I achieve a such behaviour? The only option I see is to declare two aws_db_instance resources each with opposed count which is horrifying.
By defining a count you are saying the result of the data resource will be a list even if it is a zero value.
resource "aws_db_instance" "db" {
[...]
snapshot_identifier = "${
var.db_snapshot_source == "" ? "" :
element(
concat(data.aws_db_snapshot.last_snap.*.db_snapshot_identifier, list("")), 0)
}"
}
The concat is required if you expect the list to be empty. Otherwise you get an error
element: element() may not be used with an empty list...
Github issue describing the concat behaviour
The documentation reads as though specifying snapshot_identifier is what triggers using a snapshot or not, so passing in an empty string is not enough to avoid starting from a snapshot. In that case, you would need two aws_rds_instance resources, and then have ternary expressions for count on each resource to decide which one to create. As you mentioned, this is horrifying, but it might work ok.
Another way to think about it is if you had a blank snapshot in your inventory to start from. Then it's just a ternary operator away from deciding to use the custom snapshot or this blank snapshot. I don't know that you can create a blank snapshot in Terraform though, it's creation might be out of band.