azure-pipelines.yml•13.5 kB
# Azure DevOps CI/CD Pipeline for Fabric MCP Agent
# Builds Docker image and deploys to Azure Container Apps with Key Vault integration
trigger:
branches:
include:
- main
- dev
paths:
exclude:
- README.md
- docs/
- .gitignore
variables:
- name: imageRepository
value: 'fabric-mcp-agent'
- name: dockerfilePath
value: '$(Build.SourcesDirectory)/Dockerfile'
- name: tag
value: '$(Build.BuildId)'
- name: resourceGroup
value: 'M3-RG-ALZ-DWHS-ALYTICS-D-1'
- name: containerAppName
value: 'fabric-mcp-agent'
- name: keyVaultName
value: 'itapackeyvault'
- name: keyVaultUrl
value: 'https://itapackeyvault.vault.azure.net/'
- group: ServicePrincipal # ✅ variable group now correctly inside variables list
stages:
- stage: Build
displayName: Build and Push Docker Image (ACR Tasks)
jobs:
- job: Build
displayName: Build
pool:
vmImage: ubuntu-latest
steps:
- task: AzureCLI@2
displayName: Build & push image with ACR Tasks
inputs:
azureSubscription: 'Azure-ServiceConnection' # ARM service connection
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
REGISTRY_NAME="itapacdataacr"
REPO_NAME="$(imageRepository)"
TAG="$(tag)"
IMAGE_FQN="${REGISTRY_NAME}.azurecr.io/${REPO_NAME}:${TAG}"
echo "🔧 Using ACR Tasks to build ${IMAGE_FQN}"
az acr build \
--registry "${REGISTRY_NAME}" \
--image "${REPO_NAME}:${TAG}" \
--file "$(dockerfilePath)" \
"$(Build.SourcesDirectory)"
echo "🔁 Tagging image as :latest"
az acr repository delete --name "${REGISTRY_NAME}" --image "${REPO_NAME}:latest" --yes >/dev/null 2>&1 || true
az acr import \
--name "${REGISTRY_NAME}" \
--source "${REGISTRY_NAME}.azurecr.io/${REPO_NAME}:${TAG}" \
--image "${REPO_NAME}:latest" \
--registry "${REGISTRY_NAME}"
echo "✅ Build & push complete: ${IMAGE_FQN}"
- stage: Deploy
displayName: Deploy to Azure Container Apps
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: Deploy
pool:
vmImage: ubuntu-latest
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: Login with Service Principal
inputs:
azureSubscription: 'Azure-ServiceConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az login --service-principal \
--username "$(AZURE_CLIENT_ID)" \
--password "$(AZURE_SECRET)" \
--tenant "$(AZURE_TENANT_ID)"
echo "✅ Authenticated with Service Principal"
- task: AzureCLI@2
displayName: Deploy to Azure Container Apps
inputs:
azureSubscription: 'Azure-ServiceConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
echo "🚀 Starting Azure Container Apps deployment..."
FULL_IMAGE_NAME="itapacdataacr.azurecr.io/$(imageRepository):latest"
LOCATION="southeastasia"
ENVIRONMENT_NAME="$(containerAppName)-env"
ACR_SERVER="itapacdataacr.azurecr.io"
ACR_USERNAME="itapacdataacr"
ACR_PASSWORD=$(az keyvault secret show \
--vault-name "$(keyVaultName)" \
--name "itapacdataacr" \
--query "value" -o tsv)
echo "🔑 Retrieved ACR credentials from Key Vault"
echo "Image: $FULL_IMAGE_NAME"
echo "Resource Group: $(resourceGroup)"
echo "Container App: $(containerAppName)"
echo "Key Vault: $(keyVaultUrl)"
az extension add --name containerapp --upgrade --yes
az provider register --namespace Microsoft.App --wait
az provider register --namespace Microsoft.OperationalInsights --wait
if ! az keyvault show --name "$(keyVaultName)" --resource-group "$(resourceGroup)" &>/dev/null; then
echo "❌ Key Vault $(keyVaultName) not found in resource group $(resourceGroup)"
exit 1
fi
echo "✅ Key Vault $(keyVaultName) found"
# Check environment status and handle failed states
ENV_STATE=""
if az containerapp env show --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" &>/dev/null; then
ENV_STATE=$(az containerapp env show --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" --query "properties.provisioningState" -o tsv)
echo "Existing environment state: $ENV_STATE"
if [[ "$ENV_STATE" == "ScheduledForDelete" || "$ENV_STATE" == "Failed" ]]; then
echo "Environment in bad state ($ENV_STATE), deleting..."
az containerapp env delete --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" --yes
# Wait for deletion to complete
echo "Waiting for environment deletion..."
for i in {1..20}; do
if ! az containerapp env show --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" &>/dev/null; then
echo "✅ Environment deleted successfully"
break
fi
echo "Waiting for deletion... ($i/20)"
sleep 15
done
ENV_STATE=""
fi
fi
if [[ "$ENV_STATE" != "Succeeded" ]]; then
echo "Creating Container Apps environment..."
az containerapp env create \
--name "$ENVIRONMENT_NAME" \
--resource-group "$(resourceGroup)" \
--location "$LOCATION"
echo "Waiting for environment to be fully provisioned..."
# Wait for environment to be ready
for i in {1..30}; do
if az containerapp env show --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" --query "properties.provisioningState" -o tsv | grep -q "Succeeded"; then
echo "✅ Environment provisioned successfully"
break
fi
echo "Waiting for environment... ($i/30)"
sleep 10
done
# Final check
ENV_STATE=$(az containerapp env show --name "$ENVIRONMENT_NAME" --resource-group "$(resourceGroup)" --query "properties.provisioningState" -o tsv)
if [ "$ENV_STATE" != "Succeeded" ]; then
echo "❌ Environment provisioning failed. State: $ENV_STATE"
exit 1
fi
fi
# First, create/update the Container App
if az containerapp show --name "$(containerAppName)" --resource-group "$(resourceGroup)" &>/dev/null; then
echo "Updating existing Container App..."
az containerapp update \
--name "$(containerAppName)" \
--resource-group "$(resourceGroup)" \
--image "$FULL_IMAGE_NAME" \
--registry-server "$ACR_SERVER" \
--registry-username "$ACR_USERNAME" \
--registry-password "$ACR_PASSWORD"
else
echo "Creating new Container App..."
az containerapp create \
--name "$(containerAppName)" \
--resource-group "$(resourceGroup)" \
--environment "$ENVIRONMENT_NAME" \
--image "$FULL_IMAGE_NAME" \
--registry-server "$ACR_SERVER" \
--registry-username "$ACR_USERNAME" \
--registry-password "$ACR_PASSWORD" \
--target-port 8000 \
--ingress external \
--min-replicas 1 \
--max-replicas 3 \
--cpu 1.0 \
--memory 2.0Gi
fi
# Verify Container App was created successfully
if ! az containerapp show --name "$(containerAppName)" --resource-group "$(resourceGroup)" &>/dev/null; then
echo "❌ Container App creation failed"
exit 1
fi
echo "✅ Container App created/updated successfully"
echo "🔐 Configuring secrets from Key Vault using Service Principal..."
# Retrieve secrets directly from Key Vault using Service Principal access
AZURE_OPENAI_KEY=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azureopenaikey" --query "value" -o tsv)
AZURE_OPENAI_ENDPOINT=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azureopenaiendpoint" --query "value" -o tsv)
AZURE_OPENAI_DEPLOYMENT=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azureopenaideployment" --query "value" -o tsv)
AZURE_CLIENT_ID=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azureclientid" --query "value" -o tsv)
AZURE_CLIENT_SECRET=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azureclientsecret" --query "value" -o tsv)
AZURE_TENANT_ID=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "azuretenantid" --query "value" -o tsv)
FABRIC_SQL_SERVER=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "fabricsqlserver" --query "value" -o tsv)
FABRIC_SQL_DATABASE=$(az keyvault secret show --vault-name "$(keyVaultName)" --name "fabricsqldatabase" --query "value" -o tsv)
echo "✅ Retrieved all secrets from Key Vault"
# Set secrets as static values in Container App
az containerapp secret set \
--name "$(containerAppName)" \
--resource-group "$(resourceGroup)" \
--secrets \
azure-openai-key="$AZURE_OPENAI_KEY" \
azure-openai-endpoint="$AZURE_OPENAI_ENDPOINT" \
azure-openai-deployment="$AZURE_OPENAI_DEPLOYMENT" \
azure-client-id="$AZURE_CLIENT_ID" \
azure-client-secret="$AZURE_CLIENT_SECRET" \
azure-tenant-id="$AZURE_TENANT_ID" \
fabric-sql-server="$FABRIC_SQL_SERVER" \
fabric-sql-database="$FABRIC_SQL_DATABASE"
echo "✅ Secrets configured successfully"
# Set environment variables to reference the secrets (no KEY_VAULT_URL to force env var mode)
az containerapp update \
--name "$(containerAppName)" \
--resource-group "$(resourceGroup)" \
--set-env-vars \
AZURE_OPENAI_KEY="secretref:azure-openai-key" \
AZURE_OPENAI_ENDPOINT="secretref:azure-openai-endpoint" \
AZURE_OPENAI_DEPLOYMENT="secretref:azure-openai-deployment" \
AZURE_CLIENT_ID="secretref:azure-client-id" \
AZURE_CLIENT_SECRET="secretref:azure-client-secret" \
AZURE_TENANT_ID="secretref:azure-tenant-id" \
FABRIC_SQL_SERVER="secretref:fabric-sql-server" \
FABRIC_SQL_DATABASE="secretref:fabric-sql-database"
APP_URL=$(az containerapp show --name "$(containerAppName)" --resource-group "$(resourceGroup)" --query "properties.configuration.ingress.fqdn" -o tsv)
echo "✅ Deployment complete!"
echo "App URL: https://$APP_URL"
- task: AzureCLI@2
displayName: Test Deployment
inputs:
azureSubscription: 'Azure-ServiceConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
echo "🧪 Testing deployment..."
APP_URL=$(az containerapp show --name "$(containerAppName)" --resource-group "$(resourceGroup)" --query "properties.configuration.ingress.fqdn" -o tsv)
echo "Waiting for app to be ready..."
sleep 30
if curl -f "https://$APP_URL/list_tools" > /dev/null 2>&1; then
echo "✅ Health check passed"
echo "🌐 App URL: https://$APP_URL"
echo "🔗 Test endpoints:"
echo " - https://$APP_URL/list_tools"
echo " - https://$APP_URL"
else
echo "❌ Health check failed"
echo "Check Container App logs for details"
fi