Accessing resources indirectly

So I have (thanks to Constructing resource names within Terraform), managed to remove a pre-processing of a static map to resources using for-each.

What I now have is a map that has, as one of the properties the name/reference to a resource.

(I’ve removed nearly all of the map to the only element that references a pre-existing resource).

organisation_accounts = {
  devops = {
    organization_unit = "aws_organizations_organizational_unit.devops"
  }
  old-production = {
    organization_unit = "aws_organizations_organizational_unit.production"
  },
  old-staging = {
    organization_unit = "aws_organizations_organizational_unit.staging"
  },
  production-compute = {
    organization_unit = "aws_organizations_organizational_unit.production"
  },
  staging-compute = {
    organization_unit = "aws_organizations_organizational_unit.staging"
  },
  production-store = {
    organization_unit = "aws_organizations_organizational_unit.production",
  }
  staging-store = {
    organization_unit = "aws_organizations_organizational_unit.staging",
  }
  production-testing = {
    organization_unit = "aws_organizations_organizational_unit.testing"
  }
  staging-testing = {
    organization_unit = "aws_organizations_organizational_unit.testing"
  }
}

What I want to do is use the value as a reference to the actual resource, but I can’t find the appropriate syntax for this.

resource "aws_organizations_account" "organization_account" {
  for_each  = var.organisation_accounts
  email     = each.value.email
  name      = "digitickets-${each.key}"
  parent_id = "${each.value.organization_unit}.id"
  role_name = "OrganizationAccountAccessRole"

  lifecycle {
    ignore_changes = [role_name]
  }

  tags = merge(local.base_tags, {
    "Name" = "${var.environment}-account-${each.key}"
  })
}

The above produces the error

Error: invalid value for parent_id (see https://docs.aws.amazon.com/organizations/latest/APIReference/API_MoveAccount.html#organizations-MoveAccount-request-DestinationParentId)

  on organization_accounts.tf line 1, in resource "aws_organizations_account" "organization_account":
   1: resource "aws_organizations_account" "organization_account" {

for each of the accounts.

I can’t work out how to “lookup” resources.

Any help on this would be appreciated.

The general form is resource[index].output.

resource "some_resource" "myresource" {
  for_each = { a = { value = 1 },
               b = { value = 2 } }
  input_and_output = each.value["value"]
}

output "out" { value = some_resource.myresource["a"].input_and_output }

Before answering this direct question I think it’s worth making a general observation: Terraform’s template language is for building strings, not for building expressions. There isn’t generally any way to dynamically construct an expression in Terraform: they must always be written out manually. For the sort of indirection you want to do here, the correct way to approach it is to use normal data structures that suit the situation, such as a map if you want to look values up by string keys.

With that general observation out of the way, let’s focus on the task at hand: you want to look up resources by string keys. The most common way that ends up happening is if the target resource has for_each set, in which case Terraform will automatically construct a suitable map.

However, in cases where that isn’t possible – if, for example, the resources in question are so different from one another that systematically constructing them from a map is impractical – we can write a map out manually, similar to the one you gave in your example but using the resource objects themselves instead of their names:

locals {
  organisation_accounts = {
    devops = {
      organization_unit = aws_organizations_organizational_unit.devops
    }
    old-production = {
      organization_unit = aws_organizations_organizational_unit.production
    },
    old-staging = {
      organization_unit = aws_organizations_organizational_unit.staging
    },
    production-compute = {
      organization_unit = aws_organizations_organizational_unit.production
    },
    staging-compute = {
      organization_unit = aws_organizations_organizational_unit.staging
    },
    production-store = {
      organization_unit = aws_organizations_organizational_unit.production
    }
    staging-store = {
      organization_unit = aws_organizations_organizational_unit.staging
    }
    production-testing = {
      organization_unit = aws_organizations_organizational_unit.testing
    }
    staging-testing = {
      organization_unit = aws_organizations_organizational_unit.testing
    }
  }
}

A key thing to notice in the above is that a reference to a resource, like aws_organizations_organizational_unit.testing, produces a normal value that you can pass around in the same way as values you might construct yourself in Terraform language syntax. A resource block with neither count nor for_each set produces a value of an object type, while setting count produces a list of objects, and setting for_each produces a map of objects.

The local value above embeds those resource objects inside the map, and so all of the expression-ish things you could’ve done with a direct reference to the resource you can now do also with the organization_unit attributes inside the map:

resource "aws_organizations_account" "organization_account" {
  for_each = var.organisation_accounts

  email     = each.value.email
  name      = "digitickets-${each.key}"
  parent_id = local.organization_accounts[each.key].organization_unit.id
  role_name = "OrganizationAccountAccessRole"

  tags = merge(local.base_tags, {
    "Name" = "${var.environment}-account-${each.key}"
  })

  lifecycle {
    ignore_changes = [role_name]
  }
}

The special things here, which don’t behave just like a “normal” value, are the top-level selectors like local.organization_accounts and aws_organizations_organizational_unit.testing. Those are indivisible, because Terraform uses them to understand what other objects each value depends on. You can’t treat local itself like an object value, as in local[var.something], because “local” doesn’t exist as a value in its own right, only as a prefix for selecting an individual local value to use, which also contributes a dependency relationship to Terraform’s internal graph.

1 Like