Skip to content

Add app config and related feature flag capabilities #87

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

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7af05c8
make deployable
aprilk-ms Mar 31, 2025
b399a51
minimal updates
aprilk-ms Apr 10, 2025
0f456b4
Merge branch 'main' into aprilk/add-exp
aprilk-ms Apr 10, 2025
bb08f51
remove vscode settings
aprilk-ms Apr 10, 2025
371b4c3
add to .gitignore
aprilk-ms Apr 10, 2025
c2b3b63
Work in progress
aprilk-ms Apr 10, 2025
19abd4c
Configure Azure Developer Pipeline
aprilk-ms Apr 10, 2025
ba41c7c
add empty feature flag file
aprilk-ms Apr 10, 2025
c96b5f3
Update workflow name
aprilk-ms Apr 10, 2025
3fe9f12
start a feature flag
aprilk-ms Apr 10, 2025
e4e1de1
minor updates
aprilk-ms Apr 13, 2025
f8bb98c
Add variant to message title
aprilk-ms Apr 14, 2025
0616a36
Added new thread button
aprilk-ms Apr 15, 2025
7fe4ce1
update icon and use thread id for randomization
aprilk-ms Apr 15, 2025
cd2e117
Some cleanup
aprilk-ms Apr 15, 2025
bc4981b
clean up for PR
aprilk-ms Apr 15, 2025
7466a59
Minor fixes
aprilk-ms Apr 15, 2025
dc5f9ec
minor fixes
aprilk-ms Apr 15, 2025
7f3c217
Configure Azure Developer Pipeline
aprilk-ms Apr 18, 2025
8e10baa
update deploy workflow
aprilk-ms Apr 18, 2025
968db30
Update title
aprilk-ms Apr 18, 2025
b34f52c
back to short name
aprilk-ms Apr 18, 2025
4d8c058
fixed bicep
aprilk-ms Apr 18, 2025
c880263
update feature flags
aprilk-ms Apr 18, 2025
d78639d
minor fixes to app config initialize
aprilk-ms Apr 18, 2025
f32ea57
fix bicep and address some feedbacks
aprilk-ms Apr 18, 2025
27d3a59
addressed feedback and minor updates
aprilk-ms Apr 18, 2025
94d9ffe
update
aprilk-ms Apr 18, 2025
5c847fd
update to async
aprilk-ms Apr 18, 2025
dc4e590
Merge branch 'main' into aprilk/add-exp
aprilk-ms Apr 29, 2025
027181d
update with new UX
aprilk-ms Apr 29, 2025
a989dbc
Merge with new UX updates
aprilk-ms Apr 29, 2025
5d26938
Revert registry changes
aprilk-ms Apr 29, 2025
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
7 changes: 7 additions & 0 deletions .config/feature-flags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schemaVersion": "2.0.0",
"feature_management": {
"feature_flags": [
]
}
}
40 changes: 40 additions & 0 deletions .config/feature-flags.json.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"schemaVersion": "2.0.0",
"feature_management": {
"feature_flags": [
{
"id": "my-agent",
"enabled": true,
"variants": [
{
"name": "agent_v1",
"configuration_value": "<agent-id-1-placeholder>"
},
{
"name": "agent_v2",
"configuration_value": "<agent-id-2-placeholder>"
}
],
"allocation": {
"percentile": [
{
"variant": "agent_v1",
"from": 0,
"to": 50
},
{
"variant": "agent_v2",
"from": 50,
"to": 100
}
],
"default_when_enabled": "agent_v1",
"default_when_disabled": "agent_v1"
},
"telemetry": {
"enabled": true
}
}
]
}
}
27 changes: 27 additions & 0 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ permissions:

jobs:
build:
name: Deploy
runs-on: ubuntu-latest
if: ${{ vars.AZURE_CLIENT_ID != '' }}
env:
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
Expand Down Expand Up @@ -74,3 +76,28 @@ jobs:

