Update terraform variable from resource - variables

I want to update a variable from a resource on terraform.
Is that possible? I'm a bit new in this technology.
variable "contador" {
default = 0
}
resource "azurerm_managed_disk" "test-disks-test3" {
count = "${length(var.disks_size) * var.vm_number}"
name = "SRV${var.service_base_name}${var.service_environment}01-DATADISK-0${count.index}"
location = "westeurope"
resource_group_name = "${azurerm_resource_group.test-rg-test3.name}"
storage_account_type = "${var.disk_tier}_${var.disk_replication}"
create_option = "Empty"
disk_size_gb = "${element(var.disks_size, count.index)}"
var.contador = "${count.index % length(var.disks_size) == (length(var.disks_size) - 1) ? (var.contador + 1) : var.contador}"
tags{
environment = "TestWork"
}
}
The line in question is:
var.contador = "${count.index % length(var.disks_size) == (length(var.disks_size) - 1) ? (var.contador + 1) : var.contador}"

TL;DR
You can't update the variable.
About HCL
Terraform is using the HCL language.
This language is declarative and not procedural or OOP.
It means once it is defined, terraform doesn't not allow you to modify its value at the run time.
From the terraform documentation:
The default value of an input variable must be a literal value, containing no interpolation expressions. To assign a name to an expression so that it may be re-used within a module, use Local Values instead.
Moreover in your resource block, you can only use arguments defined by that resource and var.contador is not one of them.

Related

Terraform function lookup within lookup

I am trying to use Terraform function lookup and then lookup to fetch the values and then add into the conditional loop based on the value like below - For creating s3 bucket server side encryption
Below is var.tf
variable "encryption" {
type = map
default = {
"keyMap" = "SSE-S3"
"kmsType" = "aws-kms"
"keyNull" = null
}
}
Now I want to use local.tf with below code to get "SSE-S3" value like below
encryption_type = lookup(var.encryption, "default", null) == null ? null : lookup(var.encryption.default, "keyMap", null)
Just wonder above my logic will fetch the value for encryption_type is "SSE-S3"
Any help is appreciated. Thanks in advance.
You don't have to lookup "default". The default inside a variable definitions is just the default value of that variable. Your current code is actually invalid because a lookup on "default" is never going to work. It's also not clear what your "keyMap" lookup is doing, since there is no property in your example named "keyMap".
Your code could be corrected and shortened to the following:
encryption_type = lookup(var.encryption, "keyType", null)
or just
encryption_type = var.encryption["keyType"]

Terraform for_each loop. Invalid index

