Terraform hash input variables - terraform

when working with the resources of the random provider, it's useful to configure keepers so that the rendered result changes when the related keepers changes. I'd love to define all of my input variables as keepers so that the random_string changes whenever the inputs change.
The minimal example is sth like:
variable "var1" { type = "string" }
variable "var2" { type = "string" }
resource "random_string" "rnd" {
length = 16
special = false
keepers = {
variables = "${sha256(jsonencode(var))}"
}
}
output "rnd" {
value = "${random_string.rnd.result}"
}
Unfortunately this will create this error:
random_string.rnd: invalid variable syntax: "var". Did you mean 'var.var'? If this is part of inline `template` parameter
then you must escape the interpolation with two dollar signs. For
example: ${a} becomes $${a}.
The only solution I found so far is to "embed" all of the input variables into the resource definition like so:
variable "var1" { type = "string" }
variable "var2" { type = "string" }
resource "random_string" "rnd" {
length = 16
special = false
keepers = {
variables = "${sha256("${var.var1}${var.var2}")}"
}
}
Is there a more flexible way to solve this in terraform?
Thx

I also had this issue already in another use case. But there is no proper solution so far as you can not access all available variables.
The only solution is the one you described in your question already.
In addition you could use e.g a template_file or a locale to move the ugly part into another resource:
locals {
var-map = {
var1 = "${var.var1}"
var2 = "${var.var2}"
}
}
resource "random_id" "rnd" {
byte_length = 8
keepers = "${local.var-map}"
}

Related

Creating a dynamic secret variable block within Terraform for Cloud Run

