Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ Once you've opened the project in [Codespaces](#github-codespaces) or in [Dev Co

7. (Optional) You can use a local development server to test app changes locally. To do so, follow the steps in [local deployment server](#local-development-server) after your app is deployed.

8. (Optional) Follow this [tutorial](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-tutorial-quick-task) to build your changes into a Docker image and deploy to Azure Container App.
8. (Optional) To redeploy, run `azd deploy`. This will cause new docker image rebuilt, push to Azure Container Registry, and a new revision in Azure Container App with a new docker image.

## Tracing and Monitoring

Expand Down
22 changes: 20 additions & 2 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

name: azd-get-started-with-ai-agents
metadata:
template: [email protected].0
template: [email protected].1

hooks:
preup:
Expand All @@ -29,7 +29,25 @@ hooks:
run: chmod u+r+x ./scripts/write_env.sh; ./scripts/write_env.sh;
continueOnError: true
interactive: true

postdeploy:
windows:
shell: pwsh
run: ./scripts/postdeploy.ps1
continueOnError: true
interactive: true
posix:
shell: sh
run: chmod u+r+x ./scripts/postdeploy.sh; ./scripts/postdeploy.sh;
continueOnError: true
interactive: true
services:
api_and_frontend:
project: ./src
language: py
host: containerapp
docker:
image: api_and_frontend
remoteBuild: true
pipeline:
variables:
- AZURE_RESOURCE_GROUP
Expand Down
6 changes: 3 additions & 3 deletions infra/api.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ param name string
param location string = resourceGroup().location
param tags object = {}

param containerRegistryName string
param identityName string
param containerAppsEnvironmentName string
param azureExistingAIProjectResourceId string
Expand All @@ -13,7 +14,6 @@ param embeddingDeploymentDimensions string
param searchServiceEndpoint string
param agentName string
param agentID string
param projectName string
param enableAzureMonitorTracing bool
param azureTracingGenAIContentRecordingEnabled bool
param projectEndpoint string
Expand Down Expand Up @@ -89,12 +89,12 @@ module app 'core/host/container-app-upsert.bicep' = {
params: {
name: name
location: location
tags: tags
tags: union(tags, { 'azd-service-name': 'api_and_frontend' })
identityName: apiIdentity.name
containerRegistryName: containerRegistryName
containerAppsEnvironmentName: containerAppsEnvironmentName
targetPort: 50505
env: env
projectName: projectName
}
}

Expand Down
19 changes: 17 additions & 2 deletions infra/core/host/container-app-upsert.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ param containerMinReplicas int = 1
@description('The name of the container')
param containerName string = 'main'

@description('The name of the container registry')
param containerRegistryName string

@description('Hostname suffix for container registry. Set when deploying to sovereign clouds')
param containerRegistryHostSuffix string = 'azurecr.io'

@allowed([ 'http', 'grpc' ])
@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC')
param daprAppProtocol string = 'http'
Expand All @@ -43,6 +49,9 @@ param identityType string = 'None'
@description('The name of the user-assigned identity')
param identityName string = ''

@description('The name of the container image')
param imageName string = ''

@description('The secrets required for the container')
@secure()
param secrets object = {}
Expand All @@ -59,8 +68,12 @@ param serviceBinds array = []
@description('The target port for the container')
param targetPort int = 80

param projectName string
@description('Specifies if the resource already exists')
param exists bool = false

resource existingApp 'Microsoft.App/containerApps@2023-05-02-preview' existing = if (exists) {
name: name
}

module app 'container-app.bicep' = {
name: '${deployment().name}-update'
Expand All @@ -73,6 +86,8 @@ module app 'container-app.bicep' = {
ingressEnabled: ingressEnabled
containerName: containerName
containerAppsEnvironmentName: containerAppsEnvironmentName
containerRegistryName: containerRegistryName
containerRegistryHostSuffix: containerRegistryHostSuffix
containerCpuCoreCount: containerCpuCoreCount
containerMemory: containerMemory
containerMinReplicas: containerMinReplicas
Expand All @@ -83,9 +98,9 @@ module app 'container-app.bicep' = {
secrets: secrets
external: external
env: env
imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : ''
targetPort: targetPort
serviceBinds: serviceBinds
dependOn: projectName
}
}

Expand Down
28 changes: 26 additions & 2 deletions infra/core/host/container-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ param containerMinReplicas int = 1
@description('The name of the container')
param containerName string = 'main'

@description('The name of the container registry')
param containerRegistryName string = ''

@description('Hostname suffix for container registry. Set when deploying to sovereign clouds')
param containerRegistryHostSuffix string = 'azurecr.io'

@description('The protocol used by Dapr to connect to the app, e.g., http or grpc')
@allowed([ 'http', 'grpc' ])
param daprAppProtocol string = 'http'
Expand All @@ -48,6 +54,8 @@ param identityName string = ''
@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ])
param identityType string = 'None'

@description('The name of the container image')
param imageName string = ''

@description('Specifies if Ingress is enabled for the container app')
param ingressEnabled bool = true
Expand All @@ -73,9 +81,20 @@ resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-
name: identityName
}

// Private registry support requires both an ACR name and a User Assigned managed identity
var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName)

// Automatically set to `UserAssigned` when an `identityName` has been set
var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType

