Move cosmosdb database creation to bicep deployment and fix AI Search private endpoint (#255)

This commit is contained in:
Josh Bradley 2025-03-10 19:41:28 -04:00 committed by GitHub
parent bd11643a9f
commit 942b2c63a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 355 additions and 158 deletions

View File

@ -46,22 +46,24 @@ async def catch_all_exceptions_middleware(request: Request, call_next):
return Response("Unexpected internal server error.", status_code=500) return Response("Unexpected internal server error.", status_code=500)
# NOTE: this function is not currently used, but it is a placeholder for future use once RBAC issues have been resolved
def intialize_cosmosdb_setup(): def intialize_cosmosdb_setup():
"""Initialise CosmosDB (if necessary) by setting up a database and containers that are expected at startup time.""" """Initialise database setup (if necessary) and configure CosmosDB containers that are expected at startup time if they do not exist."""
azure_client_manager = AzureClientManager() azure_client_manager = AzureClientManager()
client = azure_client_manager.get_cosmos_client() client = azure_client_manager.get_cosmos_client()
db_client = client.create_database_if_not_exists("graphrag")
# create containers with default settings
throughput = ThroughputProperties( throughput = ThroughputProperties(
auto_scale_max_throughput=1000, auto_scale_increment_percent=1 auto_scale_max_throughput=1000, auto_scale_increment_percent=1
) )
db_client = client.create_database_if_not_exists(
"graphrag", offer_throughput=throughput
)
# create containers with default settings
db_client.create_container_if_not_exists( db_client.create_container_if_not_exists(
id="jobs", partition_key=PartitionKey(path="/id"), offer_throughput=throughput id="jobs", partition_key=PartitionKey(path="/id")
) )
db_client.create_container_if_not_exists( db_client.create_container_if_not_exists(
id="container-store", id="container-store",
partition_key=PartitionKey(path="/id"), partition_key=PartitionKey(path="/id"),
offer_throughput=throughput,
) )
@ -78,8 +80,8 @@ async def lifespan(app: FastAPI):
yield yield
return return
# Initialize CosmosDB setup # TODO: must identify proper CosmosDB RBAC roles before databases and containers can be created by this web app
intialize_cosmosdb_setup() # intialize_cosmosdb_setup()
try: try:
# Check if the cronjob exists and create it if it does not exist # Check if the cronjob exists and create it if it does not exist

View File

@ -84,6 +84,7 @@
"networkNetworkSecurityGroupsSecurityRules": "nsgsr-", "networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
"networkNetworkWatchers": "nw-", "networkNetworkWatchers": "nw-",
"networkPrivateDnsZones": "pdnsz-", "networkPrivateDnsZones": "pdnsz-",
"networkPrivateLinkScope": "pls-",
"networkPrivateLinkServices": "pl-", "networkPrivateLinkServices": "pl-",
"networkPublicIPAddresses": "pip-", "networkPublicIPAddresses": "pip-",
"networkPublicIPPrefixes": "ippre-", "networkPublicIPPrefixes": "ippre-",

View File

@ -10,7 +10,7 @@ param location string = resourceGroup().location
@allowed(['enabled', 'disabled']) @allowed(['enabled', 'disabled'])
param publicNetworkAccess string = 'enabled' param publicNetworkAccess string = 'enabled'
resource aiSearch 'Microsoft.Search/searchServices@2024-03-01-preview' = { resource search 'Microsoft.Search/searchServices@2024-06-01-preview' = {
name: name name: name
location: location location: location
sku: { sku: {
@ -21,9 +21,13 @@ resource aiSearch 'Microsoft.Search/searchServices@2024-03-01-preview' = {
replicaCount: 1 replicaCount: 1
partitionCount: 1 partitionCount: 1
publicNetworkAccess: publicNetworkAccess publicNetworkAccess: publicNetworkAccess
networkRuleSet: {
ipRules: []
bypass: 'AzureServices'
}
semanticSearch: 'disabled' semanticSearch: 'disabled'
} }
} }
output name string = aiSearch.name output name string = search.name
output id string = aiSearch.id output id string = search.id

View File

@ -50,7 +50,7 @@ param subnetId string
param privateDnsZoneName string param privateDnsZoneName string
@description('Array of object ids that will have admin role of the cluster') @description('Array of object ids of admins that will have admin control over the cluster')
param clusterAdmins array = [] param clusterAdmins array = []
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = { resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = {
@ -187,11 +187,10 @@ resource aksManagedAutoUpgradeSchedule 'Microsoft.ContainerService/managedCluste
schedule: { schedule: {
weekly: { weekly: {
intervalWeeks: 1 intervalWeeks: 1
dayOfWeek: 'Monday' dayOfWeek: 'Sunday'
} }
} }
durationHours: 4 durationHours: 4
startDate: '2024-06-11'
startTime: '12:00' startTime: '12:00'
} }
} }
@ -209,7 +208,6 @@ resource aksManagedNodeOSUpgradeSchedule 'Microsoft.ContainerService/managedClus
} }
} }
durationHours: 4 durationHours: 4
startDate: '2024-06-11'
startTime: '12:00' startTime: '12:00'
} }
} }