- name: Deploy Application
run: azd deploy --no-prompt

update-experiments:
name: Update Experiments
needs: build
runs-on: ubuntu-latest
env:
APP_CONFIGURATION_FILE: .config/feature-flags.json
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Azure login using Federated Credentials
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true

- name: Deploy App Config feature flags
uses: azure/app-configuration-deploy-feature-flags@v1-beta
with:
path: ${{ env.APP_CONFIGURATION_FILE }}
app-configuration-endpoint: ${{ vars.APP_CONFIGURATION_ENDPOINT }}
strict: false
30 changes: 30 additions & 0 deletions .github/workflows/experiment-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Validate Experiments
on:
workflow_dispatch:

# GitHub Actions workflow to deploy to Azure using azd
# To configure required secrets for connecting to Azure, simply run `azd pipeline config`

# Set up permissions for deploying with secretless Azure federated credentials
# https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication
permissions:
id-token: write
contents: read

env:
APP_CONFIGURATION_FILE: .config/feature-flags.json

jobs:
validate-feature-flags:
name: Validate Feature Flags
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate App Config feature flags
uses: azure/app-configuration-deploy-feature-flags@v1-beta
with:
path: ${{ env.APP_CONFIGURATION_FILE }}
operation: validate

1 change: 1 addition & 0 deletions .github/workflows/template-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ jobs:
AZURE_AI_EMBED_MODEL_FORMAT: ${{ vars.AZURE_AI_EMBED_MODEL_FORMAT }}
AZURE_AI_EMBED_MODEL_VERSION: ${{ vars.AZURE_AI_EMBED_MODEL_VERSION }}
AZURE_EXISTING_AIPROJECT_CONNECTION_STRING: ${{ vars.AZURE_EXISTING_AIPROJECT_CONNECTION_STRING }}
APP_CONFIGURATION_ENDPOINT: ${{ vars.APP_CONFIGURATION_ENDPOINT }}
- name: print result
run: cat ${{ steps.validation.outputs.resultFile }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ ENV/
env.bak/
venv.bak/
.azure
.vscode/
3 changes: 2 additions & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ pipeline:
- AZURE_AI_EMBED_MODEL_VERSION
- AZURE_AI_EMBED_DIMENSIONS
- AZURE_AI_SEARCH_INDEX_NAME
- AZURE_EXISTING_AIPROJECT_CONNECTION_STRING
- AZURE_EXISTING_AIPROJECT_CONNECTION_STRING
- APP_CONFIGURATION_ENDPOINT
5 changes: 5 additions & 0 deletions infra/api.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ param searchServiceEndpoint string
param agentName string
param agentID string
param projectName string
param appConfigurationEndpoint string

resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: identityName
Expand Down Expand Up @@ -65,6 +66,10 @@ var env = [
name: 'AZURE_AI_SEARCH_ENDPOINT'
value: searchServiceEndpoint
}
{
name: 'APP_CONFIGURATION_ENDPOINT'
value: appConfigurationEndpoint
}
]


Expand Down
34 changes: 23 additions & 11 deletions infra/core/config/configstore.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ param name string
@description('The Azure region/location for the Azure App Configuration store')
param location string = resourceGroup().location

@description('The SKU for the Azure App Configuration store')
param sku string

@description('Custom tags to apply to the Azure App Configuration store')
param tags object = {}

Expand All @@ -15,16 +18,29 @@ param keyValueNames array = []
@description('Specifies the values of the key-value resources.')
param keyValueValues array = []

@description('The principal ID to grant access to the Azure App Configuration store')
param principalId string
@description('The Application Insights ID linked to the Azure App Configuration store')
param appInsightsName string

