Conditionally provision a gcp vm instance with terraform - google-cloud-platform

I would like to condition the provisioning of a resource (gcp vm instance) on a variable, for example:
resource "${var.param > 0 ? "google_compute_instance" : "null_resource"}" "cluster" {
# ...
}
but the above is not valid syntax:
Error: Invalid resource type name
A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes.
Error: Invalid string literal
Template sequences are not allowed in this string. To include a literal "$", double it (as "$$") to escape it.
Is there a way to accomplish the same? Ideally using terraform alone.

You can use count for that:
resource "google_compute_instance" {
count = var.param > 0 ? 1 : 0
}
resource "cluster" {
count = var.param > 0 ? 0 : 1
}

Related

How to use terraform workspace interpolation to separate resources creation?

Lets suppose I have dev, uat and prod environment. I wan't to have some modules to be deployed in the dev environment but not in other environment.
I want to put a condition based on the workspace I have but can't figure it out how. Any recommendation would be appreciated.
I tried to use $(terraform.workspace) to select 'dev' enviroment but wasn't working.
count = $(terraform.workspace) == "dev" ? 1 : 0 and it says
which resulted in:
This character is not used within the language.
You don't need to use $ sign.
count = terraform.workspace == "dev" ? 1 : 0
There are two different styles and two different logics to write this condition.
Different Styles
If terraform.workspace is equal to "dev" then create one instance else zero instance.
count = "${terraform.workspace}" == "dev" ? 1 : 0
count = terraform.workspace == "dev" ? 1 : 0
Another logic
If terraform.workspace is not equal to "dev" then create zero instance else one instance.
count = "${terraform.workspace}" != "dev" ? 0 : 1
count = terraform.workspace != "dev" ? 0 : 1

terraform tag value with special characters

