Skip to content

Commit bbfbd46

Browse files
authored
chore(infra): update to latest azd infra templates (closes #130) (#190)
* chore(infra): update to latest azd core templates (closes #130) * chore(infra): fix monitoring template * chore(infra): add user assigned identities to container apps * chore(infra): remove unused form recognizer * chore(infra): enable admin user * chore: revert OAI location * chore(infra): update to latest infra templates * chore(infra): remove app insights option * chore: revert params * chore: update packages * chore(indexer): update packages * chore(search): update packages * chore(infra): update managed identity * fix(search): compatibility with latest openai lib * test: fix playwright tests * docs: fix links Fixes #130
1 parent 1802c46 commit bbfbd46

25 files changed

+4867
-2538
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ You may try the [Azure pricing calculator](https://azure.com/e/8ffbe5b1919c4c72a
9898
- Azure Container Apps: Pay-as-you-go tier. Costs based on vCPU and memory used. [Pricing](https://azure.microsoft.com/pricing/details/container-apps/)
9999
- Azure Static Web Apps: Free Tier. [Pricing](https://azure.microsoft.com/pricing/details/app-service/static/)
100100
- Azure OpenAI: Standard tier, ChatGPT and Ada models. Pricing per 1K tokens used, and at least 1K tokens are used per question. [Pricing](https://azure.microsoft.com/pricing/details/search/)
101-
<!-- - Form Recognizer: SO (Standard) tier using pre-built layout. Pricing per document page, sample documents have 261 pages total. [Pricing](https://azure.microsoft.com/pricing/details/form-recognizer/) -->
102101
- Azure AI Search: Standard tier, 1 replica, free level of semantic search\*. Pricing per hour.[Pricing](https://azure.microsoft.com/pricing/details/search/) (_The pricing may vary or reflect an outdated tier model. Please visit the linked page for more accurate information_)
103102
- Azure Blob Storage: Standard tier with ZRS (Zone-redundant storage). Pricing per storage and read operations. [Pricing](https://azure.microsoft.com/pricing/details/storage/blobs/)
104103
- Azure Monitor: Pay-as-you-go tier. Costs based on data ingested. [Pricing](https://azure.microsoft.com/pricing/details/monitor/)
@@ -183,7 +182,7 @@ If you already have existing Azure resources, you can re-use those by setting `a
183182

184183
#### Other existing Azure resources
185184

186-
You can also use existing Form Recognizer and Storage Accounts. See `./infra/main.parameters.json` for list of environment variables to pass to `azd env set` to configure those existing resources.
185+
You can also use an existing Storage Account. See `./infra/main.parameters.json` for list of environment variables to pass to `azd env set` to configure those existing resources.
187186

188187
#### Provision remaining resources
189188

@@ -329,7 +328,7 @@ For more details, read [Azure OpenAI Landing Zone reference architecture](https:
329328
- [Generative AI For Beginners](https://github.com/microsoft/generative-ai-for-beginners)
330329
- [Revolutionize your Enterprise Data with ChatGPT: Next-gen Apps w/ Azure OpenAI and AI Search](https://aka.ms/entgptsearchblog)
331330
- [Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search)
332-
- [Azure OpenAI Service](https://learn.microsoft.com/azure/cognitive-services/openai/overview)
331+
- [Azure OpenAI Service](https://learn.microsoft.com/azure/ai-services/openai/overview)
333332
- [Building ChatGPT-Like Experiences with Azure: A Guide to Retrieval Augmented Generation for JavaScript applications](https://devblogs.microsoft.com/azure-sdk/building-chatgpt-like-experiences-with-azure-a-guide-to-retrieval-augmented-generation-for-javascript-applications/)
334333

335334
## Clean up
@@ -441,7 +440,7 @@ Here are the most common failure scenarios and solutions:
441440

442441
1. You've exceeded a quota, most often number of resources per region. See [this article on quotas and limits](https://aka.ms/oai/quotas).
443442

444-
1. You're getting "same resource name not allowed" conflicts. That's likely because you've run the sample multiple times and deleted the resources you've been creating each time, but are forgetting to purge them. Azure keeps resources for 48 hours unless you purge from soft delete. See [this article on purging resources](https://learn.microsoft.com/azure/cognitive-services/manage-resources?tabs=azure-portal#purge-a-deleted-resource).
443+
1. You're getting "same resource name not allowed" conflicts. That's likely because you've run the sample multiple times and deleted the resources you've been creating each time, but are forgetting to purge them. Azure keeps resources for 48 hours unless you purge from soft delete. See [this article on purging resources](https://learn.microsoft.com/azure/ai-services/recover-purge-resources?tabs=azure-portal#purge-a-deleted-resource).
445444

446445
1. After running `azd up` and visiting the website, you see a '404 Not Found' in the browser. Wait 10 minutes and try again, as it might be still starting up. Then try running `azd deploy` and wait again. If you still encounter errors with the deployed app, consult these [tips for debugging App Service app deployments](http://blog.pamelafox.org/2023/06/tips-for-debugging-flask-deployments-to.html) and file an issue if the error logs don't help you resolve the issue.
447446

infra/abbreviations.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"analysisServicesServers": "as",
33
"apiManagementService": "apim-",
4-
"appConfigurationConfigurationStores": "appcs-",
4+
"appConfigurationStores": "appcs-",
55
"appManagedEnvironments": "cae-",
66
"appContainerApps": "ca-",
77
"authorizationPolicyDefinitions": "policy-",
@@ -55,6 +55,7 @@
5555
"kubernetesConnectedClusters": "arck",
5656
"kustoClusters": "dec",
5757
"kustoClustersDatabases": "dedb",
58+
"loadTesting": "lt-",
5859
"logicIntegrationAccounts": "ia-",
5960
"logicWorkflows": "logic-",
6061
"machineLearningServicesWorkspaces": "mlw-",

infra/core/ai/cognitiveservices.bicep

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1+
metadata description = 'Creates an Azure Cognitive Services instance.'
12
param name string
23
param location string = resourceGroup().location
34
param tags object = {}
4-
5+
@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.')
56
param customSubDomainName string = name
67
param deployments array = []
78
param kind string = 'OpenAI'
9+
10+
@allowed([ 'Enabled', 'Disabled' ])
811
param publicNetworkAccess string = 'Enabled'
912
param sku object = {
1013
name: 'S0'
1114
}
1215

16+
param allowedIpRules array = []
17+
param networkAcls object = empty(allowedIpRules) ? {
18+
defaultAction: 'Allow'
19+
} : {
20+
ipRules: allowedIpRules
21+
defaultAction: 'Deny'
22+
}
23+
1324
resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = {
1425
name: name
1526
location: location
@@ -18,6 +29,7 @@ resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = {
1829
properties: {
1930
customSubDomainName: customSubDomainName
2031
publicNetworkAccess: publicNetworkAccess
32+
networkAcls: networkAcls
2133
}
2234
sku: sku
2335
}

infra/core/host/container-app.bicep

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,139 @@
1+
metadata description = 'Creates a container app in an Azure Container App environment.'
12
param name string
23
param location string = resourceGroup().location
34
param tags object = {}
45

5-
param containerAppsEnvironmentName string = ''
6+
@description('Allowed origins')
7+
param allowedOrigins array = []
8+
9+
@description('Name of the environment for container apps')
10+
param containerAppsEnvironmentName string
11+
12+
@description('CPU cores allocated to a single container instance, e.g., 0.5')
13+
param containerCpuCoreCount string = '0.5'
14+
15+
@description('The maximum number of replicas to run. Must be at least 1.')
16+
@minValue(1)
17+
param containerMaxReplicas int = 10
18+
19+
@description('Memory allocated to a single container instance, e.g., 1Gi')
20+
param containerMemory string = '1.0Gi'
21+
22+
@description('The minimum number of replicas to run. Must be at least 1.')
23+
param containerMinReplicas int = 1
24+
25+
@description('The name of the container')
626
param containerName string = 'main'
27+
28+
@description('The name of the container registry')
729
param containerRegistryName string = ''
30+
31+
@description('The protocol used by Dapr to connect to the app, e.g., http or grpc')
32+
@allowed([ 'http', 'grpc' ])
33+
param daprAppProtocol string = 'http'
34+
35+
@description('The Dapr app ID')
36+
param daprAppId string = containerName
37+
38+
@description('Enable Dapr')
39+
param daprEnabled bool = false
40+
41+
@description('The environment variables for the container')
842
param env array = []
9-
param secrets array = []
43+
44+
@description('Specifies if the resource ingress is exposed externally')
1045
param external bool = true
11-
param imageName string
12-
param keyVaultName string = ''
13-
param managedIdentity bool = !empty(keyVaultName)
46+
47+
@description('The name of the user-assigned identity')
48+
param identityName string = ''
49+
50+
@description('The type of identity for the resource')
51+
@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ])
52+
param identityType string = 'None'
53+
54+
@description('The name of the container image')
55+
param imageName string = ''
56+
57+
@description('Specifies if Ingress is enabled for the container app')
58+
param ingressEnabled bool = true
59+
60+
param revisionMode string = 'Single'
61+
62+
@description('The secrets required for the container')
63+
param secrets array = []
64+
65+
@description('The service binds associated with the container')
66+
param serviceBinds array = []
67+
68+
@description('The name of the container apps add-on to use. e.g. redis')
69+
param serviceType string = ''
70+
71+
@description('The target port for the container')
1472
param targetPort int = 80
15-
param allowedOrigins array = []
1673

17-
@description('CPU cores allocated to a single container instance, e.g. 0.5')
18-
param containerCpuCoreCount string = '0.5'
74+
resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) {
75+
name: identityName
76+
}
1977

20-
@description('Memory allocated to a single container instance, e.g. 1Gi')
21-
param containerMemory string = '1.0Gi'
78+
// Private registry support requires both an ACR name and a User Assigned managed identity
79+
var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName)
80+
81+
// Automatically set to `UserAssigned` when an `identityName` has been set
82+
var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType
2283

23-
resource app 'Microsoft.App/containerApps@2023-05-01' = {
84+
module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) {
85+
name: '${deployment().name}-registry-access'
86+
params: {
87+
containerRegistryName: containerRegistryName
88+
principalId: usePrivateRegistry ? userIdentity.properties.principalId : ''
89+
}
90+
}
91+
92+
resource app 'Microsoft.App/containerApps@2023-05-02-preview' = {
2493
name: name
2594
location: location
2695
tags: tags
27-
identity: { type: managedIdentity ? 'SystemAssigned' : 'None' }
96+
// It is critical that the identity is granted ACR pull access before the app is created
97+
// otherwise the container app will throw a provision error
98+
// This also forces us to use an user assigned managed identity since there would no way to
99+
// provide the system assigned identity with the ACR pull access before the app is created
100+
dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : []
101+
identity: {
102+
type: normalizedIdentityType
103+
userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null
104+
}
28105
properties: {
29106
managedEnvironmentId: containerAppsEnvironment.id
30107
configuration: {
31-
activeRevisionsMode: 'single'
32-
ingress: {
108+
activeRevisionsMode: revisionMode
109+
ingress: ingressEnabled ? {
33110
external: external
34111
targetPort: targetPort
35112
transport: 'auto'
36113
corsPolicy: {
37-
allowedOrigins: empty(allowedOrigins) ? ['*'] : allowedOrigins
38-
}
39-
}
40-
secrets: concat(secrets, [
41-
{
42-
name: 'registry-password'
43-
value: containerRegistry.listCredentials().passwords[0].value
114+
allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins)
44115
}
45-
])
46-
registries: [
116+
} : null
117+
dapr: daprEnabled ? {
118+
enabled: true
119+
appId: daprAppId
120+
appProtocol: daprAppProtocol
121+
appPort: ingressEnabled ? targetPort : 0
122+
} : { enabled: false }
123+
secrets: secrets
124+
service: !empty(serviceType) ? { type: serviceType } : null
125+
registries: usePrivateRegistry ? [
47126
{
48-
server: '${containerRegistry.name}.azurecr.io'
49-
username: containerRegistry.name
50-
passwordSecretRef: 'registry-password'
127+
server: '${containerRegistryName}.azurecr.io'
128+
identity: userIdentity.id
51129
}
52-
]
130+
] : []
53131
}
54132
template: {
133+
serviceBinds: !empty(serviceBinds) ? serviceBinds : null
55134
containers: [
56135
{
57-
image: imageName
136+
image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
58137
name: containerName
59138
env: env
60139
resources: {
@@ -64,26 +143,20 @@ resource app 'Microsoft.App/containerApps@2023-05-01' = {
64143
}
65144
]
66145
scale: {
67-
minReplicas: 1
68-
maxReplicas: 10
146+
minReplicas: containerMinReplicas
147+
maxReplicas: containerMaxReplicas
69148
}
70149
}
71150
}
72-
dependsOn: [
73-
containerRegistry
74-
]
75151
}
76152

77-
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
153+
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = {
78154
name: containerAppsEnvironmentName
79155
}
80156

81-
// 2022-02-01-preview needed for anonymousPullEnabled
82-
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = {
83-
name: containerRegistryName
84-
}
85-
86-
output identityPrincipalId string = managedIdentity ? app.identity.principalId : ''
157+
output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
158+
output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId)
87159
output imageName string = imageName
88160
output name string = app.name
89-
output uri string = 'https://${app.properties.configuration.ingress.fqdn}'
161+
output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {}
162+
output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : ''

infra/core/host/container-apps-environment.bicep

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
metadata description = 'Creates an Azure Container Apps environment.'
12
param name string
23
param location string = resourceGroup().location
34
param tags object = {}
45

6+
@description('Name of the Application Insights resource')
7+
param applicationInsightsName string = ''
8+
9+
@description('Specifies if Dapr is enabled')
10+
param daprEnabled bool = false
11+
12+
@description('Name of the Log Analytics workspace')
513
param logAnalyticsWorkspaceName string
614

7-
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = {
15+
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = {
816
name: name
917
location: location
1018
tags: tags
@@ -16,11 +24,18 @@ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01'
1624
sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
1725
}
1826
}
27+
daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : ''
1928
}
2029
}
2130

2231
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = {
2332
name: logAnalyticsWorkspaceName
2433
}
2534

35+
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) {
36+
name: applicationInsightsName
37+
}
38+
39+
output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
40+
output id string = containerAppsEnvironment.id
2641
output name string = containerAppsEnvironment.name

infra/core/host/container-apps.bicep

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.'
12
param name string
23
param location string = resourceGroup().location
34
param tags object = {}
45

5-
param containerAppsEnvironmentName string = ''
6-
param containerRegistryName string = ''
7-
param logAnalyticsWorkspaceName string = ''
6+
param containerAppsEnvironmentName string
7+
param containerRegistryName string
8+
param containerRegistryResourceGroupName string = ''
9+
param containerRegistryAdminUserEnabled bool = false
10+
param logAnalyticsWorkspaceName string
11+
param applicationInsightsName string = ''
812

913
module containerAppsEnvironment 'container-apps-environment.bicep' = {
1014
name: '${name}-container-apps-environment'
@@ -13,18 +17,24 @@ module containerAppsEnvironment 'container-apps-environment.bicep' = {
1317
location: location
1418
tags: tags
1519
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
20+
applicationInsightsName: applicationInsightsName
1621
}
1722
}
1823

1924
module containerRegistry 'container-registry.bicep' = {
2025
name: '${name}-container-registry'
26+
scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup()
2127
params: {
2228
name: containerRegistryName
2329
location: location
30+
adminUserEnabled: containerRegistryAdminUserEnabled
2431
tags: tags
2532
}
2633
}
2734

35+
output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain
2836
output environmentName string = containerAppsEnvironment.outputs.name
37+
output environmentId string = containerAppsEnvironment.outputs.id
38+
2939
output registryLoginServer string = containerRegistry.outputs.loginServer
3040
output registryName string = containerRegistry.outputs.name

0 commit comments

Comments
 (0)