resource configStore 'Microsoft.AppConfiguration/configurationStores@2023-03-01' = {
resource configStore 'Microsoft.AppConfiguration/configurationStores@2023-09-01-preview' = {
name: name
location: location
sku: {
name: 'standard'
name: sku
}
tags: tags
properties: {
encryption: {}
disableLocalAuth: true
enablePurgeProtection: false
experimentation:{}
dataPlaneProxy:{
authenticationMode: 'Pass-through'
privateLinkDelegation: 'Disabled'
}
telemetry: {
resourceId: appInsights.id
}
}
}

resource configStoreKeyValue 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = [for (item, i) in keyValueNames: {
Expand All @@ -36,13 +52,9 @@ resource configStoreKeyValue 'Microsoft.AppConfiguration/configurationStores/key
}
}]

module configStoreAccess '../security/configstore-access.bicep' = {
name: 'app-configuration-access'
params: {
configStoreName: name
principalId: principalId
}
dependsOn: [configStore]
resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' existing = {
name: appInsightsName
}

output endpoint string = configStore.properties.endpoint
output name string = configStore.name
21 changes: 0 additions & 21 deletions infra/core/security/configstore-access.bicep

This file was deleted.

48 changes: 47 additions & 1 deletion infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,25 @@ param embedDeploymentSku string = 'Standard'
// https://learn.microsoft.com/azure/ai-services/openai/quotas-limits
param embedDeploymentCapacity int = 30

@description('Whether to use Azure Application Insights')
param useApplicationInsights bool = true

@description('Whether to use Azure App Configuration')
param useAppConfiguration bool = true

@description('Sku for the App Configuration')
// Recommend to upgrade for production use. See pricing plan:
// https://azure.microsoft.com/en-us/pricing/details/app-configuration/
param appConfigurationSku string = 'free'

@description('Do we want to use the Azure AI Search')
param useSearchService bool = false

@description('Id of the user or app to assign application roles')
param principalId string = ''

@description('Random seed to be used during generation of new resources suffixes.')
param seed string = newGuid()
param seed string = '' //newGuid() // why do we need this?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you remove newGUID, it might break AI Search resource creation.


var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location, seed))
Expand Down Expand Up @@ -257,6 +270,19 @@ module containerApps 'core/host/container-apps.bicep' = {
}
}

// App Configuration
module configStore 'core/config/configstore.bicep' = if (useApplicationInsights && useAppConfiguration) {
name: 'config-store'
scope: rg
params: {
location: location
sku: appConfigurationSku
name: '${abbrs.appConfigurationStores}${resourceToken}'
tags: tags
appInsightsName: ai.outputs.applicationInsightsName
}
}

// API app
module api 'api.bicep' = {
name: 'api'
Expand All @@ -277,6 +303,7 @@ module api 'api.bicep' = {
agentName: agentName
agentID: agentID
projectName: projectName
appConfigurationEndpoint: configStore.outputs.endpoint
}
}

Expand Down Expand Up @@ -388,6 +415,24 @@ module backendRoleAzureAIDeveloperRG 'core/security/role.bicep' = {
}
}

module userRoleConfigStoreDataOwner 'core/security/role.bicep' = {
name: 'user-role-config-store-data-owner'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@howieleung I also noticed all the other userRole assignment above are using app service principal and not the caller. Is it intended?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a while ago, I also had principalId as param at the main. But when I try to deploy from Portal UI, the service principal has role issues.

scope: rg
params: {
principalId: principalId
roleDefinitionId: '5ae67dd6-50cb-40e7-96ff-dc2bfa4b606b'
}
}

module backendRoleConfigStoreDataOwner 'core/security/role.bicep' = {
name: 'backend-role-config-store-data-reader'
scope: rg
params: {
principalId: api.outputs.SERVICE_API_IDENTITY_PRINCIPAL_ID
roleDefinitionId: '516239f1-63e1-4d78-a4de-a74fb236a071'
}
}

output AZURE_RESOURCE_GROUP string = rg.name