I'm trying to create the following block dynamically based on a list of strings
env {
name = "SECRET_ENV_VAR"
value_from {
secret_key_ref {
name = google_secret_manager_secret.secret.secret_id
key = "1"
}
}
}
Based off documentation: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service#example-usage---cloud-run-service-secret-environment-variables
I would like to dynamically add Secrets, and have defined the following dynamic block:
dynamic "env" {
for_each = toset(var.secrets)
content {
name = each.value
value_from {
secret_key_ref {
name = each.value
key = "1"
}
}
}
}
Where secrets is a variable of type list(string)
However, this throws an error: Blocks of type "value_from" are not expected here.
I'm not sure what I'm missing, or where I have incorrectly specified the value_from block.
Could someone point me in the right direction for fixing this up?
UPDATE;
I have also tried to implement this variable as a map, as per the suggestion in the comments on this post. (https://www.terraform.io/docs/language/expressions/dynamic-blocks.html#multi-level-nested-block-structures)
dynamic "env" {
for_each = var.secrets
content {
name = each.key
dynamic "value_from" {
for_each = env.value.name
secret_key_ref {
name = value_from.value.name
key = value_from.value.version
}
}
}
}
However, this also gives the same error. Blocks of type "value_from" are not expected here.
In this example, the secrets variable is defined as a list(any) with this value:
secrets = [
{
name = "SECRET"
version = "1"
}
]
You have to upgrade your gcp provider. Support for secrets in google_cloud_run_service was added in v3.67.0. Current version is v4.1.0, which means that you must be using very old gcp provider.
In the end, I solved this by changing the variable type to a map(any):
secrets = {
"SECRET" = "1"
}
This allowed me to create the "dynamic" env block, without needing to implement the nested dynamic block.

Terraform conditionally override module variable

I want to conditionally override a module variable that has a default value at plan time. I.e. when the condition is true an override is provided, when it is false no override is provided and the default value is used. Example:
main.tf:
terraform {
required_version = ">= 0.14.9"
}
variable "random" {
}
module "my_animal_module" {
source = "./my-animal-module"
species = var.random > 7 ? "monkey" : "horse"
}
my-animmal-module/main.tf:
variable species {
default = "horse"
}
resource "local_file" "animal" {
content = "${var.species}"
filename = "./animal.txt"
}
As above, I can just provide the default (species = var.random > 7 ? "monkey" : "horse") but that requires the caller knows the module's default value which breaks encapsulation. An alternative is to use some place holder for the default value like "" then test for that condition in the module and use a different value as suggested in this SO answer. This is slightly better but still tedious and indirect. That SO answer is over 3y old and terraform has changed a lot since then. So I'm wondering, is there is a clean way to solve this yet? Essentially what's needed is the dynamic variable analogy to dynamic blocks but AFAIK it does not yet exist.
I would reorganize your module as shown below. Basically you would use local.species value instead of using var.species directly. The local.species would be set based on the values from the parent.
variable species {
default = null
}
locals {
defaults = {
species = "horse"
}
species = coalesce(var.species, local.defaults["species"])
}
resource "local_file" "animal" {
content = "${local.species}"
filename = "/tmp/animal.txt"
}
Then in the parent:
module "my_animal_module" {
source = "./my-animal-module"
species = var.random > 7 ? "monkey" : null
}
You can use conditional expression. Please refer below page:
https://www.terraform.io/docs/language/expressions/conditionals.html
Or you can use validation inside variable block. Refer below page:
https://www.terraform.io/docs/language/values/variables.html
Let me know if it helps

Is there any method for looping over all variables in Terraform?

I'd like to have a resource block that can loop over every variable that I've defined with variable blocks. Is this possible?
For Example: Assuming I set myfirstvar and mysecondvar in a tfvar file. I'm searching for a solution that would take the following template and deploy all the names, values and descriptions to Terraform Cloud.
variable "myfirstvar" {
type = string
description = "a var to upload"
}
variable "mysecondvar" {
type = string
description = "another var to upload"
}
resource "tfe_variable" "test" {
for_each = var
key = currentvar.key
value = currentvar.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = currentvar.description
}
So far the only solutions I can think of are to put all tfvars in a single list type variable, but that wouldn't have the additional info from the variable blocks. Or I could do some further parsing in another program on the variable blocks and tfvars files gather all the info necessary. Neither is ideal.
The the answer to your direct question is no, because var is not actually an object in Terraform. Instead, it's just a prefix that all variable symbols have. This distinction is important in Terraform because it works by creating a dependency graph based on your references between objects, and there is no node in the dependency graph representing "all variables" and so there's no way for Terraform to represent a reference to it.
With that said, the typical way to achieve a similar result is to write a single variable that has a map type, like this:
variable "tfe_variables" {
type = map(object({
value = string
description = string
}))
}
resource "tfe_variable" "test" {
for_each = var.tfe_variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}
Then when you define this variable you can set it to a map of objects, like this:
tfe_variables = {
myfirstvar = {
value = "example"
description = "a var to upload"
}
mysecondvar = {
value = "example"
description = "another var to upload"
}
}
It seems like you'd prefer to have the variables of this configuration match the variables defined on the workspace you're managing with it. There's no direct way to do that, because the variable definitions of the current configuration are metadata for outside consumption and not directly accessible by the module itself. The closest you could get to it is to construct the necessary map explicitly, so that Terraform can see all of the necessary dependency edges in order to execute the program:
variable "myfirstvar" {
type = string
}
variable "mysecondvar" {
type = string
}
locals {
variables = tomap({
myfirstvar = {
value = var.myfirstvar
description = "a var to upload"
}
mysecondvar = {
value = var.mysecondvar
description = "another var to upload"
}
})
}
resource "tfe_variable" "test" {
for_each = local.variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}

How to handle optional dynamic blocks in terraform

I am trying to iterate in resource "launchdarkly_feature_flag" variables with dynamic block that might have optional nested objects - "variations" (could be 0 or 2+):
variable "feature_flags" {
default = {
flag_1 = {
project_key = "project"
key = "number example"
name = "number example flag"
description = "this is a multivariate flag with number variations."
variation_type = "number"
variations = {
value = 100
}
variations = {
value = 300
}
tags = ["example"]
},
flag_2 = {
project_key = "project"
key = "boolean example"
name = "boolean example flag"
description = "this is a boolean flag"
variation_type = "boolean"
tags = ["example2"]
}
}
}
Ive tried various scenarios of how to get all flag and always face different problems. Piece of code:
resource "launchdarkly_feature_flag" "ffs" {
for_each = var.feature_flag_map
project_key = each.value.project_key
key = each.value.key
name = each.value.name
description = each.value.description
variation_type = each.value.variation_type
# main problem here
dynamic variations {
for_each = lookup(each.value, "variations", {}) == {} ? {} : {
content {
value = each.value.variations.value
}
}
}
tags = each.value.tags
}
Could you please help with that? I am using 0.14v of Terraform
The first step would be to tell Terraform what type of value this variable expects. While it's often okay to omit an explicit type for a simple value and let Terraform infer it automatically from the default, when the data structure is this complex it's better to tell Terraform what you intended, because then you can avoid it "guessing" incorrectly and giving you confusing error messages as a result.
The following looks like a suitable type constraint for the default value you showed:
variable "feature_flags" {
type = map(object({
project_key = string
key = string
name = string
description = string
variation_type = string
tags = set(string)
variations = list(object({
value = string
}))
}))
}
With the type written out, Terraform will guarantee that any var.feature_flags value conforms to that type constraint, which means that you can then make your dynamic decisions based on whether the values are null or not:
resource "launchdarkly_feature_flag" "ffs" {
for_each = var.feature_flags
project_key = each.value.project_key
key = each.value.key
name = each.value.name
description = each.value.description
variation_type = each.value.variation_type
tags = each.value.tags
dynamic "variations" {
for_each = each.value.variations != null ? each.value.variations : []
content {
variations.value.value
}
}
}
As written above, Terraform will require that all values in feature_flags have all of the attributes defined, although the caller can set them to null to indicate that they are unset.
At the time of writing, in Terraform v0.14, there is an experimental feature for marking attributes as optional which seems like it would, once stabilized, be suitable for this use-case. Marking some or all of the attributes as optional would allow callers to omit them and thus cause Terraform to automatically set them to null, rather than the caller having to explicitly write out the null value themselves.
Hopefully that feature is stabilized in v0.15, at which point you could return to this and add the optional annotations to some attributes without changing anything else about the module.

Make Terraform resource key multiline

I am declaring a google_logging_metric resource in Terraform (using version 0.11.14)
I have the following declaration
resource "google_logging_metric" "my_metric" {
description = "Check for logs of some cron job\t"
name = "mycj-logs"
filter = "resource.type=\"k8s_container\" AND resource.labels.cluster_name=\"${local.k8s_name}\" AND resource.labels.namespace_name=\"workable\" AND resource.labels.container_name=\"mycontainer-cronjob\" \nresource.labels.pod_name:\"my-pod\""
project = "${data.terraform_remote_state.gke_k8s_env.project_id}"
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
}
}
Is there a way to make the filter field multiline?
The existence of the local variable "${local.k8s_name} makes it a bit challenging.
From the docs
String values are simple and represent a basic key to value mapping
where the key is the variable name. An example is:
variable "key" {
type = "string"
default = "value"
}
A multi-line string value can be provided using heredoc syntax.
variable "long_key" {
type = "string"
default = <<EOF
This is a long key.
Running over several lines.
EOF
}

Resources