While trying to update via web console AWS is accepting below tag values, however with terraform its hard to get things done.
The tags that I wanted to update against my resources are:
1. "IT R&D & DATA - 7777"
2. "Example Team, Inc. - 001"
Getting below error on every different try during terraform apply (However preview always shows correct data). Is there any workaround for this ?
Error: error updating LB (arn:aws:elasticloadbalancing:us-west-1:xxxx:loadbalancer/app/LB-DEV/f4c252)
tags: error tagging resource (arn:aws:elasticloadbalancing:us-west-1:xxxx:loadbalancer/app/LB-DEV/f4c252):
ValidationError: 1 validation error detected: Value 'Example Team, Inc. - 001' at 'tags.1.member.value'
failed to satisfy constraint: Member must satisfy regular expression pattern: ^([\p{L}\p{Z}\p{N}_.:/=+\-#]*)$
resource "aws_lb" "lb" {
internal = "true"
load_balancer_type = "application"
name = format("%s%s","LB-",var.name)
subnets = data.aws_subnet_ids.subnet.ids
security_groups = [
data.aws_security_group.sec_group.id
]
tags = {
business_unit = "IT R//&D //& DATA //- 7777"
legal_entity = replace("Example Team, Inc. - 001", "/(['\\*])/", "//$1")
}
}
& is not allowed in tags. From docs:
In general, the allowed characters are letters, numbers, spaces representable in UTF-8, and the following characters: _ . : / = + - #.

Getting InvalidParameterException while trying to setup cloudwatch log filter via terraform

I am trying to setup cloudwatch log filter using terrafom using below resource element (The logs are in the default format):
resource "aws_cloudwatch_log_metric_filter" "exception-filter" {
name = "Exception filter"
pattern = "Exception:"
log_group_name = "/ecs/application/log"
metric_transformation {
name = "Exceptions"
namespace = "app-custom"
value = "1"
default_value = "0"
}
}
The terraform apply command fails stating InvalidParameterException: Invalid metric filter pattern.
I tried to escape ":" using \ but doing do also I got an error that the symbol ":" is not a valid escape.
Is there any other way to specify the pattern here?
If the pattern does contain other characters then than alphanumeric or underscore, then (froo docs) it must be placed inside double quotes ("") . :
So instead of:
pattern = "Exception:"
you should have
pattern = "\"Exception:\""

Terraform decouple Security Group dependency

This is tested with Terraform v0.12.9
I generally manage security groups and security group rules as separate resources, as in the below example:
resource "aws_security_group" "this" {
count = var.create ? 1 : 0
name_prefix = "${var.security_group_name}_"
vpc_id = var.vpc_id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "ingress_rules" {
count = var.create ? length(var.inbound_security_group_ids) : 0
security_group_id = aws_security_group.this[0].id
type = "ingress"
from_port = var.from_port
to_port = var.to_port
protocol = "tcp"
source_security_group_id = var.inbound_security_group_ids[count.index]
}
The implementation for this would look something like the below:
module "test_module" {
source = "../path/to/module/"
create = true
vpc_id = "vpc-xxxxxx"
security_group_name = "${var.service_name}-db"
from_port = 1234
to_port = 1234
inbound_security_group_ids = [
module.service.security_group_id_one,
module.service.security_group_id_two
]
}
Problem
I want this to work if the outputs from the module.service aren't created. In that scenario my expectation is that length(var.inbound_security_group_ids) should evaluate to 0 resulting in the security group rules not being created
What actually happens is that length(var.inbound_security_group_ids) evaluates to 2 when module.service isn't created. This is presumably because it is an array of two blank strings ["", ""]
According to the Terraform documentation I can handle this with the compact function, which removes empty strings from an array.
resource "aws_security_group_rule" "ingress_rules" {
count = var.create ? length(compact(var.inbound_security_group_ids)) : 0
security_group_id = aws_security_group.this[0].id
type = "ingress"
from_port = var.from_port
to_port = var.to_port
protocol = "tcp"
source_security_group_id = var.inbound_security_group_ids[count.index]
}
The problem with this, however, is that Terraform is unable to determine the plan because it doesn't know what var.inbound_security_group_ids evaluates to until apply-time. This is the error message (for context):
The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.
Question
Is it possible to decouple a security group like this so that it will still be created even when the source_security_group_id attribute has no values?
It's often easier to work with lists or sets that might be empty in Terraform than to work with individual values that might not be set, for a reason related to what you've observed: it separates whether the value is set from what the value actually is, so that the presence of the value can be known even if the value itself isn't known.
In this case, you might approach that by changing how you return the security group ids from your service module, so that each of those outputs is a list of security group ids rather than a single string that might be either a valid security group id or an empty string:
module "test_module" {
source = "../path/to/module/"
create = true
vpc_id = "vpc-xxxxxx"
security_group_name = "${var.service_name}-db"
from_port = 1234
to_port = 1234
inbound_security_group_ids = concat(
module.service.security_group_ids_one,
module.service.security_group_ids_two,
)
}
If either security_group_ids_one or security_group_ids_two is known to be an empty list then concat will ignore it entirely. If they are enabled then you can arrange for them to be a known list with one element whose value is unknown, and thus length(var.inbound_security_group_ids) will still have a known value in all cases.

How to parse instance id in Terraform

We want to get away from using incremental numbers for the host names of our instances. These machines are not in an auto-scaling group, hence we could keep the prod-webserver-001, prod-webserver-002 etc convention if we wanted but it just doesn't make sense with tags being available.
What we've been doing with auto-scaling groups, we want to utilize parts of the instance id. I'm able to accomplish this with a post script with our ASG servers but unlike that, I want Terraform to keep track of the DNS record so that they get destroyed when I issue a terraform destroy.
Ideally we want the first six characters after the hyphen in the instance_id. For example if the instance_id is i-0876cr2456 we want to use 0876cr within the name.
prod-webserver-0876cr
prod-webserver-09a24i
Terraform code:
resource "aws_instance" "instance" {
...
...
}
resource "aws_route53_record" "instance_dns_a" {
count = "${var.num_instances}"
zone_id = "${var.internal_zone_id}"
# New line but we want the parsed version
name = "${aws_instance.instance.id}"
# old that works
name = "${format(prod-${service_name}-%03d", count.index + 1)}"
}
You could extract the first 6 characters from the instance id by using substr:
$ terraform console
> substr("i-123456adgcgabsadh", 2, 6)
123456
So to use it in your Route53 record you'd want to use something like:
resource "aws_instance" "instance" {
...
...
}
resource "aws_route53_record" "instance_dns_a" {
count = "${var.num_instances}"
zone_id = "${var.internal_zone_id}"
name = "prod-${var.service_name}-${substr(aws_instance.instance.*.id[count.index], 2, 6}"
}
I would be worried about truncating the instance ID for something that needs to be unique though because you will then inevitably end up with instances with ids of i-123456a... and i-123456b and then you'll end up overwriting the prod-webserver-123456 record with the IP address of the second instance and losing the first record. Any reason you need to truncate here? You have 63 characters to play with for the first label in DNS which should be enough to leave this untruncated no?