I am facing a problem with for_each looping in terraform.
I have a azure resource for managed_keys as follow:
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
for_each = toset(var.key-name)
key_name = "Key-Client-${each.value}"
key_vault_id = azurerm_key_vault.tenantsnbshared.id
key_version = azurerm_key_vault_key.client-key[each.value].version
storage_account_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
I have a variable named key-name and a storage-account storage-foreach, both of them have a list(string) with some values.
My aim is to be able to loop through those 2 variables and encrypt the storage account with the respective key.
but if I run my code, I get this error:
Error: Invalid index
on main.tf line 173, in resource "azurerm_storage_account_customer_managed_key" "storage-managed-key":
173: storage_account_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
|----------------
| azurerm_storage_account.storage-foreach is object with 4 attributes
| each.value is "key-name"
The given key does not identify an element in this collection value.
EDIT:
resource "azurerm_storage_account" "storage-foreach" {
for_each = toset(var.storage-foreach)
access_tier = "Hot"
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
location = var.location
name = each.value
resource_group_name = azurerm_resource_group.tenant-testing-hamza.name
identity {
type = "SystemAssigned"
}
lifecycle {
prevent_destroy = false
}
}
Key vault access policies:
resource "azurerm_key_vault_access_policy" "storage" {
for_each = var.storage-foreach
key_vault_id = azurerm_key_vault.tenantsnbshared.id
tenant_id = "<tenant-id>"
object_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
key_permissions = ["get", "Create", "List", "Restore", "Recover", "Unwrapkey", "Wrapkey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify", "Delete"]
secret_permissions = ["get", "set", "list", "delete", "recover"]
depends_on = [azurerm_key_vault.tenantsnbshared]
}
variable "storage-foreach" {
type = map(string)
default = { "<name1>" = "storage1", "<name2>" = "storage2", "<name3>" = "storage3", "<name4>" = "storage4"}
}
variable "key-name" {
type = map(string)
default = {"<name1>" = "key1", "<name2>" = "<key2>", "name3" = "<key3>", "<name4>" = "key4"}
}
this error get repeated for each element I have in my key-name variable.
I tried the some thing but using a count instead of a for_each and it works just fine, but the problem I had with that, was if I wanted to delete the first storage account and the first key, it automatically destroy all the element coming after to then recreate them, and is not something I wanted to do.
Is there anyone who can help me to understand this error and how to fix it please?
I'm assuming the lists with keys and storage accounts are of the same length, and that you want to encrypt storage account number i with key number i. You can either use "old style", index-based loops or zip the two lists into a single list of tuples, and then iterate over the zipped list.
Solution 1: using index-based iteration
This solution does not use for_each but the meta-argument count. This way of iterating over resources in Terraform predates the newer for_each style.
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
count = length(var.key-name)
key_name = azurerm_key_vault_key.client-key[var.key-name[count.index]].name
key_vault_id = azurerm_key_vault_key.client-key[var.key-name[count.index]].key_vault_id
key_version = azurerm_key_vault_key.client-key[var.key-name[count.index]].version
storage_account_id = azurerm_storage_account.storage-foreach[var.storage-foreach[count.index]].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
I've taken the liberty to replace the explicit key name and key vault ID by references to the attributes of your key resource.
Solution 2: using combined structure
Here, the idea is to create a structure that you can iterate over, and of which the elements combine key name and the storage account names. There's multiple ways of doing this. The easiest way is probably to "misuse" a map and treat it as a list of tuples, as you can then simply use zipmap.
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
for_each = zipmap(var.storage-foreach, var.key-name)
key_name = azurerm_key_vault_key.client-key[each.value].name
key_vault_id = azurerm_key_vault_key.client-key[each.value].key_vault_id
key_version = azurerm_key_vault_key.client-key[each.value].version
storage_account_id = azurerm_storage_account.storage-foreach[each.key].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
Note that I, perhaps confusingly, chose var.storage-foreach to be the keys of the object. Picking the keys as the map keys would make it impossible to use the same key to encrypt multiple storage accounts. Since storage-foreach is already used to index Terraform resources, I also already know these adhere to the Terraform naming restrictions.
What's the difference?
In solution 1, your azurerm_storage_account_customer_managed_key resource names are integer-based. Re-ordering elements in the lists may cause Terraform to destroyed and re-create resources. This is not the case for solution 2, which is why I usually prefer solution 2. However, in this case solution 1 may have the advantage of being more straight-forward.
A suggestion...
If possible, I would suggest to re-evaluate how you define your variables. It likely makes more sense to ask for list of objects combining key name and storage accounts in the first place; then you can basically use solution 2 without the call to zipmap. It's almost never a good idea to have an implicit dependency between two variables like this - these lists have to have the same length, and implicitly, the contents of the lists are connected by virtue of having the same index.

Terraform conditions in a module

