Search code examples
azureterraformterraform-provider-azureinfrastructure-as-code

Understanding shared modules in terraform and the destroy command, how do I exclude the shared module when destroying?


I'm currently scripting some new Azure infrastructure in Terraform, and doing so using nested modules in order to make the management and deployment of the different sections of our infrastructure nice and confined.

I have created a module called global that contains several shared resources (pretty much just azure resource groups and permissions) along with some azure ad lookups and currently client config. Having these in a central module seems to make sense to avoid code reuse and ensuring that they are handled consistently throughout the project.

However, I've noticed that when I run terraform destroy from any module, all of the objects in the global module are marked to be destroyed. Is there a way to exclude the nested module from the destruction process?

Should I actually be placing the creation of the resources (which are exclusively resource groups and permissions applied to those resource groups) in my global module in a seperate, non-nested module, but then use a data module for my nested global module to look up their values etc?

Example code for the global module:-

data "azurerm_client_config" "current" {}

data "azurerm_subscription" "subscription_current" {}

data "azuread_group" "dba" {
  name = "DBAs"
}

data "azuread_group" "bi-developer" {
  name = "BIDevelopers"
}

## local variables

locals {
  subscription_name = substr(lower("${data.azurerm_subscription.subscription_current.display_name}"),0,4)
  tenant_id = data.azurerm_subscription.subscription_current.tenant_id
}

## Create resource group and add permission

resource "azurerm_resource_group" "data" {
  name     = "data"
  location = var.location

  tags = {
    owner       = "Data"
    environment = local.subscription_name
  }
}

resource "azurerm_role_assignment" "DBA_Data_Permission" {
    scope = azurerm_resource_group.data.id
    role_definition_name = var.permission_level
    principal_id = data.azuread_group.dba.id
}

resource "azurerm_resource_group" "key-vault" {
  name     = "key-vault"
  location = var.location

  tags = {
    owner       = "techops"
    environment = local.subscription_name
  }
}

## Output Locals

output "subscription_name" {
    value = local.subscription_name
}

output "tenant_id" {
    value = local.tenant_id
}

output "object_id" {
    value = data.azurerm_client_config.current.object_id
}

## Output Resource Groups

output "rg_data_id" {
    value = azurerm_resource_group.data.id
}

output "rg_data_name" {
    value = azurerm_resource_group.data.name
}

output "rg_key-vault_id" {
    value = azurerm_resource_group.key-vault.id
}

output "rg_key-vault_name" {
    value = azurerm_resource_group.key-vault.name
}

## Output Azure AD lookups

output "aad_dba_id" {
    value = data.azuread_group.dba.id
}

output "aad_dba_name" {
    value = data.azuread_group.dba.name
}

output "aad_bi-developer_id" {
    value = data.azuread_group.bi-developer.id
}

output "aad_data-science_id" {
    value = data.azuread_group.data-science.id
}

And example of the usage in one of my nested modules, to create database resources in this instance:-

module "global" {
  source = "../global"
  permission_level = var.permission_level
  location = var.location
  prefix = var.prefix
}

resource "azurerm_sql_server" "dwh" {
  name                         = "${var.prefix}-dwh-${module.global.subscription_name}"
  resource_group_name          = module.global.rg_data_name
  location                     = var.location
  version                      = "12.0"
  administrator_login          = var.sql_login_name
  administrator_login_password = var.sql_login_password

  tags = {
    environment = module.global.subscription_name
    owner       = "Data"
    subscription = "${module.global.subscription_name}"
  }
}

resource "azurerm_sql_active_directory_administrator" "dwh" {
  server_name          = azurerm_sql_server.dwh.name
  resource_group_name = module.global.rg_data_name
  login               = module.global.aad_dba_name
  tenant_id           = module.global.tenant_id
  object_id           = module.global.aad_dba_id
}

resource "azurerm_mssql_elasticpool" "dwh-ep" {
  name                = "${var.prefix}-dwh-${module.global.subscription_name}"
  resource_group_name = module.global.rg_data_name
  location            = var.location
  server_name         = azurerm_sql_server.dwh.name
  max_size_gb         = 1000

  sku {
      name  = "GP_Gen5"
      tier  = "GeneralPurpose"
      family = "Gen5"
      capacity = 6
  }

  per_database_settings {
    min_capacity = 0.25
    max_capacity = 4
  }

  depends_on = [azurerm_sql_server.dwh,azurerm_sql_active_directory_administrator.dwh]
} 

Then in a seperate module the following code to create a key vault, reusing some of the output from the global module:-

module "global" {
  source = "../global"
  permission_level = var.permission_level
  location = var.location
  prefix = var.prefix
}

resource "azurerm_key_vault" "data" {
    name = "${var.prefix}-data-${module.global.subscription_name}"
    location = var.location
    resource_group_name = "${module.global.rg_key-vault_name}"
    enabled_for_disk_encryption = true
    tenant_id = module.global.tenant_id

    sku_name = "standard"

    tags = {
        environment = module.global.subscription_name
        owner = "data"
    }
}

resource "azurerm_key_vault_access_policy" "data-bi-developer" {
    key_vault_id = "${azurerm_key_vault.data.id}"
    tenant_id = module.global.tenant_id
    object_id = module.global.aad_bi-developer_id

    certificate_permissions = ["get","list"]
    key_permissions = ["get", "list"]
    secret_permissions = ["get", "list"]   
}

resource "azurerm_key_vault_access_policy" "data-admin" {
    key_vault_id = "${azurerm_key_vault.data.id}"
    tenant_id = module.global.tenant_id
    object_id = "${module.global.object_id}"

certificate_permissions = [
      "create",
      "delete",
      "deleteissuers",
      "get",
      "getissuers",
      "import",
      "list",
      "listissuers",
      "managecontacts",
      "manageissuers",
      "setissuers",
      "update",
    ]

    key_permissions = [
      "backup",
      "create",
      "decrypt",
      "delete",
      "encrypt",
      "get",
      "import",
      "list",
      "purge",
      "recover",
      "restore",
      "sign",
      "unwrapKey",
      "update",
      "verify",
      "wrapKey",
    ]

    secret_permissions = [
      "backup",
      "delete",
      "get",
      "list",
      "purge",
      "recover",
      "restore",
      "set",
    ]
}

Solution

  • OK, managed to work this out, essentially splitting the global module up into three modules; one to hold the resources that are not being created at all through terraform as lookups (e.g. subscription, azure ad etc), one to create the resource groups and apply permissions, and then one that is purely a data module to read the resource groups for other modules.