View File

@ -2,7 +2,7 @@
// Licensed under the MIT License. // Licensed under the MIT License.
@description('The name of the API Management service instance') @description('The name of the API Management service instance')
param apiManagementName string = 'apiservice${uniqueString(resourceGroup().id)}' param apiManagementName string
@description('The email address of the owner of the service') @description('The email address of the owner of the service')
@minLength(1) @minLength(1)

View File

@ -10,6 +10,8 @@ param location string = resourceGroup().location
@allowed(['Enabled', 'Disabled']) @allowed(['Enabled', 'Disabled'])
param publicNetworkAccess string = 'Disabled' param publicNetworkAccess string = 'Disabled'
var maxThroughput = 1000
resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = { resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = {
name: cosmosDbName name: cosmosDbName
location: location location: location
@ -64,7 +66,101 @@ resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = {
} }
networkAclBypassResourceIds: [] networkAclBypassResourceIds: []
capacity: { capacity: {
totalThroughputLimit: 4000 totalThroughputLimit: maxThroughput
}
}
}
// create a single database that is used to maintain state information for graphrag indexing
// NOTE: The current CosmosDB role assignments are not sufficient to allow the aks workload identity to create databases and containers so we must do it in bicep at deployment time.
// TODO: Identify and assign appropriate RBAC roles that allow the workload identity to create new databases and containers instead of relying on this bicep implementation.
resource graphragDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-11-15' = {
parent: cosmosDb
name: 'graphrag'
properties: {
options: {
autoscaleSettings: {
maxThroughput: maxThroughput
}
}
resource: {
id: 'graphrag'
}
}
}
resource jobsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-11-15' = {
parent: graphragDatabase
name: 'jobs'
properties: {
resource: {
id: 'jobs'
indexingPolicy: {
indexingMode: 'consistent'
automatic: true
includedPaths: [
{
path: '/*'
}
]
excludedPaths: [
{
path: '/"_etag"/?'
}
]
}
partitionKey: {
paths: [
'/id'
]
kind: 'Hash'
version: 2
}
uniqueKeyPolicy: {
uniqueKeys: []
}
conflictResolutionPolicy: {
mode: 'LastWriterWins'
conflictResolutionPath: '/_ts'
}
}
}
}
resource containerStoreContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-11-15' = {
parent: graphragDatabase
name: 'container-store'
properties: {
resource: {
id: 'container-store'
indexingPolicy: {
indexingMode: 'consistent'
automatic: true
includedPaths: [
{
path: '/*'
}
]
excludedPaths: [
{
path: '/"_etag"/?'
}
]
}
partitionKey: {
paths: [
'/id'
]
kind: 'Hash'
version: 2
}
uniqueKeyPolicy: {
uniqueKeys: []
}
conflictResolutionPolicy: {
mode: 'LastWriterWins'
conflictResolutionPath: '/_ts'
}
} }
} }
} }

View File

@ -24,5 +24,6 @@ resource federatedCredentialResources 'Microsoft.ManagedIdentity/userAssignedIde
] ]
output name string = identity.name output name string = identity.name
output id string = identity.id
output clientId string = identity.properties.clientId output clientId string = identity.properties.clientId
output principalId string = identity.properties.principalId output principalId string = identity.properties.principalId

