How to use file function dynamically with the policy name from .tfvars

I have a list of policies in custom_role_policies which is a list of maps in the main module,and for now i am passing the POLICY HEREDOC in tfvars directly ,which i dont want.I would like to parse the file function with the same of name of the policy in locals , is it possible ?

I am using this locals to convert the variable key into map modules instead of writing seperate modules

file/folder structure
.
├── main.tf
├── policies
│   ├── test_policy1.json
│   └── test_policy2.json
├── provider.tf
├── roles.tfvars
├── terragrunt.hcl
└── variables.tf

1 directory, 7 files

My tfvars
---
roles_config =  {
 "test_role" = {
   role_path                = "/service-role/"
   custom_role_trust_policy = <<POLICY  #### Instead of using as direct json statement in tfvars
SOMETHING IN JSON  
   POLICY
   custom_policies_needed   = true
   custom_role_policies = [
     {
       name   = "test_policy1"
       policy = file("${path.module}/policies/name_of_the_policy.json")  #### This is not allowed in tfvars
     },
     {
       name   = "test_policy2"  
       policy = file("${path.module}/policies/name_of_the_policy.json") #### This is not allowed in tfvars
     }
   ]
 },
}


Here is my main.tf
---

module "mi-roles" {
 source                       = "../../../modules/iam-role-with-policy"
 for_each                     = { for rolesconfig in local.roles_config : rolesconfig.name => rolesconfig }
 create_role                  = true
 role_name                    = each.key
 role_path                    = try(each.value.role_path)
 custom_role_trust_policy     = try(each.value.custom_role_trust_policy)
 custom_role_policy_arns      = try(each.value.custom_role_policy_arns)
 custom_role_policies         = try(each.value.custom_role_policies) 
 managed_role_inline_policies = try(each.value.inline_policies)
 tags = {
   "RoleName" = try(each.key)
 }
}


locals {
 roles_config = flatten([
   for roles_key, roles_value in var.roles_config : [
     for roles_count in range(1) : {
       name                     = try(roles_key)
       role_path                = try(roles_value.role_path, "/")
       custom_role_policy_arns  = try(roles_value.custom_role_policy_arns, [])
       custom_role_trust_policy = try(roles_value.custom_role_trust_policy)
       custom_role_policies     = try(roles_value.custom_policies_needed, false) ? roles_value.custom_role_policies: []
       inline_policies          = try(roles_value.inline_policies_needed, false) ? roles_value.custom_inline_policies : []
     }
   ]
 ])
}

As you have discovered, Terraform variables are pure data, not code snippets.

Therefore, place the filename in the variable, and move the use of the file function into your main Terraform code, where it can decide which file to load based on the value of the variables.

Also, please look back at your code, and see how the forum software has messed it up by trying to interpret special characters as formatting.

You need to enclose code in ``` marker lines if you want people to be able to read it clearly.

Hey @maxb ,

Thanks for pointing out the post . Yes i can do that.But the problem is .I dont want to hardcode the file name in variable and parse it file function in mainmodule.

The main reason is . there are few other resources/functions are pointing to the main.If i change this in main .it will affect other areas as well .

Main moto of me trying to make the code as dry as possible for the reuse whenever there is a change in main module.

So trying something with conditions,concat and all other possible functions in local itself .

Cheers and thanks.!

If the file name is in a variable, that’s the opposite of hardcoded, no?

I know doing below works ,but… I don’t want to touch the main module .The reason being we don’t maintain the main module and we just replace it with new versions .

So if i touch the main and update other submodules/resources will affect.Let me dig something in and see if i get work this at top layer rather than at root layer itself

by the way here is what it is working temporarily

Main resource from a root module

resource "aws_iam_policy" "custom" {
  count = length(var.custom_role_policies)

  name        = var.custom_role_policies[count.index]["name"]
  policy      = file(var.custom_role_policies[count.index]["policy"])  ### changed here 
  description = lookup(var.custom_role_policies[count.index], "description", null)
  path        = var.role_path
  tags        = var.tags
  lifecycle {
    ignore_changes = [
      description,
      path
    ]
  }  
}

This is from tfvars
----
   custom_role_policies = [
     {
       name   = "test_policy1"
       policy = "./policies/test_policy1.json" ## Actual change
     },
     {
       name   = "test_policy2"  
       policy = "./policies/test_policy2.json" ## Actual change
     }
   ]

I’m having difficulty following what you consider as the main module here.

If it’s the main.tf you have shown the content of… no, you have to change it.

If it’s the module pointed to by "../../../modules/iam-role-with-policy", there’s no need to change it.


About your temporary resource block:

It is not a good thing to use count in this way, because it will lead to Terraform doing lots of unnecessary changes, if the input configuration changes order, or has new entries added/removed so existing ones shift index. You should always use for_each over count, when working with objects which have any sort of name.

The ignore_changes here also looks wrong to me, and could be saving up trouble for the future, by allowing your actual infrastructure to drift from your code.


Moving back to your original main.tf:

This all seems incredibly over-complicated.

You are starting out with a map of roles in your tfvars, then you’re turning it into a list of lists, including a redundant for roles_count in range(1) : expression, then flattening it, only to turn it back into a map to use it with for_each ??

You are using try() all over the place, even on things which cannot possibly generate errors.

You have additional values in your tfvars, custom_policies_needed and inline_policies_needed, which seem redundant, since they simply override another value to be treated as an empty list when false.

I strongly recommend removing unnecessary layers of abstraction here, so the code can be understood more easily. To me it looks like this:

module "mi-roles" {
 source                       = "../../../modules/iam-role-with-policy"
 for_each                     = var.roles_config
 create_role                  = true
 role_name                    = each.key
 role_path                    = try(each.value.role_path, "/")
 custom_role_trust_policy     = try(each.value.custom_role_trust_policy)
 custom_role_policy_arns      = try(each.value.custom_role_policy_arns, [])
 custom_role_policies         = try(each.value.custom_role_policies, [])
 managed_role_inline_policies = try(each.value.inline_policies, [])
 tags = {
   "RoleName" = each.key
 }
}

would replace your entire current main.tf.

Then, you need to add the file reading on top of that, so instead of

 custom_role_policies         = try(each.value.custom_role_policies, [])

something like

 custom_role_policies         = [ for input in try(each.value.custom_role_policies, []) :
                                  {
                                    name = input.name
                                    policy = file("${path.module}/${input.policy}")
                                  }
                                ]