Search code examples
loopsterraformterraform-provider-azure

Terraform - How to use same resource creation code block for multiple role assignments


I am sure there is a better way to achieve it hence I thought to ask it here. Currently I am creating PIM role assignment (both active and eligible) in a different resource block for each of the default roles in Azure. e.g. Data Factory Contributor, Grafana Editor and many others.

I would like to compress the code for each resource creation block and also variables block if possible so that I am not duplicating same code again and again for each of the role assignment.

Can someone please help with their expertise on Terraform to achieve this? I don't want to over complicate it as well so if its simple, I would like to go for it.

//azurerm_role_definition code block
data "azurerm_role_definition" "roles" {
  for_each = toset(["Data Factory Contributor", "Grafana Editor"])
  name     = each.value
}

variable "groupids_data_factory_contributor" {
  type    = list(string)
  default = ["387400af-3a87-4ee6-ac9b-487283203ecb", "d5ad0c72-5c44-422a-bd2f-7261a1415ed1"]
}
variable "groupids_grafana_editor" {
  type    = list(string)
  default = ["387400af-3a87-4ee6-ac9b-487283203ecb", "875a79c6-26dc-4402-a9f4-7a18e3fbde0e"]
}

resource "azurerm_pim_active_role_assignment" "data_factory_contributor" {
  for_each = toset(var.groupids_data_factory_contributor)
  scope              = data.azurerm_subscription.primary.id
  role_definition_id = "${data.azurerm_subscription.primary.id}${data.azurerm_role_definition.roles["Data Factory Contributor"].id}"
  principal_id       = each.value
  schedule {
    start_date_time = time_static.example.rfc3339
    expiration {
      duration_hours = 8
    }
  }
  ticket {
    number = "1"
    system = "blank"
  }
  justification = "Expiration Duration Set"
}

resource "azurerm_pim_active_role_assignment" "grafana_editor" {
  for_each = toset(var.groupids_grafana_editor)
  scope              = data.azurerm_subscription.primary.id
  role_definition_id = "${data.azurerm_subscription.primary.id}${data.azurerm_role_definition.roles["Grafana Editor"].id}"
  principal_id       = each.value
  schedule {
    start_date_time = time_static.example.rfc3339
    expiration {
      duration_hours = 8
    }
  }
  ticket {
    number = "1"
    system = "blank"
  }
  justification = "Expiration Duration Set"
}

I am expecting the code to be compressed into single resource creation block so that I am not duplicating it for each role assignment.


Solution

  • Using same resource creation code block for multiple role assignments using terraform.

    Here to achieve this we can use 'for_each' and 'locals' to avoid duplication for each role assignment.

    As Lorenzo Felletti suggested we can use role_definition_id in the new field. Define the roles you want to assign in the local map and by using dynamic modules in azurerm_pim_active_role_assignment

    The code which I shared below uses the single resource block for multiple role assignments.

    Terraform code :

    provider "azurerm" {
      features {}
    }
    
    data "azurerm_subscription" "primary" {}
    
    data "azurerm_role_definition" "roles" {
      for_each = toset(["Data Factory Contributor", "Grafana Editor"])
      name     = each.value
    }
    
    variable "role_assignments" {
      type = map(list(string))
      default = {
        "Data Factory Contributor" = ["733f2806-xxx-b59af3b21126", "ea5ab011-xxx-ed2350d96cc4"]
        "Grafana Editor"           = ["733f2806-xxx-b59af3b21126", "ea5ab011-xxx-ed2350d96cc4"]
      }
    }
    
    resource "time_static" "example" {
      rfc3339 = "2023-06-18T00:00:00Z"
    }
    
    locals {
      combined_assignments = flatten([
        for role, ids in var.role_assignments : [
          for id in ids : {
            role_definition_id = data.azurerm_role_definition.roles[role].id
            principal_id       = id
            role               = role
          }
        ]
      ])
    }
    
    resource "azurerm_role_assignment" "existing" {
      for_each = { for idx, val in local.combined_assignments : idx => val }
    
      scope              = data.azurerm_subscription.primary.id
      role_definition_name = each.value.role
      principal_id       = each.value.principal_id
    
      skip_service_principal_aad_check = true
      depends_on = [
        data.azurerm_subscription.primary
      ]
    }
    
    resource "azurerm_pim_active_role_assignment" "assignments" {
      for_each = { for idx, val in local.combined_assignments : idx => val }
    
      scope              = data.azurerm_subscription.primary.id
      role_definition_id = each.value.role_definition_id
      principal_id       = each.value.principal_id
    
      schedule {
        start_date_time = time_static.example.rfc3339
        expiration {
          duration_hours = 8
        }
      }
      
      ticket {
        number = "1"
        system = "blank"
      }
      
      justification = "Expiration Duration Set"
    
      lifecycle {
        create_before_destroy = true
        ignore_changes        = [justification]
      }
    
      timeouts {
        create = "10m"
        delete = "10m"
      }
    
      depends_on = [
        azurerm_role_assignment.existing
      ]
    }
    

    Deployment succeeded:

    enter image description here

    enter image description here

    enter image description here