View File

@ -25,6 +25,7 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
} }
} }
output name string = appInsights.name
output id string = appInsights.id output id string = appInsights.id
output connectionString string = appInsights.properties.ConnectionString output connectionString string = appInsights.properties.ConnectionString
output instrumentationKey string = appInsights.properties.InstrumentationKey output instrumentationKey string = appInsights.properties.InstrumentationKey

View File

@ -6,7 +6,7 @@ param privateLinkScopedResources array = []
param queryAccessMode string = 'Open' param queryAccessMode string = 'Open'
param ingestionAccessMode string = 'PrivateOnly' param ingestionAccessMode string = 'PrivateOnly'
resource privateLinkScope 'microsoft.insights/privateLinkScopes@2021-07-01-preview' = { resource privateLinkScope 'microsoft.Insights/privateLinkScopes@2021-07-01-preview' = {
name: privateLinkScopeName name: privateLinkScopeName
location: 'global' location: 'global'
properties: { properties: {
@ -17,7 +17,7 @@ resource privateLinkScope 'microsoft.insights/privateLinkScopes@2021-07-01-previ
} }
} }
resource scopedResources 'microsoft.insights/privateLinkScopes/scopedResources@2021-07-01-preview' = [ resource scopedResources 'Microsoft.Insights/privateLinkScopes/scopedResources@2021-07-01-preview' = [
for id in privateLinkScopedResources: { for id in privateLinkScopedResources: {
name: uniqueString(id) name: uniqueString(id)
parent: privateLinkScope parent: privateLinkScope

View File

@ -8,85 +8,189 @@ param principalId string
@allowed(['ServicePrincipal', 'User', 'Group', 'Device', 'ForeignGroup']) @allowed(['ServicePrincipal', 'User', 'Group', 'Device', 'ForeignGroup'])
param principalType string param principalType string
@description('Name of an existing AI Search resource.')
param aiSearchName string
@description('Name of an existing AppInsights resource.')
param appInsightsName string
@description('Name of an existing CosmosDB resource.') @description('Name of an existing CosmosDB resource.')
param cosmosDbName string param cosmosDbName string
@description('Role definitions for various roles that will be assigned at deployment time. Learn more: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles') @description('Name of an existing Azure Storage resource.')
var roleDefinitions = [ param storageName string
{
id: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor Role
}
{
id: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // AI Search Contributor Role
}
{
id: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' // AI Search Index Data Contributor Role
}
{
id: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // AI Search Index Data Reader Role
}
{
id: 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Cognitive Services OpenAI Contributor
}
{
id: '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher Role
}
]
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ @description('Role definitions for various roles that will be assigned. Learn more: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles')
for roleDef in roleDefinitions: { var roleIds = {
// note: the guid must be globally unique and deterministic (reproducible) across Azure contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor Role
name: guid(subscription().subscriptionId, resourceGroup().name, principalId, principalType, roleDef.id) aiSearchIndexDataContributor: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' // AI Search Index Data Contributor Role
aiSearchIndexDataReader: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // AI Search Index Data Reader Role
cognitiveServicesOpenAIContributor: 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Cognitive Services OpenAI Contributor Role
cosmosDBOperator: '230815da-be43-4aae-9cb4-875f7bd000aa' // Cosmos DB Operator Role - cosmos control plane operations
cosmosDbBuiltInDataContributor: '00000000-0000-0000-0000-000000000002' // Cosmos Built-in Data Contributor Role - cosmos data plane operations
documentDBAccountContributor: '5bd9cd88-fe45-4216-938b-f97437e15450' // DocumentDB Account Contributor Role - cosmos control plane operations
monitoringMetricsPublisher: '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher Role
storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor Role
sqlDBContributor: '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec' // SQL DB Contributor Role - cosmos control plane operations
}
// get references to existing resources
resource aiSearch 'Microsoft.Search/searchServices@2024-03-01-preview' existing = {
name: aiSearchName
}
resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = {
name: appInsightsName
}
resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = {
name: cosmosDbName
}
resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' existing = {
name: storageName
}
// make RBAC role assignments to each resource
resource contributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(aiSearch.id, principalId, principalType, roleIds.contributor)
scope: aiSearch
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.contributor)
}
}
resource cognitiveServicesOpenAIContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(resourceGroup().id, principalId, principalType, roleIds.cognitiveServicesOpenAIContributor)
scope: resourceGroup() scope: resourceGroup()
properties: { properties: {
principalId: principalId principalId: principalId
principalType: principalType principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDef.id) roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.cognitiveServicesOpenAIContributor)
} }
}
]
resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = {
name: cosmosDbName
} }
var customRoleName = 'Custom cosmosDB role for graphrag - adds read/write permissions at the database and container level' resource aiSearchIndexDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
resource customCosmosRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-12-01-preview' = { // note: the guid must be globally unique and deterministic across Azure
// note: the guid must be globally unique and deterministic (reproducible) across Azure name: guid(aiSearch.id, principalId, principalType, roleIds.aiSearchIndexDataContributor)
name: guid(subscription().subscriptionId, resourceGroup().name, cosmosDb.id, customRoleName) // guid is used to ensure uniqueness scope: aiSearch
parent: cosmosDb
properties: { properties: {
roleName: customRoleName principalId: principalId
type: 'CustomRole' principalType: principalType
assignableScopes: [ roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.aiSearchIndexDataContributor)
cosmosDb.id
]
permissions: [
{
dataActions: [
'Microsoft.DocumentDB/databaseAccounts/readMetadata'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/write'
]
}
]
} }
} }
resource assignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-12-01-preview' = { resource aiSearchIndexDataReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic (reproducible) across Azure // note: the guid must be globally unique and deterministic across Azure
name: guid( name: guid(aiSearch.id, principalId, principalType, roleIds.aiSearchIndexDataReader)
subscription().subscriptionId, scope: aiSearch
resourceGroup().name, properties: {
cosmosDb.id, principalId: principalId
customCosmosRoleDefinition.id, principalType: principalType
principalId roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.aiSearchIndexDataReader)
) }
}
resource cosmosDbOperatorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(cosmosDb.id, principalId, principalType, roleIds.cosmosDBOperator)
scope: cosmosDb
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.cosmosDBOperator)
}
}
resource documentDbAccountContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(cosmosDb.id, principalId, principalType, roleIds.documentDBAccountContributor)
scope: cosmosDb
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.documentDBAccountContributor)
}
}
resource sqlDbContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(cosmosDb.id, principalId, principalType, roleIds.sqlDBContributor)
scope: cosmosDb
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.sqlDBContributor)
}
}
resource storageBlobDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(storage.id, principalId, principalType, roleIds.storageBlobDataContributor)
scope: storage
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.storageBlobDataContributor)
}
}
resource monitoringMetricsPublisherRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// note: the guid must be globally unique and deterministic across Azure
name: guid(appInsights.id, principalId, roleIds.monitoringMetricsPublisher)
scope: appInsights
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleIds.monitoringMetricsPublisher)
}
}
// NOTE: The SQL role assignment below can be flaky due to a known race condition issue at deployment time when assigning Cosmos DB built-in roles to an identity.
// For more information: https://github.com/pulumi/pulumi-azure-native/issues/2816
// In practice, one option that may not have such flaky behavior is to create a custom role defintion with the same permissions as the built-in role and use it instead
resource sqlRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-11-15' = {
name: guid(cosmosDb.id, principalId, principalType, roleIds.cosmosDbBuiltInDataContributor)
parent: cosmosDb parent: cosmosDb
properties: { properties: {
principalId: principalId principalId: principalId
roleDefinitionId: customCosmosRoleDefinition.id roleDefinitionId: '${cosmosDb.id}/sqlRoleDefinitions/${roleIds.cosmosDbBuiltInDataContributor}'
scope: cosmosDb.id scope: cosmosDb.id
} }
} }
// var customRoleName = 'Custom cosmosDB role for graphrag - adds read/write permissions at the container level'
// resource customCosmosRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-12-01-preview' = {
// // note: the guid must be globally unique and deterministic (reproducible) across Azure
// name: guid(cosmosDb.id, customRoleName)
// parent: cosmosDb
// properties: {
// roleName: customRoleName
// type: 'CustomRole'
// assignableScopes: [
// cosmosDb.id
// ]
// permissions: [
// {
// dataActions: [
// 'Microsoft.DocumentDB/databaseAccounts/readMetadata'
// 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*'
// 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
// ]
// }
// ]
// }
// }
// resource customRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-12-01-preview' = {
// // note: the guid must be globally unique and deterministic (reproducible) across Azure
// name: guid(cosmosDb.id, principalId, principalType, customCosmosRoleDefinition.id)
// parent: cosmosDb
// properties: {
// principalId: principalId
// roleDefinitionId: customCosmosRoleDefinition.id
// scope: cosmosDb.id
// }
// }