// Outputs required for local development server
Expand All @@ -401,6 +446,7 @@ output AZURE_AI_SEARCH_ENDPOINT string = searchServiceEndpoint
output AZURE_AI_EMBED_DIMENSIONS string = embeddingDeploymentDimensions
output AZURE_AI_AGENT_NAME string = agentName
output AZURE_AI_AGENT_ID string = agentID
output APP_CONFIGURATION_ENDPOINT string = configStore.outputs.endpoint

// Outputs required by azd for ACA
output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName
Expand Down
6 changes: 6 additions & 0 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
"useSearchService": {
"value": "${USE_AZURE_AI_SEARCH_SERVICE=false}"
},
"useAppConfiguration": {
"value": "${USE_APP_CONFIGURATION=true}"
},
"appConfigurationSku": {
"value": "${APP_CONFIGURATION_SKU=free}"
},
"agentName": {
"value": "${AZURE_AI_AGENT_NAME=agent-template-assistant}"
},
Expand Down
2 changes: 2 additions & 0 deletions scripts/write_env.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ $azureAIEmbedDimensions = azd env get-value AZURE_AI_EMBED_DIMENSIONS
$azureAISearchIndexName = azd env get-value AZURE_AI_SEARCH_INDEX_NAME
$azureAISearchEndpoint = azd env get-value AZURE_AI_SEARCH_ENDPOINT
$serviceAPIUri = azd env get-value SERVICE_API_URI
$appConfigurationEndpoint = azd env get-value APP_CONFIGURATION_ENDPOINT

Add-Content -Path $envFilePath -Value "AZURE_AIPROJECT_CONNECTION_STRING=$azureAiProjectConnectionString"
Add-Content -Path $envFilePath -Value "AZURE_AI_AGENT_DEPLOYMENT_NAME=$azureAiagentDeploymentName"
Expand All @@ -28,6 +29,7 @@ Add-Content -Path $envFilePath -Value "AZURE_AI_SEARCH_INDEX_NAME=$azureAISearch
Add-Content -Path $envFilePath -Value "AZURE_AI_SEARCH_ENDPOINT=$azureAISearchEndpoint"
Add-Content -Path $envFilePath -Value "AZURE_AI_AGENT_NAME=$azureAiAgentName"
Add-Content -Path $envFilePath -Value "AZURE_TENANT_ID=$azureTenantId"
Add-Content -Path $envFilePath -Value "APP_CONFIGURATION_ENDPOINT=$appConfigurationEndpoint"

Write-Host "Web app URL:"
Write-Host $serviceAPIUri -ForegroundColor Cyan
1 change: 1 addition & 0 deletions scripts/write_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ echo "AZURE_AI_SEARCH_INDEX_NAME=$(azd env get-value AZURE_AI_SEARCH_INDEX_NAME)
echo "AZURE_AI_SEARCH_ENDPOINT=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)" >> $ENV_FILE_PATH
echo "AZURE_AI_AGENT_NAME=$(azd env get-value AZURE_AI_AGENT_NAME)" >> $ENV_FILE_PATH
echo "AZURE_TENANT_ID=$(azd env get-value AZURE_TENANT_ID)" >> $ENV_FILE_PATH
echo "APP_CONFIGURATION_ENDPOINT=$(azd env get-value APP_CONFIGURATION_ENDPOINT)" >> $ENV_FILE_PATH

echo "Web app URL:"
echo -e "\033[0;36m $(azd env get-value SERVICE_API_URI)"
Expand Down
3 changes: 3 additions & 0 deletions src/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ AZURE_AI_SEARCH_INDEX_NAME="" # required for index search. Example: "index_samp
# Highly recommended. Example: "asst_AbCdEfGhIjKlMnOpQrStUvWxYz". If not specified, the agent name will be used to find the agent ID. Agent ID can be found by following https://learn.microsoft.com/en-us/azure/ai-services/agents/quickstart?pivots=ai-foundry-portal
AZURE_AI_AGENT_ID=""

# Recommended. Enable tracing for debugging and testing feature flag capabilities.
ENABLE_AZURE_MONITOR_TRACING=false
AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=false
Loading