2
votes

I have a configuration that creates 6 resource groups in azure and then loops over them to assign the contributor access to these resource groups for a certain service principal. The code is this:

auto generated before terraform is run:

locals {
  pod_rg_key_list = ["non_regional_web_rg","regional_data_rg['centralus']","regional_data_rg['eastus2']","regional_web_rg['centralus']","regional_web_rg['eastus2']"]
  pod_rg_list = [azurerm_resource_group.non_regional_web_rg,azurerm_resource_group.regional_data_rg["centralus"],azurerm_resource_group.regional_data_rg["eastus2"],azurerm_resource_group.regional_web_rg["centralus"],azurerm_resource_group.regional_web_rg["eastus2"]]
}

main.tf:

...
locals {
  injected_rg_key_list = ["cfg"]
  injected_rg_list     = [azurerm_resource_group.cfg]
  all_rg_key_list      = concat(local.pod_rg_key_list, local.injected_rg_key_list)
  all_rg_list          = concat(local.pod_rg_list, local.injected_rg_list)
}

resource "azurerm_role_assignment" "pod_sp_contributor_to_pod_rgs" {
  for_each = toset(local.all_rg_key_list)

  scope              = local.all_rg_list[index(local.all_rg_key_list, each.value)].id
  role_definition_id = data.azurerm_role_definition.contributor.id
  principal_id       = azuread_service_principal.sp.id
}
...

This is the only way I found to make terraform create the role assignments without complaining about for_each being unable to resolve due to dynamic bla-bla-bla and suggesting to run terraform apply with -target.

So, it creates the role assignments just fine. However, when I run destroy, terraform first destroys the resource groups and then proceeds to destroy the role assignments, which is wrong and of course, it fails.

More Context

In general my code is part of an automated build that takes in a basic configuration, identifies all the resource groups, appends another terraform file and then runs the terraform on the whole shebang.

So, the way it identifies the resource groups is by running terraform plan -out main.tfplan with variables that guarantee a from scratch deployment. Then the plan is converted to json and the resource group addresses are extracted. Then they can be injected into the additional terraform code that gets added by the build before running terraform plan again and finally terraform apply.

So I cannot hard code the depends_on statement, because the list of actual resource groups is deduced by the build at run-time. I would have to also auto generate the depends_on statement, which I am trying to avoid. My reasons - currently all the auto generated stuff is just local variables all found in a separate file. If I am forced to auto generate the depends_on statement, I will have to break this nice separation.

I feel there must be a better way to achieve what I am doing.

EDIT

So, I changed the code - I moved the role assignment resource to the file where I do substitutions. This allowed me to auto generate the depends_on statement. Here is the new file:

locals {
  pod_rg_key_list = ["non_regional_web_rg", "regional_data_rg['centralus']", "regional_data_rg['eastus2']", "regional_web_rg['centralus']", "regional_web_rg['eastus2']", "cfg"]
  pod_rg_list = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
}

### Grant Contributor access to the resource groups

data "azurerm_role_definition" "contributor" {
  name  = "Contributor"
  scope = "/subscriptions/${data.azurerm_subscriptions.sub.subscriptions.0.subscription_id}"
}

resource "azurerm_role_assignment" "pod_sp_contributor_to_pod_rgs" {
  for_each = toset(local.pod_rg_key_list)

  depends_on         = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
  scope              = local.pod_rg_list[index(local.pod_rg_key_list, each.value)].id
  role_definition_id = data.azurerm_role_definition.contributor.id
  principal_id       = azuread_service_principal.sp.id
}

But it still does not work:

Error: Invalid index

  on ..\..\modules\bootstrap\powershell.tf line 5, in locals:
   5:   pod_rg_list = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
    |----------------
    | azurerm_resource_group.regional_data_rg is object with no attributes

The given key does not identify an element in this collection value.


Error: Invalid index

  on ..\..\modules\bootstrap\powershell.tf line 5, in locals:
   5:   pod_rg_list = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
    |----------------
    | azurerm_resource_group.regional_data_rg is object with no attributes

The given key does not identify an element in this collection value.


Error: Invalid index

  on ..\..\modules\bootstrap\powershell.tf line 5, in locals:
   5:   pod_rg_list = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
    |----------------
    | azurerm_resource_group.regional_web_rg is object with no attributes

The given key does not identify an element in this collection value.


Error: Invalid index

  on ..\..\modules\bootstrap\powershell.tf line 5, in locals:
   5:   pod_rg_list = [azurerm_resource_group.non_regional_web_rg, azurerm_resource_group.regional_data_rg["centralus"], azurerm_resource_group.regional_data_rg["eastus2"], azurerm_resource_group.regional_web_rg["centralus"], azurerm_resource_group.regional_web_rg["eastus2"], azurerm_resource_group.cfg]
    |----------------
    | azurerm_resource_group.regional_web_rg is object with no attributes

The given key does not identify an element in this collection value.

Note, that it only breaks when destroying the environment.

EDIT 2

The actual role assignments are destroyed with the respective resource groups, but I do not get it why terraform attempts to destroy them AFTER the resource groups despite the clear message from the depends_on.

1
Why can you not dynamically generate the depends_on since it is within an iterator scope? - Matt Schuchard
I actually did that, still there is a problem - see my EDIT - mark

1 Answers

1
votes

I was on the verge of opening a bug to Terraform when I noticed that I was running version 0.12.18 instead of the latest 0.12.20. Upgrading to 0.12.20 solved the issue!

I checked the change log for 0.12.20 and have not noticed anything of relevance since 0.12.18 was out. But it is a fact - working with 0.12.20, not working with 0.12.18.