module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) {
name: '${deployment().name}-registry-access'
params: {
containerRegistryName: containerRegistryName
principalId: usePrivateRegistry ? userIdentity.properties.principalId : ''
}
}

resource app 'Microsoft.App/containerApps@2023-05-02-preview' = {
name: name
location: location
Expand Down Expand Up @@ -112,13 +131,18 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = {
value: secret.value
}]
service: !empty(serviceType) ? { type: serviceType } : null
registries: []
registries: usePrivateRegistry ? [
{
server: '${containerRegistryName}.${containerRegistryHostSuffix}'
identity: userIdentity.id
}
] : []
}
template: {
serviceBinds: !empty(serviceBinds) ? serviceBinds : null
containers: [
{
image: 'azdtemplate.azurecr.io/get-start-with-ai-agents:latest'
image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
name: containerName
env: env
resources: {
Expand Down
17 changes: 16 additions & 1 deletion infra/core/host/container-apps.bicep
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
metadata description = 'Creates an Azure Container Apps environment.'
metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.'
param name string
param location string = resourceGroup().location
param tags object = {}

param containerAppsEnvironmentName string
param containerRegistryName string
param containerRegistryAdminUserEnabled bool = false
param logAnalyticsWorkspaceName string
param applicationInsightsName string = ''

Expand All @@ -18,7 +20,20 @@ module containerAppsEnvironment 'container-apps-environment.bicep' = {
}
}

module containerRegistry 'container-registry.bicep' = {
name: '${name}-container-registry'
scope: resourceGroup()
params: {
name: containerRegistryName
location: location
adminUserEnabled: containerRegistryAdminUserEnabled
tags: tags
}
}

output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain
output environmentName string = containerAppsEnvironment.outputs.name
output environmentId string = containerAppsEnvironment.outputs.id

output registryLoginServer string = containerRegistry.outputs.loginServer
output registryName string = containerRegistry.outputs.name
137 changes: 137 additions & 0 deletions infra/core/host/container-registry.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
metadata description = 'Creates an Azure Container Registry.'
param name string
param location string = resourceGroup().location
param tags object = {}

@description('Indicates whether admin user is enabled')
param adminUserEnabled bool = false

@description('Indicates whether anonymous pull is enabled')
param anonymousPullEnabled bool = false

@description('Azure ad authentication as arm policy settings')
param azureADAuthenticationAsArmPolicy object = {
status: 'enabled'
}

@description('Indicates whether data endpoint is enabled')
param dataEndpointEnabled bool = false

@description('Encryption settings')
param encryption object = {
status: 'disabled'
}

@description('Export policy settings')
param exportPolicy object = {
status: 'enabled'
}

@description('Metadata search settings')
param metadataSearch string = 'Disabled'

@description('Options for bypassing network rules')
param networkRuleBypassOptions string = 'AzureServices'

@description('Public network access setting')
param publicNetworkAccess string = 'Enabled'

@description('Quarantine policy settings')
param quarantinePolicy object = {
status: 'disabled'
}

@description('Retention policy settings')
param retentionPolicy object = {
days: 7
status: 'disabled'
}

@description('Scope maps setting')
param scopeMaps array = []

@description('SKU settings')
param sku object = {
name: 'Basic'
}

@description('Soft delete policy settings')
param softDeletePolicy object = {
retentionDays: 7
status: 'disabled'
}

@description('Trust policy settings')
param trustPolicy object = {
type: 'Notary'
status: 'disabled'
}

@description('Zone redundancy setting')
param zoneRedundancy string = 'Disabled'

@description('The log analytics workspace ID used for logging and monitoring')
param workspaceId string = ''

// 2023-11-01-preview needed for metadataSearch
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = {
name: name
location: location
tags: tags
sku: sku
properties: {
adminUserEnabled: adminUserEnabled
anonymousPullEnabled: anonymousPullEnabled
dataEndpointEnabled: dataEndpointEnabled
encryption: encryption
metadataSearch: metadataSearch
networkRuleBypassOptions: networkRuleBypassOptions
policies:{
quarantinePolicy: quarantinePolicy
trustPolicy: trustPolicy
retentionPolicy: retentionPolicy
exportPolicy: exportPolicy
azureADAuthenticationAsArmPolicy: azureADAuthenticationAsArmPolicy
softDeletePolicy: softDeletePolicy
}
publicNetworkAccess: publicNetworkAccess
zoneRedundancy: zoneRedundancy
}

resource scopeMap 'scopeMaps' = [for scopeMap in scopeMaps: {
name: scopeMap.name
properties: scopeMap.properties
}]
}

// TODO: Update diagnostics to be its own module
// Blocking issue: https://github.com/Azure/bicep/issues/622
// Unable to pass in a `resource` scope or unable to use string interpolation in resource types
resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) {
name: 'registry-diagnostics'
scope: containerRegistry
properties: {
workspaceId: workspaceId
logs: [
{
category: 'ContainerRegistryRepositoryEvents'
enabled: true
}
{
category: 'ContainerRegistryLoginEvents'
enabled: true
}
]
metrics: [
{
category: 'AllMetrics'
enabled: true
timeGrain: 'PT1M'
}
]
}
}

output id string = containerRegistry.id
output loginServer string = containerRegistry.properties.loginServer
output name string = containerRegistry.name
Loading