Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ai Services in Application Layer #127

Merged
merged 11 commits into from
Jan 17, 2025
4 changes: 2 additions & 2 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module "data_application" {

providers = {
azurerm = azurerm
azapi = azapi
azuread = azuread
time = time
}
Expand All @@ -94,6 +95,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
Expand Down
28 changes: 28 additions & 0 deletions modules/dataapplication/aiservice.tf
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 39 additions & 0 deletions modules/dataapplication/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
19 changes: 19 additions & 0 deletions modules/dataapplication/roleassignments_admin.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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_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"
}

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."
Expand Down
11 changes: 11 additions & 0 deletions modules/dataapplication/roleassignments_developer.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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_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"
}

# Storage role assignments
resource "azurerm_role_assignment" "role_assignment_storage_container_external_blob_data_conributor_developer" {
count = var.developer_group_name == "" ? 0 : 1
Expand Down
2 changes: 2 additions & 0 deletions modules/dataapplication/roleassignments_reader.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions modules/dataapplication/roleassignments_service_principal.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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_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"
}

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."
Expand Down
4 changes: 4 additions & 0 deletions modules/dataapplication/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 30 additions & 0 deletions modules/dataapplication/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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."
Expand Down
59 changes: 59 additions & 0 deletions schemas/app.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
6 changes: 6 additions & 0 deletions tests/e2e/data-applications/app001.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ budget:
endpoints:
email:
email_address: [email protected]

ai_services:
fr:
location: swedencentral
kind: FormRecognizer
sku: S0
2 changes: 1 addition & 1 deletion tests/e2e/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.14.0"
version = "4.15.0"
}
azapi = {
source = "azure/azapi"
Expand Down
Loading