Terrafrom reading yaml file and assign local varible - terraform

I am trying to read the yaml file and assign value to local variable, below code giving `Invalid index' error. how to fix this error message?
YAML file server.yaml
vm:
- name: vmingd25
- system_cores: 4
Code block
locals {
vm_raw = yamldecode(file("server.yaml"))["vm"]
vm_name= local.vm_raw["name"]
vm_cpu = local.vm_raw["system_cores"]
}
Error message
╷
│ Error: Invalid index
│
│ on main.tf line 16, in locals:
│ 16: vm_name= local.vm_raw["name"]
│ ├────────────────
│ │ local.vm_raw is tuple with 10 elements
│
│ The given key does not identify an element in this collection value: a number is required.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 17, in locals:
│ 17: vm_cpu = local.vm_raw["system_cores"]
│ ├────────────────
│ │ local.vm_raw is tuple with 10 elements
│
│ The given key does not identify an element in this collection value: a number is required.

Your YAML is equivalent to the following JSON:
{
"vm": [
{
"name": "vmingd25"
},
{
"system_cores": 4
}
]
}
As you can see the vm element is a list of objects because you are using the - character. This means you need to either:
Change you YAML to remove the - list definition. e.g.
vm:
name: vmingd25
system_cores: 4
This would turn the list into a dictionary so you could index with the keys as you have done in your question. OR
If you cannot change the YAML then you will need to index with an integer. This might work if your YAML never changes but is definitely not recommended.

Related

Parse yaml file to run a for_each loop in terraform for multiple resources creation

I have the following config.yaml file
entities:
admins:
someone#somewhere.com
viewers:
anotherone#somewhere.com
And I want to create vault entities based on the users below the yaml nodes admins and viewers
locals {
config = yamldecode(file("config.yaml"))
admins = keys(local.config["entities"]["admins"]...)
viewers = keys(local.config["entities"]["viewers"]...)
}
resource "vault_identity_entity" "admins" {
for_each = toset(local.admins)
name = each.key
policies = ["test"]
metadata = {
foo = "bar"
}
}
resource "vault_identity_entity" "viewers" {
for_each = toset(local.viewers)
name = each.key
policies = ["test"]
metadata = {
foo = "bar"
}
}
The code above fails with:
│ Error: Invalid expanding argument value
│
│ on ../../../../entities/main.tf line 3, in locals:
│ 3: admins = keys(local.config["entities"]["admins"]...)
│ ├────────────────
│ │ while calling keys(inputMap)
│ │ local.config["entities"]["admins"] is "someone#somewhere.com"
│
│ The expanding argument (indicated by ...) must be of a tuple, list, or set
│ type.
╵
╷
│ Error: Invalid index
│
│ on ../../../../entities/main.tf line 4, in locals:
│ 4: viewers = keys(local.config["entities"]["viewers"]...)
│ ├────────────────
│ │ local.config["entities"] is object with 2 attributes
│
│ The given key does not identify an element in this collection value.
How should I structure my yaml file?
It seems like you want the YAML to be a hash of a hash of a list of strings. You can restructure for that like:
entities:
admins:
- someone#somewhere.com
viewers:
- anotherone#somewhere.com
This will recast to map(map(list(string))) when yamldecode from a YAML format string to HCL2.
However, you are also attempting to convert the type with the ellipsis operator ... in your locals, and returning only the keys. I am unsure why you are doing that, and both should be removed:
admins = local.config["entities"]["admins"]
viewers = local.config["entities"]["viewers"]
Afterwards, you can convert to a set type with toset like you are doing already, and then leverage that within the for_each meta-argument as per usual:
for_each = toset(local.admins)
for_each = toset(local.viewers)
This will result in the desired behavior.

Handling list of maps in for loop in terraform

I have the following locals file. I need to get the child and parent names separately in for each in terraform.
locals:
{
l3_crm:
[
{ parent: "crm", child: ["crm-sap", "crm-sf"] },
{ parent: "fin", child: ["fin-mon"] },
]
}
For the following ou creation code in aws, parent_id needs the parent name from the locals and ou_name needs the corresponding child name iterated.
module "l3_crm" {
source = "./modules/ou"
for_each = { for idx, val in local.l3_crm : idx => val }
ou_name = [each.value.child]
parent_id = module.l2[each.key.parent].ou_ids[0]
depends_on = [module.l2]
ou_tags = var.l2_ou_tags
}
I get the following error:
│ Error: Unsupported attribute
│
│ on main.tf line 30, in module "l3_rnd":
│ 30: parent_id = module.l2[each.key.parent].ou_ids[0]
│ ├────────────────
│ │ each.key is a string, known only after apply
│
│ This value does not have any attributes.
╵
Let me know what I am doing wrong in for loop.
I tried this as well:
module "l3_rnd" {
source = "./modules/ou"
for_each = { for parent, child in local.l3_crm : parent => child }
ou_name = [each.value]
parent_id = module.l2[each.key].ou_ids[0]
depends_on = [module.l2]
ou_tags = var.l2_ou_tags
}
with the local.tf:
locals {
l3_crm = [
{ "rnd" : ["crm-sap", "crm-sf"] },
{ "trade" : ["fin-mon"] }
]
}
I get these errors:
╷
│ Error: Invalid value for module argument
│
│ on main.tf line 28, in module "l3_crm":
│ 28: ou_name = [each.value]
│
│ The given value is not suitable for child module variable "ou_name" defined
│ at modules\ou\variables.tf:1,1-19: element 0: string required.
╵
╷
│ Error: Invalid value for module argument
│
│ on main.tf line 28, in module "l3_crm":
│ 28: ou_name = [each.value]
│
│ The given value is not suitable for child module variable "ou_name" defined
│ at modules\ou\variables.tf:1,1-19: element 0: string required.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 29, in module "l3_crm":
│ 29: parent_id = module.l2[each.key].ou_ids[0]
│ ├────────────────
│ │ each.key is "1"
│ │ module.l2 is object with 2 attributes
│
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 29, in module "l3_crm":
│ 29: parent_id = module.l2[each.key].ou_ids[0]
│ ├────────────────
│ │ each.key is "0"
│ │ module.l2 is object with 2 attributes
│
│ The given key does not identify an element in this collection value.
╵
time=2022-11-11T13:24:15Z level=error msg=Hit multiple errors:
Hit multiple errors:
exit status 1
With your current structure you can reconstruct the map in your meta-argument like:
for_each = { for l3_crm in local.l3_crm : l3_crm.parent => l3_crm.child }
to access the values of each key in the list element and reconstruct to a map of parent keys and child values.
You can also optimize the structure like:
l3_crm:
[
{ "crm" = ["crm-sap", "crm-sf"] },
{ "fin" = ["fin-mon"] },
]
and then:
for_each = { for parent, child in local.l3_crm : parent => child }
where you cannot simply convert to a set type with toset because set(map) is not allowed as an argument value type.
Either way the references are updated fully accordingly:
ou_name = [each.key]
parent_id = module.l2[each.value].ou_ids[0]

Yamldecode - Cannot include the given value in a string template: string required

I have some very simple code to do some testing with and I am running into some issues.
My overall goal is to have config stored in a .yaml file which I then use templatefile to replace some variables and finally it will be jsonencoded.
main.tf
locals {
test = yamldecode(templatefile("${path.module}/test.yaml",
{
admin_groups = ["123456-7890"]
}
))
}
output "test" {
value = jsonencode(local.test)
}
test.yaml
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['omniauth_providers'] = |
admin_groups: ['${admin_groups}']
When terraform plan however I am getting the following error message:
│ Error: Error in function call
│
│ on main.tf line 2, in locals:
│ 2: test = yamldecode(templatefile("${path.module}/test.yaml",
│ 3: {
│ 4: admin_groups = ["123456-7890"]
│ 5: }
│ 6: ))
│ ├────────────────
│ │ path.module is "."
│
│ Call to function "templatefile" failed: ./test.yaml:3,25-37: Invalid template interpolation value; Cannot include the given value in a string template: string required..
Is there some way to interpolate this list of strings that I am not doing?
Cheers for any help.
For list in yaml, you should iterate the values like:
test.yaml
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['omniauth_providers'] = |
admin_groups:
%{ for g in admin_groups ~}
- ${g}
%{ endfor ~}

Terraform output being flagged as sensitive

We have created some terraform stacks for different domains, like network stack for vpc, rds stack for rds stuff, etc.
And, for instance, the rds stack depends on the network stack to get values from the outputs:
Output from network stack:
output "public_subnets" {
value = aws_subnet.public.*.id
}
output "private_subnets" {
value = aws_subnet.private.*.id
}
output "data_subnets" {
value = aws_subnet.data.*.id
}
... an so on
And the rds stack will tap on the outputs
data "tfe_outputs" "networking" {
organization = "my-tf-cloud-org"
workspace = "network-production-eucentral1"
}
But when I try to use the output:
│
│ on main.tf line 20, in module "db":
│ 20: base_domain = data.tfe_outputs.dns.values.fqdn
│ ├────────────────
│ │ data.tfe_outputs.dns.values has a sensitive value
│
│ This object does not have an attribute named "fqdn".
╵
╷
│ Error: Unsupported attribute
│
│ on main.tf line 22, in module "db":
│ 22: subnets = data.tfe_outputs.networking.values.data_subnets
│ ├────────────────
│ │ data.tfe_outputs.networking.values has a sensitive value
│
│ This object does not have an attribute named "data_subnets".
╵
╷
│ Error: Unsupported attribute
│
│ on main.tf line 23, in module "db":
│ 23: vpc_id = data.tfe_outputs.networking.values.vpc_id
│ ├────────────────
│ │ data.tfe_outputs.networking.values has a sensitive value
│
│ This object does not have an attribute named "vpc_id".
This was working before; it started all of a sudden.
I tried adding the nonsensitive cast, but it does not work.
Any idea?
Update:
I manage to fix the issue. I'm using terraform cloud with a remote state. If you go to your workspace_with_the_output general settings in tf cloud you will find an option called "Remote state sharing".
I added my workspace_which_consume_state on that list and now it's working. Hopefully, this helps

Terraform plan fails due to content argument missing in local_file resource

I have been testing out something using the terraform for_each loop method and ran into this error with the local_file resource.
$ cat main.tf
resource "local_file" "pet" {
filename = each.value
for_each = var.filename
}
$ cat variables.tf
variable "filename" {
type = set(string)
default = [
"/home/user/pets.txt",
"/home/user/dogs.txt",
"/home/user/cats.txt"
]
}
when I run terraform plan after init, I see the following errors:
$ terraform plan
╷
│ Error: Invalid combination of arguments
│
│ with local_file.pet,
│ on main.tf line 1, in resource "local_file" "pet":
│ 1: resource "local_file" "pet" {
│
│ "content_base64": one of `content,content_base64,sensitive_content,source` must be specified
╵
╷
│ Error: Invalid combination of arguments
│
│ with local_file.pet,
│ on main.tf line 1, in resource "local_file" "pet":
│ 1: resource "local_file" "pet" {
│
│ "source": one of `content,content_base64,sensitive_content,source` must be specified
╵
╷
│ Error: Invalid combination of arguments
│
│ with local_file.pet,
│ on main.tf line 1, in resource "local_file" "pet":
│ 1: resource "local_file" "pet" {
│
│ "content": one of `content,content_base64,sensitive_content,source` must be specified
╵
╷
│ Error: Invalid combination of arguments
│
│ with local_file.pet,
│ on main.tf line 1, in resource "local_file" "pet":
│ 1: resource "local_file" "pet" {
│
│ "sensitive_content": one of `content,content_base64,sensitive_content,source` must be specified
╵
From the documentation, I can see argument content is optional:
so I am confused with the above error.
Having encountered the same issue while using the count and for_each meta arguments, I resorted to creating a "content" variable with some dummy text and the errors disappeared afterwards.
But why does hashicorp say they (content, sensitive_content etc) are optional, if without them, the config won't run successfully?
I hope this helps!
As the error message says:
one of content,content_base64,sensitive_content,source must be specified
The documentation states for each of content, content_base64, sensitive_content, and source, that they are optional but also that they do conflict with the other three of them, and it also does not specify a default value.
In consequence, you need to specify exactly one of these four arguments.
Also it makes much sense, as you need to define the content of the file you want to be created.

Resources