From 116426f3c5d03131ad71f31bbe000bf5d9a6374c Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Wed, 15 Jan 2025 12:49:11 +0100 Subject: [PATCH 01/10] Add AI service module reference --- modules/dataapplication/aiservice.tf | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 modules/dataapplication/aiservice.tf diff --git a/modules/dataapplication/aiservice.tf b/modules/dataapplication/aiservice.tf new file mode 100644 index 0000000..7e7ff63 --- /dev/null +++ b/modules/dataapplication/aiservice.tf @@ -0,0 +1,28 @@ +module "ai_service" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/aiservice?ref=main" + providers = { + azurerm = azurerm + azapi = azapi + time = time + } + + for_each = var.ai_services + + location = var.location + resource_group_name = azurerm_resource_group.resource_group_app.name + tags = var.tags + + cognitive_account_name = "${local.prefix}-${each.key}-kv001" + cognitive_account_kind = each.value.kind + cognitive_account_sku = each.value.sku + cognitive_account_firewall_bypass_azure_services = contains(local.ai_service_kind_firewall_bypass_azure_services_list, each.value.kind) ? true : false + cognitive_account_outbound_network_access_restricted = true + cognitive_account_outbound_network_access_allowed_fqdns = [] + cognitive_account_local_auth_enabled = false + cognitive_account_deployments = {} + diagnostics_configurations = var.diagnostics_configurations + subnet_id = var.subnet_id_app + connectivity_delay_in_seconds = var.connectivity_delay_in_seconds + private_dns_zone_id_cognitive_account = var.private_dns_zone_id_cognitive_account + customer_managed_key = var.customer_managed_key +} From 2b48db6ed340aae77adca32d529a1d14b7772732 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Wed, 15 Jan 2025 12:53:31 +0100 Subject: [PATCH 02/10] Add variables and terraform config --- main.tf | 1 + modules/dataapplication/locals.tf | 39 ++++++++++++++++++++++++++++ modules/dataapplication/terraform.tf | 4 +++ modules/dataapplication/variables.tf | 30 +++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/main.tf b/main.tf index 05e1029..d012ebb 100644 --- a/main.tf +++ b/main.tf @@ -94,6 +94,7 @@ module "data_application" { app_name = each.key storage_account_ids = module.core.storage_account_ids databricks_workspace_details = module.core.databricks_workspace_details + ai_services = try(each.value.ai_services, {}) # HA/DR variables zone_redundancy_enabled = var.zone_redundancy_enabled diff --git a/modules/dataapplication/locals.tf b/modules/dataapplication/locals.tf index 0398158..bf71cac 100644 --- a/modules/dataapplication/locals.tf +++ b/modules/dataapplication/locals.tf @@ -5,4 +5,43 @@ locals { # Databricks locals databricks_enterprise_application_id = "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d" + + # AI service locals + ai_service_kind_firewall_bypass_azure_services_list = [ + "OpenAI" + ] + ai_service_kind_role_map_write = { + "AnomalyDetector" = "Cognitive Services User" + "ComputerVision" = "Cognitive Services User" + "CognitiveServices" = "Cognitive Services User" + "ContentModerator" = "Cognitive Services User" + "CustomVision.Training" = "Cognitive Services Custom Vision Contributor" + "CustomVision.Prediction" = "Cognitive Services Custom Vision Contributor" + "Face" = "Cognitive Services User" + "FormRecognizer" = "Cognitive Services User" + "ImmersiveReader" = "Cognitive Services User" + "LUIS" = "Cognitive Services Language Owner" + "Personalizer" = "Cognitive Services User" + "SpeechServices" = "Cognitive Services Speech Contributor" + "TextAnalytics" = "Cognitive Services Language Owner" + "TextTranslation" = "Cognitive Services Language Owner" + "OpenAI" = "Cognitive Services OpenAI Contributor" + } + ai_service_kind_role_map_use = { + "AnomalyDetector" = "Cognitive Services User" + "ComputerVision" = "Cognitive Services User" + "CognitiveServices" = "Cognitive Services User" + "ContentModerator" = "Cognitive Services User" + "CustomVision.Training" = "Cognitive Services Custom Vision Reader" + "CustomVision.Prediction" = "Cognitive Services Custom Vision Reader" + "Face" = "Cognitive Services User" + "FormRecognizer" = "Cognitive Services User" + "ImmersiveReader" = "Cognitive Services User" + "LUIS" = "Cognitive Services Language Reader" + "Personalizer" = "Cognitive Services User" + "SpeechServices" = "Cognitive Services Speech User" + "TextAnalytics" = "Cognitive Services Language Reader" + "TextTranslation" = "Cognitive Services Language Reader" + "OpenAI" = "Cognitive Services OpenAI User" + } } diff --git a/modules/dataapplication/terraform.tf b/modules/dataapplication/terraform.tf index fb0bb9d..250b441 100644 --- a/modules/dataapplication/terraform.tf +++ b/modules/dataapplication/terraform.tf @@ -4,6 +4,10 @@ terraform { source = "hashicorp/azurerm" version = "~> 4.0" } + azapi = { + source = "Azure/azapi" + version = "~> 2.0" + } azuread = { source = "hashicorp/azuread" version = "~> 3.0" diff --git a/modules/dataapplication/variables.tf b/modules/dataapplication/variables.tf index c700c26..d7ca226 100644 --- a/modules/dataapplication/variables.tf +++ b/modules/dataapplication/variables.tf @@ -89,6 +89,25 @@ variable "databricks_workspace_details" { default = {} } +variable "ai_services" { + description = "Specifies the map of ai services to be created for this application." + type = map(object({ + location = optional(string, null) + kind = string + sku = string + })) + sensitive = false + nullable = false + default = {} + validation { + condition = alltrue([ + length([for kind in values(var.ai_services)[*].kind : kind if !contains(["AnomalyDetector", "ComputerVision", "CognitiveServices", "ContentModerator", "CustomVision.Training", "CustomVision.Prediction", "Face", "FormRecognizer", "ImmersiveReader", "LUIS", "Personalizer", "SpeechServices", "TextAnalytics", "TextTranslation", "OpenAI"], kind)]) <= 0, + length([for sku in values(var.ai_services)[*].sku : sku if !startswith(sku, "S") && !startswith(sku, "P") && !startswith(sku, "E") && !startswith(sku, "DC")]) <= 0 + ]) + error_message = "Please specify a valid ai service configuration." + } +} + # HA/DR variables variable "zone_redundancy_enabled" { description = "Specifies whether zone-redundancy should be enabled for all resources." @@ -263,6 +282,17 @@ variable "private_dns_zone_id_vault" { } } +variable "private_dns_zone_id_cognitive_account" { + description = "Specifies the resource ID of the private DNS zone for Azure Cognitive Services. Not required if DNS A-records get created via Azure Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_cognitive_account == "" || (length(split("/", var.private_dns_zone_id_cognitive_account)) == 9 && (endswith(var.private_dns_zone_id_cognitive_account, "privatelink.cognitiveservices.azure.com") || endswith(var.private_dns_zone_id_cognitive_account, "privatelink.openai.azure.com"))) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} + # Customer-managed key variables variable "customer_managed_key" { description = "Specifies the customer managed key configurations." From d5ff2d9c1ee12969ab4c4ec27cc00084d7ab5bb1 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Wed, 15 Jan 2025 12:54:02 +0100 Subject: [PATCH 03/10] Update app schema and test app definition --- schemas/app.schema.json | 59 ++++++++++++++++++++++++++ tests/e2e/data-applications/app001.yml | 6 +++ 2 files changed, 65 insertions(+) diff --git a/schemas/app.schema.json b/schemas/app.schema.json index f25503f..500fd0c 100644 --- a/schemas/app.schema.json +++ b/schemas/app.schema.json @@ -198,6 +198,65 @@ }, "required": [], "additionalProperties": false + }, + "ai_services": { + "description": "Specifies the ai services to be deployed for the application.", + "type": "object", + "patternProperties": { + "^.*$": { + "properties": { + "location": { + "description": "Specifies the location of the ai service in case it needs to be deployed in another region for capacity reasons.", + "type": "string" + }, + "kind": { + "description": "Specifies the kind of the ai service.", + "type": "string", + "enum": [ + "AnomalyDetector", + "ComputerVision", + "CognitiveServices", + "ContentModerator", + "CustomVision.Training", + "CustomVision.Prediction", + "Face", + "FormRecognizer", + "ImmersiveReader", + "LUIS", + "Personalizer", + "SpeechServices", + "TextAnalytics", + "TextTranslation", + "OpenAI" + ] + }, + "sku": { + "description": "Specifies the sku of the ai service.", + "type": "string", + "enum": [ + "S", + "S0", + "S1", + "S2", + "S3", + "S4", + "S5", + "S6", + "P0", + "P1", + "P2", + "E0", + "DC0" + ] + } + }, + "required": [ + "kind", + "sku" + ], + "additionalProperties": false + } + } } }, "required": [ diff --git a/tests/e2e/data-applications/app001.yml b/tests/e2e/data-applications/app001.yml index 0d66d09..e0beb7b 100644 --- a/tests/e2e/data-applications/app001.yml +++ b/tests/e2e/data-applications/app001.yml @@ -29,3 +29,9 @@ budget: endpoints: email: email_address: test@microsoft.com + +ai_services: + oai: + location: westeurope + kind: OpenAI + sku: S0 From e9ae6c47bf61e26df8d1086b87bdf1095eddad7f Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Wed, 15 Jan 2025 13:08:43 +0100 Subject: [PATCH 04/10] Add role assignments for persona model --- .../dataapplication/roleassignments_admin.tf | 19 +++++++++++++++++++ .../roleassignments_developer.tf | 11 +++++++++++ .../dataapplication/roleassignments_reader.tf | 2 ++ .../roleassignments_service_principal.tf | 19 +++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/modules/dataapplication/roleassignments_admin.tf b/modules/dataapplication/roleassignments_admin.tf index d6ed1cd..6d47155 100644 --- a/modules/dataapplication/roleassignments_admin.tf +++ b/modules/dataapplication/roleassignments_admin.tf @@ -41,6 +41,25 @@ resource "azurerm_role_assignment" "role_assignment_databricks_workspace_reader_ principal_type = "Group" } +# AI service role assignments +resource "azurerm_role_assignment" "role_assignment_ai_service_admin" { + for_each = var.ai_services + + description = "Role assignment to the ai services." + scope = module.ai_services[each.key].cognitive_account_id + role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] + principal_id = data.azuread_group.group_admin.object_id + principal_type = "Group" +} + +resource "azurerm_role_assignment" "role_assignment_cognitive_services_usages_reader_admin" { + description = "Cognitive Services Usages Reader to check quota for Azure Open AI models." + scope = data.azurerm_subscription.current.id + role_definition_name = "Cognitive Services Usages Reader" + principal_id = data.azuread_group.group_admin.object_id + principal_type = "Group" +} + # Storage role assignments resource "azurerm_role_assignment" "role_assignment_storage_container_external_blob_data_owner_admin" { description = "Role assignment to the external storage container." diff --git a/modules/dataapplication/roleassignments_developer.tf b/modules/dataapplication/roleassignments_developer.tf index ca75be2..fcfe471 100644 --- a/modules/dataapplication/roleassignments_developer.tf +++ b/modules/dataapplication/roleassignments_developer.tf @@ -51,6 +51,17 @@ resource "azurerm_role_assignment" "role_assignment_databricks_workspace_reader_ principal_type = "Group" } +# AI service role assignments +resource "azurerm_role_assignment" "role_assignment_ai_service_developer" { + for_each = var.ai_services + + description = "Role assignment to the ai services." + scope = module.ai_services[each.key].cognitive_account_id + role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] + principal_id = one(data.azuread_group.group_developer[*].object_id) + principal_type = "Group" +} + # Storage role assignments resource "azurerm_role_assignment" "role_assignment_storage_container_external_blob_data_conributor_developer" { count = var.developer_group_name == "" ? 0 : 1 diff --git a/modules/dataapplication/roleassignments_reader.tf b/modules/dataapplication/roleassignments_reader.tf index 77a2dd7..08b0d9d 100644 --- a/modules/dataapplication/roleassignments_reader.tf +++ b/modules/dataapplication/roleassignments_reader.tf @@ -42,6 +42,8 @@ resource "azurerm_role_assignment" "role_assignment_databricks_workspace_reader_ principal_type = "Group" } +# AI service role assignments + # Storage role assignments resource "azurerm_role_assignment" "role_assignment_storage_container_external_blob_data_reader_reader" { count = var.reader_group_name == "" ? 0 : 1 diff --git a/modules/dataapplication/roleassignments_service_principal.tf b/modules/dataapplication/roleassignments_service_principal.tf index 9abaf31..a01e622 100644 --- a/modules/dataapplication/roleassignments_service_principal.tf +++ b/modules/dataapplication/roleassignments_service_principal.tf @@ -41,6 +41,25 @@ resource "azurerm_role_assignment" "role_assignment_databricks_workspace_reader_ principal_type = "ServicePrincipal" } +# AI service role assignments +resource "azurerm_role_assignment" "role_assignment_ai_service_service_principal" { + for_each = var.ai_services + + description = "Role assignment to the ai services." + scope = module.ai_services[each.key].cognitive_account_id + role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] + principal_id = data.azuread_service_principal.service_principal.object_id + principal_type = "ServicePrincipal" +} + +resource "azurerm_role_assignment" "role_assignment_cognitive_services_usages_reader_service_principal" { + description = "Cognitive Services Usages Reader to check quota for Azure Open AI models." + scope = data.azurerm_subscription.current.id + role_definition_name = "Cognitive Services Usages Reader" + principal_id = data.azuread_service_principal.service_principal.object_id + principal_type = "ServicePrincipal" +} + # Storage role assignments resource "azurerm_role_assignment" "role_assignment_storage_container_external_blob_data_owner_service_principal" { description = "Role assignment to the external storage container." From 2acae33d642de5ad3edc0f14f527090de5ee2509 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Wed, 15 Jan 2025 16:29:27 +0100 Subject: [PATCH 05/10] Lint --- main.tf | 1 + modules/dataapplication/aiservice.tf | 6 +++--- modules/dataapplication/terraform.tf | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/main.tf b/main.tf index d012ebb..8d6bfd6 100644 --- a/main.tf +++ b/main.tf @@ -80,6 +80,7 @@ module "data_application" { providers = { azurerm = azurerm + azapi = azapi azuread = azuread time = time } diff --git a/modules/dataapplication/aiservice.tf b/modules/dataapplication/aiservice.tf index 7e7ff63..5f9bbf1 100644 --- a/modules/dataapplication/aiservice.tf +++ b/modules/dataapplication/aiservice.tf @@ -8,9 +8,9 @@ module "ai_service" { for_each = var.ai_services - location = var.location - resource_group_name = azurerm_resource_group.resource_group_app.name - tags = var.tags + location = var.location + resource_group_name = azurerm_resource_group.resource_group_app.name + tags = var.tags cognitive_account_name = "${local.prefix}-${each.key}-kv001" cognitive_account_kind = each.value.kind diff --git a/modules/dataapplication/terraform.tf b/modules/dataapplication/terraform.tf index 250b441..ea79b96 100644 --- a/modules/dataapplication/terraform.tf +++ b/modules/dataapplication/terraform.tf @@ -5,7 +5,7 @@ terraform { version = "~> 4.0" } azapi = { - source = "Azure/azapi" + source = "Azure/azapi" version = "~> 2.0" } azuread = { From b7296c4d2cd03fd467f33099a51938ef424b64f4 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Thu, 16 Jan 2025 08:29:55 +0100 Subject: [PATCH 06/10] Update ai service reference --- modules/dataapplication/roleassignments_admin.tf | 2 +- modules/dataapplication/roleassignments_developer.tf | 2 +- modules/dataapplication/roleassignments_service_principal.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/dataapplication/roleassignments_admin.tf b/modules/dataapplication/roleassignments_admin.tf index 6d47155..cdc07a1 100644 --- a/modules/dataapplication/roleassignments_admin.tf +++ b/modules/dataapplication/roleassignments_admin.tf @@ -46,7 +46,7 @@ resource "azurerm_role_assignment" "role_assignment_ai_service_admin" { for_each = var.ai_services description = "Role assignment to the ai services." - scope = module.ai_services[each.key].cognitive_account_id + scope = module.ai_service[each.key].cognitive_account_id role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] principal_id = data.azuread_group.group_admin.object_id principal_type = "Group" diff --git a/modules/dataapplication/roleassignments_developer.tf b/modules/dataapplication/roleassignments_developer.tf index fcfe471..1323ca6 100644 --- a/modules/dataapplication/roleassignments_developer.tf +++ b/modules/dataapplication/roleassignments_developer.tf @@ -56,7 +56,7 @@ resource "azurerm_role_assignment" "role_assignment_ai_service_developer" { for_each = var.ai_services description = "Role assignment to the ai services." - scope = module.ai_services[each.key].cognitive_account_id + scope = module.ai_service[each.key].cognitive_account_id role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] principal_id = one(data.azuread_group.group_developer[*].object_id) principal_type = "Group" diff --git a/modules/dataapplication/roleassignments_service_principal.tf b/modules/dataapplication/roleassignments_service_principal.tf index a01e622..1325e1c 100644 --- a/modules/dataapplication/roleassignments_service_principal.tf +++ b/modules/dataapplication/roleassignments_service_principal.tf @@ -46,7 +46,7 @@ resource "azurerm_role_assignment" "role_assignment_ai_service_service_principal for_each = var.ai_services description = "Role assignment to the ai services." - scope = module.ai_services[each.key].cognitive_account_id + scope = module.ai_service[each.key].cognitive_account_id role_definition_name = local.ai_service_kind_role_map_write[each.value.kind] principal_id = data.azuread_service_principal.service_principal.object_id principal_type = "ServicePrincipal" From 0edd8efdbc1ab7408c97bcc355b95f1b565097ad Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Thu, 16 Jan 2025 08:35:21 +0100 Subject: [PATCH 07/10] Update to latest terraform --- .github/workflows/terraform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index c99e7ec..78d25b5 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -28,7 +28,7 @@ jobs: name: "Terraform" with: environment: "dev" - terraform_version: "1.10.3" + terraform_version: "1.10.4" node_version: 20 working_directory: "./tests/e2e" tenant_id: "37963dd4-f4e6-40f8-a7d6-24b97919e452" @@ -43,7 +43,7 @@ jobs: if: github.event_name == 'push' || github.event_name == 'release' with: environment: "dev" - terraform_version: "1.10.3" + terraform_version: "1.10.4" node_version: 20 working_directory: "./tests/e2e" tenant_id: "37963dd4-f4e6-40f8-a7d6-24b97919e452" From 5fafdefd4f37fb56c785b42aa4abc55bbe887e65 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Thu, 16 Jan 2025 08:35:30 +0100 Subject: [PATCH 08/10] Update azurerm provider version --- tests/e2e/terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/terraform.tf b/tests/e2e/terraform.tf index 956769f..9e2ab40 100644 --- a/tests/e2e/terraform.tf +++ b/tests/e2e/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "4.14.0" + version = "4.15.0" } azapi = { source = "azure/azapi" From 0122f0ef72c1e4f99bb7f0c0e4d314cb504b143f Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Thu, 16 Jan 2025 21:18:42 +0100 Subject: [PATCH 09/10] Update region --- tests/e2e/data-applications/app001.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/data-applications/app001.yml b/tests/e2e/data-applications/app001.yml index e0beb7b..884074a 100644 --- a/tests/e2e/data-applications/app001.yml +++ b/tests/e2e/data-applications/app001.yml @@ -32,6 +32,6 @@ budget: ai_services: oai: - location: westeurope + location: swedencentral kind: OpenAI sku: S0 From bcd66d066f3ba57cf321e871cacba441cd268c0e Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 17 Jan 2025 08:58:59 +0100 Subject: [PATCH 10/10] Switch to form recognizer --- tests/e2e/data-applications/app001.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/data-applications/app001.yml b/tests/e2e/data-applications/app001.yml index 884074a..0054555 100644 --- a/tests/e2e/data-applications/app001.yml +++ b/tests/e2e/data-applications/app001.yml @@ -31,7 +31,7 @@ budget: email_address: test@microsoft.com ai_services: - oai: + fr: location: swedencentral - kind: OpenAI + kind: FormRecognizer sku: S0