Skip to content

Commit

Permalink
Simplify function app terraform
Browse files Browse the repository at this point in the history
We only have one region across all environments so remove the loop over regions.
We can address adding another region when the time comes.
We only have once function app now, so remove the additional configuration needed to loop over multiple function configs.
  • Loading branch information
steventux committed Jan 9, 2025
1 parent ce8fda8 commit bbefad8
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 125 deletions.
163 changes: 55 additions & 108 deletions infrastructure/tf-core/function_app.tf
Original file line number Diff line number Diff line change
@@ -1,55 +1,53 @@
module "functionapp" {
for_each = local.function_app_map

source = "../../../dtos-devops-templates/infrastructure/modules/function-app"

function_app_name = "${module.regions_config[each.value.region].names.function-app}-${lower(each.value.name_suffix)}"
resource_group_name = azurerm_resource_group.core[each.value.region].name
location = each.value.region
function_app_name = "${module.regions_config[local.primary_region].names.function-app}-${local.name_suffix}"
resource_group_name = azurerm_resource_group.core[local.primary_region].name
location = local.primary_region

app_settings = each.value.app_settings
app_settings = local.app_settings

log_analytics_workspace_id = data.terraform_remote_state.audit.outputs.log_analytics_workspace_id[local.primary_region]
monitor_diagnostic_setting_function_app_enabled_logs = local.monitor_diagnostic_setting_function_app_enabled_logs
monitor_diagnostic_setting_function_app_metrics = local.monitor_diagnostic_setting_function_app_metrics

public_network_access_enabled = var.features.public_network_access_enabled
vnet_integration_subnet_id = module.subnets["${module.regions_config[each.value.region].names.subnet}-apps"].id
vnet_integration_subnet_id = module.subnets["${module.regions_config[local.primary_region].names.subnet}-apps"].id

rbac_role_assignments = each.value.rbac_role_assignments
rbac_role_assignments = local.rbac_role_assignments

asp_id = module.app-service-plan["${each.value.app_service_plan_key}-${each.value.region}"].app_service_plan_id
asp_id = module.app-service-plan["${var.app_service_plan_key}-${local.primary_region}"].app_service_plan_id

# Use the storage account assigned identity for the Function Apps:
storage_account_name = module.storage["fnapp-${each.value.region}"].storage_account_name
storage_account_access_key = var.function_apps.storage_uses_managed_identity == true ? null : module.storage["fnapp-${each.value.region}"].storage_account_primary_access_key
storage_uses_managed_identity = var.function_apps.storage_uses_managed_identity
storage_account_name = module.storage["fnapp-${local.primary_region}"].storage_account_name
storage_account_access_key = var.function_app.storage_uses_managed_identity == true ? null : module.storage["fnapp-${local.primary_region}"].storage_account_primary_access_key
storage_uses_managed_identity = var.function_app.storage_uses_managed_identity

# Connection string for Application Insights:
ai_connstring = data.azurerm_application_insights.ai.connection_string

# Use the ACR assigned identity for the Function Apps:
cont_registry_use_mi = var.function_apps.cont_registry_use_mi
cont_registry_use_mi = var.function_app.cont_registry_use_mi

# Other Function App configuration settings:
always_on = var.function_apps.always_on
worker_32bit = var.function_apps.worker_32bit
always_on = var.function_app.always_on
worker_32bit = var.function_app.worker_32bit

acr_mi_client_id = data.azurerm_user_assigned_identity.acr_mi.client_id
acr_login_server = data.azurerm_container_registry.acr.login_server

# Use the ACR assigned identity for the Function Apps too:
assigned_identity_ids = var.function_apps.cont_registry_use_mi ? [data.azurerm_user_assigned_identity.acr_mi.id] : []
assigned_identity_ids = var.function_app.cont_registry_use_mi ? [data.azurerm_user_assigned_identity.acr_mi.id] : []

image_tag = var.function_apps.docker_env_tag
image_name = "${var.function_apps.docker_img_prefix}-${lower(each.value.name_suffix)}"
image_tag = var.function_app.docker_env_tag
image_name = "${var.function_app.docker_img_prefix}-${lower(local.name_suffix)}"

# Private Endpoint Configuration if enabled
private_endpoint_properties = var.features.private_endpoints_enabled ? {
private_dns_zone_ids = [data.terraform_remote_state.hub.outputs.private_dns_zones["${each.value.region}-app_services"].id]
private_dns_zone_ids = [data.terraform_remote_state.hub.outputs.private_dns_zones["${local.primary_region}-app_services"].id]
private_endpoint_enabled = var.features.private_endpoints_enabled
private_endpoint_subnet_id = module.subnets["${module.regions_config[each.value.region].names.subnet}-pep"].id
private_endpoint_resource_group_name = azurerm_resource_group.rg_private_endpoints[each.value.region].name
private_endpoint_subnet_id = module.subnets["${module.regions_config[local.primary_region].names.subnet}-pep"].id
private_endpoint_resource_group_name = azurerm_resource_group.rg_private_endpoints[local.primary_region].name
private_service_connection_is_manual = var.features.private_service_connection_is_manual
} : null

Expand All @@ -60,95 +58,44 @@ module "functionapp" {


locals {
name_suffix = "communication_management"
primary_region = [for k, v in var.regions : k if v.is_primary_region][0]

app_settings_common = {
DOCKER_ENABLE_CI = var.function_apps.docker_CI_enable
REMOTE_DEBUGGING_ENABLED = var.function_apps.remote_debugging_enabled
WEBSITES_ENABLE_APP_SERVICE_STORAGE = var.function_apps.enable_appsrv_storage
WEBSITE_PULL_IMAGE_OVER_VNET = var.features.private_endpoints_enabled
FUNCTIONS_WORKER_RUNTIME = "python"
}

# There are multiple Function Apps and possibly multiple regions.
# We cannot nest for loops inside a map, so first iterate all permutations of both as a list of objects...
function_app_config_object_list = flatten([
for region in keys(var.regions) : [
for function, config in var.function_apps.fa_config : merge(
{
region = region # 1st iterator
function = function # 2nd iterator
},
config, # the rest of the key/value pairs for a specific function
{
app_settings = merge(
local.app_settings_common,
config.env_vars_static,

# Dynamic env vars which cannot be stored in tfvars file
function == "message-status" ? {
APPLICATION_ID = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.application_id[region].versionless_id})"
NOTIFY_API_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.notify_api_key[region].versionless_id})"
} : {},
function == "notify" ? {
OAUTH2_API_KID = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.oauth2_api_kid[region].versionless_id})"
OAUTH2_API_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.oauth2_api_key[region].versionless_id})"
PRIVATE_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_key.private_key[region].versionless_id})"
} : {},
function == "process-pilot-data" ? {
AzureWebJobsStorage__accountName = module.storage["fnapp-${region}"].storage_account_name
NOTIFY_FUNCTION_URL = format(
"https://%s-%s.azurewebsites.net/api/%s/message/send",
module.regions_config[region].names["function-app"],
var.function_apps.fa_config["notify"].name_suffix,
lower(var.function_apps.fa_config["notify"].function_endpoint_name)
)
} : {},

