Integrating Remote and Local State - ivaramme/terraform-azurerm-caf GitHub Wiki

What?

Part of the advantages behind the design of CAF is the to segregate and reuse components different landing zones or levels. These objects are persisted in tfstate files inside the launchpad.

The combined_object pattern provides a way to access objects of a certain resource type regardless of where they were created by merging local objects (to the current landing zone) with objects referenced in other landing zones (external .tfstate files).

How does it work?

When the rover command is invoked passing the command launchpad or landingzone, an entry point is expected as an argument using the -lz flag, pointing to the location of the entry point folder.

One of such entry points available is the caf_solution. This entry point enables the deployment of any configurations on Azure using the respective Azure CAF module and the retrieval of remote objects (persisted as tfstate files).

This entry point module works as follows:

Sequence Diagram

To reference objects that have been created elsewhere they have to be defined as part of the variable files. These files are loaded by the rover into the caf_solution entry point among with the variables.

The caf_solution then get the remote tfstate files and combine them based on their resource-type. The result of this process is exposed in the landing zone and injected into the Azure Cloud Adoption Framework Terraform module

Inside the Azure CAF Terraform module, the file locals.combined_objects.tf is responsible of merging the local and remote states based on the resources types. It will then proceed with the creation of resources.

Example

This example shows how to use the combined object in a context of an example module. After that there's an explanation of how this data is accessible.

  1. Application
  2. Background

Application

To use the combined_object pattern in this example module, first, lets understand the attributes provided inside the resource_groups object. These attributes are:

  1. name
  2. location
  3. tags
  4. rbac_id
  5. id

Referencing attributes from the resource_groups object

For example, a module may require any combination of the id, tags, location, and name attributes. We opt to de-structure the combined objects at the resource level by passing the combined object local.combined_objects_resource_groups at the root level as the value for the resource_groups variable:

module "example" {
  source   = "./modules/some_module/example"
  for_each = local.some_module.example

  client_config   = local.client_config
  global_settings = local.global_settings
  name            = each.value.name
  resource_groups = local.combined_objects_resource_groups
  settings        = each.value
}

Then in the modules directory, for this specific resource, create a variables.tf that expects this resource_groups object

variable "resource_groups" {
}

Then de-structure this variable in the resource's .tf file. In the example below, the resource_group_name and location are extracted from var.resource_groups

resource_group_name = coalesce(
  try(var.resource_groups[var.settings.resource_group.lz_key][var.settings.resource_group.key].name, null),
  try(var.resource_groups[var.client_config.landingzone_key][var.settings.resource_group.key].name, null),
  try(var.resource_groups[var.settings.resource_group.lz_key][var.settings.resource_group_key].name, null),
  try(var.resource_groups[var.client_config.landingzone_key][var.settings.resource_group_key].name, null),
)
location = coalesce(
  try(var.resource_groups[var.settings.resource_group.lz_key][var.settings.resource_group.key].location, null),
  try(var.resource_groups[var.client_config.landingzone_key][var.settings.resource_group.key].location, null),
  try(var.resource_groups[var.settings.resource_group.lz_key][var.settings.resource_group_key].location, null),
  try(var.resource_groups[var.client_config.landingzone_key][var.settings.resource_group_key].location, null),
)

Background

To use the combined_object pattern in this example, we'll inspect three files and discover how the local state is combined with the remote state. These files are located in the root directory of the repository.

Combined objects located in locals.combined_objects.tf

Inside locals.combined_objects.tf you can find all the combined objects for each resource type that are available.

For the context of this example, the relevant snippet defines combined_objects_resource_groups and merges the local state that comes from local.resource_groups with the remote state that comes from var.remote_objects.resource_groups.

locals {
  combined_objects_resource_groups = merge(
    tomap({
      (local.client_config.landingzone_key) = local.resource_groups
    }),
    try(var.remote_objects.resource_groups, {})
  )
}

Remote Objects located in variables.tf

The remote objects is defined and initialized here. This is how locals.combined_objects.tf can reference the remote resource groups by using var.remote_objects.resource_groups. This variable has its values injected behind the scenes by the caf_launchpad or landing zone.

variable "remote_objects" {
  description = "Remote objects is used to allow the landing zone to retrieve remote tfstate objects and pass them to the caf module"
  default     = {}
}

Local Objects located in resource_groups.tf

This module initializes the resource groups and stores the outputs in the locals namespace. This is how locals.combined_objects.tf can reference the local resource groups by using local.resource_groups.

locals {
  resource_groups = merge(module.resource_groups, module.resource_group_reused)
}