Search code examples
azurevariablesterraform

Terraform multiple values for single variable issue


I'm trying to create an Azure application in AD using Terraform.

resource "azuread_application" "my_ad_app" {
  display_name = "my_app"

  #API Permissions
  required_resource_access {
    resource_app_id = "0000000-000000000000" # Microsoft Graph

    resource_access {
      id   = "df021288-bdef-9214" # User.Read.All
      type = "Role"
    }

    resource_access {
      id   = "18a4783c662c884" # User.Read.All
      type = "Role"
    }

    resource_access {
      id   = "9a5d68c3a30" # Application.Read.All
      type = "Role"
    }

I will be creating some more Azure AD applications with the same API permissions assigned. There are many API permissions that will be assigned and they are the same for all Azure AD applications that I will be creating. Since the API permissions are same for all the Azure AD applications which I will be creating for, I want to assign the entire required_resource_access as a variable.

Something like this.

#API Permissions
  required_resource_access {
    resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

    resource_access {
     assign the entire part as a variable here? Example: var.ms_graph_api_permission
     }

I tried to add the below as a variable but it doesn't seem to work.

variable "ms_graph_api_permission" {
  type = list(string)
  default =

resource_access {
id   = "df021288f22de89214" 
type = "Role"
}

resource_access {
id   = "18a4783e5662c884" 
type = "Role"
}

resource_access {
id   = "9a5d68ac3a30" 
type = "Role"
}
}"

This doesn't seems to work. Any idea how to automate this?

I tried using locals.

locals {
  resource_access = [
    {
      id   = "df021288-bdde89214" # User.Read.All
      type = "Role"
    },
    {
      id   = "18a4783c-85e5662c884" # User.Read.All
      type = "Role"
    }
  ]
}

resource "azuread_application" "my_ad_app" {
  display_name = "my_app"

  #API Permissions
  required_resource_access {
    for_each   = {
    for index, az_app_id in local.resource_access:
    az_app_id.id => az_app_id
    }

    resource_app_id = "000000000000" # Microsoft Graph

    resource_access = How should I pass values here using locals??
     

Solution

  • An important thing to note to start here is that nested blocks like resource_access are not plain data that you can just pass in a variable: they are static configuration constructs declared by the provider in its schema. Therefore you can't "just" pass a whole set of blocks through a variable, but you can pass some data that would be used to populate those blocks, and ask the Terraform language to dynamically generate resource_access blocks for you based on that data. The rest of this answer is an example of how you can do that.

    First, the variable declaration:

    variable "ms_graph_api_permission" {
      type = list(object({
        id   = string
        type = string
      }))
    }
    

    This declares that var.ms_graph_api_permission is a list of objects where each object must have two attributes -- id and type -- that are both strings. This seems to match the shape of the data you want to provide through the resource_access blocks.

    A value of that type would look like what you tried with a local value:

    [
      {
        id   = "df021288-bdde89214" # User.Read.All
        type = "Role"
      },
      {
        id   = "18a4783c-85e5662c884" # User.Read.All
        type = "Role"
      },
    ]
    

    You can use an expression like the above both as the default value for the variable and in the module block that calls the module, when choosing a value for this variable.

    Inside the resource block itself you'll need to ask Terraform to generate one resource_access block for each element of that list, using a dynamic block:

    resource "azuread_application" "my_ad_app" {
      display_name = "my_app"
    
      #API Permissions
      required_resource_access {
        resource_app_id = "0000000-000000000000" # Microsoft Graph
    
        dynamic "resource_access" {
          for_each = var.ms_graph_api_permission
          content {
            id   = resource_access.value.id
            type = resource_access.value.type
          }
        }
      }
    }
    

    A dynamic "resource_access" block specifies a rule for generating zero or more resource_access blocks based on some other collection, which in this case is var.ms_graph_api_permission. The content block describes how to generate the body of each of the blocks, where resource_access is a local symbol referring to the current element of var.ms_graph_api_permission.

    Terraform Core will dynamically expand this to a series of individual resource_access blocks before passing the configuration to the provider, so from the provider's perspective this is equivalent to you having written out a series of static resource_access blocks as in your original example.