View File

@ -7,7 +7,7 @@ param nsgName string = 'apim-nsg-${uniqueString(resourceGroup().id)}'
@description('Azure region where the resources will be deployed') @description('Azure region where the resources will be deployed')
param location string = resourceGroup().location param location string = resourceGroup().location
resource nsg 'Microsoft.Network/networkSecurityGroups@2024-01-01' = { resource nsg 'Microsoft.Network/networkSecurityGroups@2024-05-01' = {
name: nsgName name: nsgName
location: location location: location
properties: { properties: {

View File

@ -5,13 +5,13 @@ param vnetId string
param privateDnsZoneName string param privateDnsZoneName string
var vnet_id_hash = uniqueString(vnetId) var vnet_id_hash = uniqueString(vnetId)
resource dnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { resource dnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
name: privateDnsZoneName name: privateDnsZoneName
location: 'global' location: 'global'
properties: {} properties: {}
} }
resource dnsZoneLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { resource dnsZoneLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
name: 'vnet-link-${privateDnsZoneName}-${vnet_id_hash}' name: 'vnet-link-${privateDnsZoneName}-${vnet_id_hash}'
location: 'global' location: 'global'
parent: dnsZone parent: dnsZone

View File

@ -13,11 +13,11 @@ param ttl int = 900
@description('The IP address') @description('The IP address')
param ipv4Address string param ipv4Address string
resource dnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = { resource dnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
name: dnsZoneName name: dnsZoneName
} }
resource aRecord 'Microsoft.Network/privateDnsZones/A@2020-06-01' = { resource aRecord 'Microsoft.Network/privateDnsZones/A@2024-06-01' = {
name: name name: name
parent: dnsZone parent: dnsZone
properties: { properties: {

View File

@ -1,12 +1,13 @@
{ {
"azureCloud": { "azureCloud": {
"aiSearch": "privatelink.search.azure.com", "aiSearch": "privatelink.search.windows.net",
"azureMonitor": [ "azureMonitor": [
"privatelink.monitor.azure.com", "privatelink.monitor.azure.com",
"privatelink.oms.opinsights.azure.com", "privatelink.oms.opinsights.azure.com",
"privatelink.agentsvc.azure-automation.net", "privatelink.agentsvc.azure-automation.net",
"privatelink.ods.opinsights.azure.com" "privatelink.ods.opinsights.azure.com"
], ],
"blobStorage": "privatelink.blob.core.windows.net",
"cosmosDB": "privatelink.documents.azure.com" "cosmosDB": "privatelink.documents.azure.com"
}, },
"azureusgovernment": { "azureusgovernment": {
@ -17,6 +18,7 @@
"privatelink.agentsvc.azure-automation.us", "privatelink.agentsvc.azure-automation.us",
"privatelink.ods.opinsights.azure.us" "privatelink.ods.opinsights.azure.us"
], ],
"blobStorage": "privatelink.blob.core.usgovcloudapi.net",
"cosmosDB": "privatelink.documents.azure.us" "cosmosDB": "privatelink.documents.azure.us"
} }
} }

View File

@ -5,33 +5,29 @@
param name string param name string
@description('The name of the virtual networks the DNS zone should be associated with.') @description('The name of the virtual networks the DNS zone should be associated with.')
param vnetNames string[] param vnetName string
resource dnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = {
name: vnetName
}
resource dnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
name: name name: name
location: 'global' location: 'global'
properties: {} properties: {}
} }
resource vnets 'Microsoft.Network/virtualNetworks@2024-01-01' existing = [ resource dnsZoneLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
for vnetName in vnetNames: {
name: vnetName
}
]
resource dnsZoneLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = [
for (vnetName, index) in vnetNames: {
name: vnetName name: vnetName
location: 'global' location: 'global'
parent: dnsZone parent: dnsZone
properties: { properties: {
registrationEnabled: false registrationEnabled: false
virtualNetwork: { virtualNetwork: {
id: vnets[index].id id: vnet.id
} }
} }
} }
]
output name string = dnsZone.name output name string = dnsZone.name
output id string = dnsZone.id output id string = dnsZone.id

View File

@ -14,7 +14,7 @@ param privateEndpointName string
param groupId string param groupId string
param location string = resourceGroup().location param location string = resourceGroup().location
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = { resource privateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = {
name: privateEndpointName name: privateEndpointName
location: location location: location
properties: { properties: {
@ -33,7 +33,7 @@ resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = {
} }
} }
resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-05-01' = { resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = {
name: groupId name: groupId
parent: privateEndpoint parent: privateEndpoint
properties: { properties: {

View File

@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
@description('Virtual Network IDs to link to') @description('The virtual network ID to link to')
param linkedVnetIds array param linkedVnetId string
var privateDnsZoneData = loadJsonContent('private-dns-zone-groups.json') var privateDnsZoneData = loadJsonContent('private-dns-zone-groups.json') // for more information: https://learn.microsoft.com/en-us/azure/azure-government/compare-azure-government-global-azure
var cloudName = toLower(environment().name) var cloudName = toLower(environment().name)
var aiSearchPrivateDnsZoneName = privateDnsZoneData[cloudName].aiSearch var aiSearchPrivateDnsZoneName = privateDnsZoneData[cloudName].aiSearch
var blobStoragePrivateDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}' var blobStoragePrivateDnsZoneName = privateDnsZoneData[cloudName].blobStorage
var cosmosDbPrivateDnsZoneName = privateDnsZoneData[cloudName].cosmosDb var cosmosDbPrivateDnsZoneName = privateDnsZoneData[cloudName].cosmosDb
var storagePrivateDnsZoneNames = [blobStoragePrivateDnsZoneName] var storagePrivateDnsZoneNames = [blobStoragePrivateDnsZoneName]
var azureMonitorPrivateDnsZones = privateDnsZoneData[cloudName].azureMonitor var azureMonitorPrivateDnsZones = privateDnsZoneData[cloudName].azureMonitor
@ -20,7 +20,7 @@ var privateDnsZones = union(
[aiSearchPrivateDnsZoneName] [aiSearchPrivateDnsZoneName]
) )
resource privateDnsZoneResources 'Microsoft.Network/privateDnsZones@2020-06-01' = [ resource privateDnsZoneResources 'Microsoft.Network/privateDnsZones@2024-06-01' = [
for name in privateDnsZones: { for name in privateDnsZones: {
name: name name: name
location: 'global' location: 'global'
@ -32,7 +32,7 @@ module dnsVnetLinks 'vnet-dns-link.bicep' = [
name: replace(privateDnsZoneName, '.', '-') name: replace(privateDnsZoneName, '.', '-')
params: { params: {
privateDnsZoneName: privateDnsZoneResources[index].name privateDnsZoneName: privateDnsZoneResources[index].name
vnetIds: linkedVnetIds vnetId: linkedVnetId
} }
} }
] ]

View File

@ -2,22 +2,21 @@
// Licensed under the MIT License. // Licensed under the MIT License.
param privateDnsZoneName string param privateDnsZoneName string
param vnetIds array param vnetId string
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = { resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
name: privateDnsZoneName name: privateDnsZoneName
} }
resource dnsVnetLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = [ resource dnsVnetLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
for vnetId in vnetIds: {
name: '${replace(privateDnsZoneName, '.', '-')}-${uniqueString(vnetId)}' name: '${replace(privateDnsZoneName, '.', '-')}-${uniqueString(vnetId)}'
parent: privateDnsZone parent: privateDnsZone
location: 'global' location: 'global'
properties: { properties: {
registrationEnabled: false
resolutionPolicy: 'Default'
virtualNetwork: { virtualNetwork: {
id: vnetId id: vnetId
} }
registrationEnabled: false
} }
} }
]

View File

@ -17,7 +17,7 @@ param apimTier string
@description('NSG resource ID.') @description('NSG resource ID.')
param nsgID string param nsgID string
resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' = { resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: vnetName name: vnetName
location: location location: location
properties: { properties: {
@ -67,7 +67,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' = {
} }
} }
output vnetId string = vnet.id output name string = vnet.name
output vnetName string = vnet.name output id string = vnet.id
output apimSubnetId string = vnet.properties.subnets[0].id output apimSubnetId string = vnet.properties.subnets[0].id
output aksSubnetId string = vnet.properties.subnets[1].id output aksSubnetId string = vnet.properties.subnets[1].id

