I am sure this is not a unique requirement and I have delt with this type of issue using eval() syntax in few other programming and scripting environments.
My problem: I am trying to create a azurerm_pim_active_role_assignment based on a local variable which internally is created based on a map object (group_roles). If you see the below code, I am specifically facing issue about role_definition_id = "${data.azurerm_subscription.primary.id}${local.DataFactoryContributor}"
because if I apply this logic, I have to create different resource blocks for each role definition type (e.g. DataFactoryContributor) as I am not able to do something like this: eval("${data.azurerm_subscription.primary.id}${local.${group.role})
.
What I want to understand if it is even supported in HCL to do something like this or I will have to create different resource code blocks for each role definition?
Variable object that holds all the role and Entra group details (only 1 shared as example)
variable "groupsetuparray"{
type = list(object({
name = string,
group_name = string,
resource_group_name = string,
resource_name = string,
group_roles = list(string),
members = list(string)
}))
default = [
{
name = "DFDeveloperTeam",
group_name = "DFDeveloperTeam",
resource_group_name = "",
resource_name = "",
group_roles = ["DataFactoryContributor","SQLServerContributor"]
members = []
}
Local variable to generate group roles
locals {
group_roles = flatten([for group in var.groupsetuparray : [
for role in group.group_roles : {
name = group.name
role = role
rg = group.resource_group_name
}
]
])
DataFactoryContributor = data.azurerm_role_definition.DataFactoryContributor.id
SQLServerContributor = data.azurerm_role_definition.SQLServerContributor.id
}
data resources to reference the inbuilt role definitions
data "azurerm_role_definition" "DataFactoryContributor" {
name = "Data Factory Contributor"
}
data "azurerm_role_definition" "SQLServerContributor" {
name = "SQL Server Contributor"
}
Resource for creating PIM activation role assignment using for each loop
resource "azurerm_pim_active_role_assignment" "DataFactoryContributorAssignment" {
for_each = { for group in local.group_roles : "${group.name}-${group.role}" => group if group.role == "DataFactoryContributor" }
scope = data.azurerm_subscription.primary.id
role_definition_id = "${data.azurerm_subscription.primary.id}${group.role}.id"
#=====BELOW IS CODE THAT WORKS BUT I HAVE TO GIVE DIRECT REFERENCE FOR EACH ROLE DEFINITION, WHICH IS NOT BEST WAY=====
#role_definition_id = "${data.azurerm_subscription.primary.id}${local.DataFactoryContributor}"
#=====
principal_id = azuread_group.group[each.value.name].object_id
schedule {
start_date_time = time_static.example.rfc3339
expiration {
duration_hours = 8
}
}
justification = "Expiration Duration Set"
ticket {
number = "1"
system = "example ticket system"
}
}
Error message that I am getting if I used above code block
Error: Reference to undeclared resource
│
│ on main.tf line 61, in resource "azurerm_pim_active_role_assignment" "DataFactoryContributor":
│ 61: role_definition_id = "${data.azurerm_subscription.primary.id}${group.role}.id"
│
│ A managed resource "group" "role" has not been declared in the root module.
I am still learning TF HCL (as you might feel after looking at above code) so it will be great help if someone can please point me in correct direction.
What I have already tried: I tried to use lookup function, but that doesn't work. Also I have tried different ways of expressions and interpolation but none worked.
Below is a small example using for_each
with your data...
I've done a few changes to your code:
type = any
just to make the code smallernull_resource
with some triggersvariable "groupsetuparray" {
type = any
default = [
{
name = "DFDeveloperTeam",
group_roles = ["DataFactoryContributor", "SQLServerContributor"]
}
]
}
locals {
group_roles = flatten([
for group in var.groupsetuparray : [
for role in group.group_roles : {
name = group.name
role = role
}
]
])
}
resource "null_resource" "test" {
for_each = {
for group in local.group_roles :
"${group.name}-${group.role}" => group
if group.role == "DataFactoryContributor"
}
triggers = {
"key" = each.key
"role" = each.value.role
}
}
a terraform plan on that will look like:
Terraform will perform the following actions:
# null_resource.test["DFDeveloperTeam-DataFactoryContributor"] will be created
+ resource "null_resource" "test" {
+ id = (known after apply)
+ triggers = {
+ "key" = "DFDeveloperTeam-DataFactoryContributor"
+ "role" = "DataFactoryContributor"
}
}
The two data you can refactor into a loop as well
FROM
data "azurerm_role_definition" "DataFactoryContributor" {
name = "Data Factory Contributor"
}
data "azurerm_role_definition" "SQLServerContributor" {
name = "SQL Server Contributor"
}
TO
data "azurerm_role_definition" "roles" {
for_each = toset(["SQL Server Contributor", "Data Factory Contributor"])
name = each.value
}
then you can access them with the key:
data.azurerm_role_definition.roles["SQL Server Contributor"]
Here is another example slightly more complex...
using a data "local_file"
with a loop:
variable "groupsetuparray" {
type = any
default = [{
name = "DFDeveloperTeam",
group_roles = ["DataFactoryContributor", "SQLServerContributor"]
}]
}
locals {
group_roles = flatten([
for group in var.groupsetuparray : [
for role in group.group_roles : {
name = group.name
role = role
}
]
])
}
data "local_file" "input" {
for_each = toset(["DataFactoryContributor", "SQLServerContributor"])
filename = "${path.module}/${each.value}.txt"
}
resource "null_resource" "test" {
for_each = {
for group in local.group_roles :
"${group.name}-${group.role}" => group
}
triggers = {
"key" = each.key
"role" = each.value.role
"content" = data.local_file.input[each.value.role].content
}
}
I have this full code in Github:
https://github.com/heldersepu/hs-scripts/tree/master/TerraForm/group_roles