# Dynamic reference to Key Vault
length(config.key_vault_url) > 0 ? {
(config.key_vault_url) = module.key_vault[region].key_vault_url
} : {},

# Database
config.database_required ? {
DATABASE_NAME = "communication_management"
DATABASE_HOST = "${module.regions_config[region].names.postgres-sql-server}.postgres.database.azure.com"
DATABASE_USER = "commgt_db_user"
DATABASE_PASSWORD = "@Microsoft.KeyVault(SecretUri=${module.postgresql_flexible_db[region].db_admin_pwd_keyvault_secret})"
# DATABASE_USER = var.postgresql.postgres_sql_admin_group
} : {}

)

# These RBAC assignments are for the Function Apps only
rbac_role_assignments = flatten([

# Key Vault
var.key_vault != {} ? [
for role in local.rbac_roles_key_vault_user : {
role_definition_name = role
scope = module.key_vault[region].key_vault_id
}
] : [],

# Storage Accounts
[
for account in keys(var.storage_accounts) : [
for role in local.rbac_roles_storage : {
role_definition_name = role
scope = module.storage["${account}-${region}"].storage_account_id
}
]
]
])
app_settings = merge(
config.env_vars_static,
{
DOCKER_ENABLE_CI = var.function_app.docker_CI_enable
FUNCTIONS_WORKER_RUNTIME = "python"
REMOTE_DEBUGGING_ENABLED = var.function_app.remote_debugging_enabled
WEBSITES_ENABLE_APP_SERVICE_STORAGE = var.function_app.enable_appsrv_storage
WEBSITE_PULL_IMAGE_OVER_VNET = var.features.private_endpoints_enabled

DATABASE_NAME = "communication_management"
DATABASE_HOST = "${module.regions_config[primary_region].names.postgres-sql-server}.postgres.database.azure.com"
DATABASE_USER = "commgt_db_user"
DATABASE_PASSWORD = "@Microsoft.KeyVault(SecretUri=${module.postgresql_flexible_db[primary_region].db_admin_pwd_keyvault_secret})"

APPLICATION_ID = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.application_id[primary_region].versionless_id})"
NOTIFY_API_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.notify_api_key[primary_region].versionless_id})"
OAUTH2_API_KID = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.oauth2_api_kid[primary_region].versionless_id})"
OAUTH2_API_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.oauth2_api_key[primary_region].versionless_id})"
PRIVATE_KEY = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_key.private_key[primary_region].versionless_id})"
}
)