View File

@ -313,9 +313,6 @@ checkForApimSoftDelete () {
deployAzureResources () { deployAzureResources () {
echo "Deploying Azure resources..." echo "Deploying Azure resources..."
# get principal/object id of the signed in user
local deployerPrincipalId=$(az ad signed-in-user show --output json | jq -r .id)
exitIfValueEmpty $deployerPrincipalId "Principal ID of deployer not found"
local datetime="`date +%Y-%m-%d_%H-%M-%S`" local datetime="`date +%Y-%m-%d_%H-%M-%S`"
local deployName="graphrag-deploy-$datetime" local deployName="graphrag-deploy-$datetime"
echo "Deployment name: $deployName" echo "Deployment name: $deployName"
@ -331,7 +328,6 @@ deployAzureResources () {
--parameters "apiPublisherEmail=$PUBLISHER_EMAIL" \ --parameters "apiPublisherEmail=$PUBLISHER_EMAIL" \
--parameters "enablePrivateEndpoints=$ENABLE_PRIVATE_ENDPOINTS" \ --parameters "enablePrivateEndpoints=$ENABLE_PRIVATE_ENDPOINTS" \
--parameters "acrName=$CONTAINER_REGISTRY_NAME" \ --parameters "acrName=$CONTAINER_REGISTRY_NAME" \
--parameters "deployerPrincipalId=$deployerPrincipalId" \
--output json) --output json)
# errors in deployment may not be caught by exitIfCommandFailed function so we also check the output for errors # errors in deployment may not be caught by exitIfCommandFailed function so we also check the output for errors
exitIfCommandFailed $? "Error deploying Azure resources..." exitIfCommandFailed $? "Error deploying Azure resources..."

View File

@ -31,9 +31,6 @@ var resourceBaseNameFinal = !empty(resourceBaseName)
@description('Cloud region for all resources') @description('Cloud region for all resources')
param location string = az.resourceGroup().location param location string = az.resourceGroup().location
@description('Principal/Object ID of the deployer. Will be used to assign admin roles to the AKS cluster.')
param deployerPrincipalId string
@minLength(1) @minLength(1)
@description('Name of the publisher of the API Management instance.') @description('Name of the publisher of the API Management instance.')
param apiPublisherName string = 'Microsoft' param apiPublisherName string = 'Microsoft'
@ -45,7 +42,7 @@ param apiPublisherEmail string = 'publisher@microsoft.com'
@description('The AKS namespace to install GraphRAG in.') @description('The AKS namespace to install GraphRAG in.')
param aksNamespace string = 'graphrag' param aksNamespace string = 'graphrag'
@description('Whether to enable private endpoints.') @description('Whether to use private endpoint connections or not.')
param enablePrivateEndpoints bool = true param enablePrivateEndpoints bool = true
@description('Whether to restore the API Management instance.') @description('Whether to restore the API Management instance.')
@ -92,7 +89,10 @@ module aksWorkloadIdentityRBAC 'core/rbac/workload-identity-rbac.bicep' = {
params: { params: {
principalId: workloadIdentity.outputs.principalId principalId: workloadIdentity.outputs.principalId
principalType: 'ServicePrincipal' principalType: 'ServicePrincipal'
aiSearchName: aiSearch.outputs.name
appInsightsName: appInsights.outputs.name
cosmosDbName: cosmosdb.outputs.name cosmosDbName: cosmosdb.outputs.name
storageName: storage.outputs.name
} }
} }
@ -163,7 +163,7 @@ module aks 'core/aks/aks.bicep' = {
location: location location: location
graphragVMSize: 'standard_d8s_v5' // 8 vcpu, 32 GB memory graphragVMSize: 'standard_d8s_v5' // 8 vcpu, 32 GB memory
graphragIndexingVMSize: 'standard_e8s_v5' // 8 vcpus, 64 GB memory graphragIndexingVMSize: 'standard_e8s_v5' // 8 vcpus, 64 GB memory
clusterAdmins: !empty(deployerPrincipalId) ? ['${deployerPrincipalId}'] : null clusterAdmins: [deployer().objectId]
logAnalyticsWorkspaceId: log.outputs.id logAnalyticsWorkspaceId: log.outputs.id
subnetId: vnet.outputs.aksSubnetId subnetId: vnet.outputs.aksSubnetId
privateDnsZoneName: privateDnsZone.outputs.name privateDnsZoneName: privateDnsZone.outputs.name
@ -260,25 +260,21 @@ module privateDnsZone 'core/vnet/private-dns-zone.bicep' = {
name: 'private-dns-zone-deployment' name: 'private-dns-zone-deployment'
params: { params: {
name: dnsDomain name: dnsDomain
vnetNames: [ vnetName: vnet.outputs.name
vnet.outputs.vnetName // name
]
} }
} }
module privatelinkPrivateDns 'core/vnet/privatelink-private-dns-zones.bicep' = if (enablePrivateEndpoints) { module privatelinkPrivateDns 'core/vnet/privatelink-private-dns-zones.bicep' = if (enablePrivateEndpoints) {
name: 'privatelink-private-dns-zones-deployment' name: 'privatelink-private-dns-zones-deployment'
params: { params: {
linkedVnetIds: [ linkedVnetId: vnet.outputs.id
vnet.outputs.vnetId // id
]
} }
} }
module azureMonitorPrivateLinkScope 'core/monitor/private-link-scope.bicep' = if (enablePrivateEndpoints) { module azureMonitorPrivateLinkScope 'core/monitor/private-link-scope.bicep' = if (enablePrivateEndpoints) {
name: 'azure-monitor-privatelink-scope-deployment' name: 'azure-monitor-privatelink-scope-deployment'
params: { params: {
privateLinkScopeName: 'pls-${resourceBaseNameFinal}' privateLinkScopeName: '${abbrs.networkPrivateLinkScope}${resourceBaseNameFinal}'
privateLinkScopedResources: [ privateLinkScopedResources: [
log.outputs.id log.outputs.id
appInsights.outputs.id appInsights.outputs.id
@ -334,6 +330,7 @@ module privateLinkScopePrivateEndpoint 'core/vnet/private-endpoint.bicep' = if (
} }
} }
output deployer_principal_id string = deployer().objectId
output azure_location string = location output azure_location string = location
output azure_tenant_id string = tenant().tenantId output azure_tenant_id string = tenant().tenantId
output azure_ai_search_name string = aiSearch.outputs.name output azure_ai_search_name string = aiSearch.outputs.name

View File

@ -61,7 +61,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": null,
"id": "4", "id": "4",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -100,7 +100,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": null,
"id": "7", "id": "7",
"metadata": { "metadata": {
"tags": [] "tags": []
@ -132,7 +132,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": null,
"id": "9", "id": "9",
"metadata": { "metadata": {
"tags": [] "tags": []
@ -159,7 +159,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": null,
"id": "10", "id": "10",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -181,7 +181,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": null,
"id": "12", "id": "12",
"metadata": { "metadata": {
"tags": [] "tags": []
@ -543,7 +543,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 35, "execution_count": null,
"id": "18", "id": "18",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -566,7 +566,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": null,
"id": "20", "id": "20",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -702,7 +702,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 40, "execution_count": null,
"id": "31", "id": "31",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -944,7 +944,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 24, "execution_count": null,
"id": "54", "id": "54",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],