I am trying to create some simple logic when calling applicationg gateway module.
When creating WAF v2 application gateway I want to specify more attributes that simple application gateway can't handle and they won't be described.
resource "azurerm_application_gateway" {
name = var.appgatewayname
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
......................
waf_configuration {
enabled = "${length(var.waf_configuration) > 0 ? lookup(var.waf_configuration, "enabled", "") : null }"
firewall_mode = "${length(var.waf_configuration) > 0 ? lookup(var.waf_configuration, "firewall_mode", "") : null }"
............
Calling module:
module "GWdemo" {
source = "./...."
sku-name = "WAF_v2"
sku-tier = "WAF_v2"
sku-capacity = 1
waf-configuration = [
{
enabled = true
firewall_mode = "Detection"
}
Am I thinking right that if waf-configuration map is specified it should specify following settings is applied and if not than null?
When working with Terraform we often want to reframe problems involving a conditional test into problems involving a collection that may or may not contain elements, because the Terraform language features are oriented around transforming collections into configuration on an element-by-element basis.
In your case, you have a variable that is already a list, so it could work to just ensure that its default value is an empty list rather than null, and thus that you can just generate one waf_configuration block per element:
variable "waf_configuration" {
type = list(object({
enabled = bool
firewall_mode = string
}))
default = []
}
Then you can use a dynamic block to generate one waf_configuration block per element of that list:
dynamic "waf_configuration" {
for_each = var.waf_configuration
content {
enabled = waf_configuration.value.enabled
firewall_mode = waf_configuration.value.firewall_mode
}
}
Although it doesn't seem to apply to this particular example, another common pattern is a variable that can be set to enable something or left unset to disable it. For example, if your module was designed to take only a single optional WAF configuration, you might define the variable like this:
variable "waf_configuration" {
type = object({
enabled = bool
firewall_mode = string
})
default = null
}
As noted above, the best way to work with something like that in Terraform is to recast it as a list that might be empty. Because it's a common situation, there is a shorthand for it via splat expressions:
dynamic "waf_configuration" {
for_each = var.waf_configuration[*]
content {
enabled = waf_configuration.value.enabled
firewall_mode = waf_configuration.value.firewall_mode
}
}
When we apply the [*] operator to a value of a non-list/non-set type, Terraform will test to see if the value is null. If it is null then the result will be an empty list, while if it is not null then the result will be a single-element list containing that one value.
After converting to a list we can then use it in the for_each argument to dynamic in the usual way, accessing the attributes from that possible single element inside the content block. We don't need to repeat the conditionals for each argument because the content block contents are evaluated only when the list is non-empty.
I would encourage you to upgrade to Terraform v0.12.x and this should get a much easier to do. I would leverage the new dynamic block syntax to make the block optional based on whatever condition you need to use.
Here is a rough example, but should get you going in the correct direction.
dynamic "waf-configuration " {
for_each = length(var.waf_configuration) > 0 ? [] : [1]
content {
enabled = "${length(var.waf_configuration) > 0 ? lookup(var.waf_configuration, "enabled", "") : null }"
firewall_mode = "${length(var.waf_configuration) > 0 ? lookup(var.waf_configuration, "firewall_mode", "") : null }"
}
}

Terraform combine 2 variables into a new variable

I want to automate deployments of Vmware VM's in an landscape with lots of portgroups. To be able to select the correct portgroup it would be best to enter 2 variables tenant and environment. These 2 variables are used for CMDB registration and deployment purposes.
For the deployment the variables need to be combined in to 1 new variable to pick the correct portgroup. Due to interpolation syntax it seems to be impossible to use 2 combined variables in the lookup.
How can I combine 2 variables to 1 in Terraform?
I also tried to make a local file with the correct string, but that file needs to exist before the script starts, terraform plan gives a error message that the file doesn't exist.
variable "tenant" {
description = "tenant: T1 or T2"
}
variable "environment" {
description = "environment: PROD or TEST"
}
variable "vm_network" {
description = "network the VM will be provisioned with"
type = "map"
default = {
T1_PROD = "T1-PROD-network"
T2_PROD = "T2-PROD-network"
T1_TEST = "T1-TEST-network"
T2_TEST = "T2-TEST-network"
}
}
data "vsphere_network" "network" {
name = "${lookup(var.vm_network, tenant_environment)}"
datacenter_id = "${data.vsphere_datacenter.dc.id}"
}
Off the top of my head I can think of three different ways to merge the variables to use as a lookup key:
variable "tenant" {}
variable "environment" {}
variable "vm_network" {
default = {
T1_PROD = "T1-PROD-network"
T2_PROD = "T2-PROD-network"
T1_TEST = "T1-TEST-network"
T2_TEST = "T2-TEST-network"
}
}
locals {
tenant_environment = "${var.tenant}_${var.environment}"
}
output "local_network" {
value = "${lookup(var.vm_network, local.tenant_environment)}"
}
output "format_network" {
value = "${lookup(var.vm_network, format("%s_%s", var.tenant, var.environment))}"
}
output "lookup_network" {
value = "${lookup(var.vm_network, "${var.tenant}_${var.environment}")}"
}
The first option uses locals to create a variable that is interpolated already and can be easily reused in multiple places which can't be done directly with variables in Terraform/HCL. This is generally the best way to do variable combination/interpolation in later versions of Terraform (they were introduced in Terraform 0.10.3).
The second option uses the format function to create a string containing the tenant and environment variables.
The last one is a little funny looking but is valid HCL. I'd probably shy away from using that syntax if possible.

lua: module import regarding local scope

There are two script files with the following script
//parent.lua
function scope()
local var = "abc"
require "child"
end
//child.lua
print(var)
This way, child.lua will print a nil value because the scope in parent.lua does not expose its local features to the module. I thought it would, since the require directive is stated within this scope and after the declaration of var. My desire is to practically wholly inject all the lines of the child into the parent. The child script is just exported for better readability. How can I pass the local scope? loadfile() did not work, nor did dofile(). The function environment fenv does not harbor local values. debug.setlocal() does not seem to be able to create new variables (also it would require a receiver in the child). Any method besides recompiling the script?
You can with a bit of effort. If your variables in child are real upvalues you can "link" them to values in your scope function. If they are global variables (which seems to be the case here), you can map them to an environment using setfenv and populate that environment with values from your local variables.
The following will print abc as you'd expect (you can change loadstring to loadfile with the same effect):
function vars(f)
local func = debug.getinfo(f, "f").func
local i = 1
local vars = {}
while true do
local name, value = debug.getlocal(f, i)
if not name then break end
if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
i = i + 1
end
i = 1
while func do -- check for func as it may be nil for tail calls
local name, value = debug.getupvalue(func, i)
if not name then break end
vars[name] = value
i = i + 1
end
return vars
end
function parent()
local var = "abc"
local child = loadstring("print(var)")
local env = vars(2) -- grab all local/upvalues for the current function
-- use these values to populate new environment; map to _G for everything else
setmetatable(env, {__index = _G})
setfenv(child, env)
child()
end
parent()
This is all for Lua 5.1, but it's also possible in Lua 5.2.