rbac_role_assignments = flatten([
var.key_vault != {} ? [
for role in local.rbac_roles_key_vault_user : {
role_definition_name = role
scope = module.key_vault[primary_region].key_vault_id
}
] : [],
[
for account in keys(var.storage_accounts) : [
for role in local.rbac_roles_storage : {
role_definition_name = role
scope = module.storage["${account}-${primary_region}"].storage_account_id
}
)
]
]
])

# ...then project the list of objects into a map with unique keys (combining the iterators), for consumption by a for_each meta argument
function_app_map = {
for object in local.function_app_config_object_list : "${object.function}-${object.region}" => object
}
}
40 changes: 23 additions & 17 deletions infrastructure/tf-core/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ variable "app_service_plan" {
})
}

variable "function_apps" {
variable "function_app" {
description = "Configuration for function apps"
type = object({
acr_mi_name = string
Expand All @@ -185,22 +185,23 @@ variable "function_apps" {
name = string
slot_enabled = optional(bool, false)
})))
fa_config = map(object({
name_suffix = string
function_endpoint_name = string
app_service_plan_key = string
storage_containers = optional(list(object({
env_var_name = string
container_name = string
})), [])
database_required = optional(bool, false)
key_vault_url = optional(string, "")
app_urls = optional(list(object({
env_var_name = string
function_app_key = string
})), [])
env_vars_static = optional(map(string), {})
}))

name_suffix = string
function_endpoint_name = string
app_service_plan_key = string

storage_containers = optional(list(object({
env_var_name = string
container_name = string
})), [])

key_vault_url = optional(string, "")

app_urls = optional(list(object({
env_var_name = string
function_app_key = string
})), [])
env_vars_static = optional(map(string), {})
})
}

Expand Down Expand Up @@ -363,3 +364,8 @@ variable "function_app_slots" {
function_app_slot_enabled = optional(bool, false)
}))
}

variable "app_service_plan_key" {
description = "The key for the app service plan"
type = string
}

0 comments on commit bbefad8

Please sign in to comment.