Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Deployment script for DB Planet MCP Server
|
||||
# Usage: ./infra/deploy.sh --environment DEV|IAT|Prod [--image-tag TAG]
|
||||
|
||||
set -e
|
||||
|
||||
ENVIRONMENT=""
|
||||
IMAGE_TAG=""
|
||||
LOCATION="westeurope"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-e|--environment) ENVIRONMENT="$2"; shift 2 ;;
|
||||
-t|--image-tag) IMAGE_TAG="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 -e ENV [-t IMAGE_TAG]"
|
||||
echo " -e, --environment DEV, IAT, or Prod"
|
||||
echo " -t, --image-tag Container image tag (optional, uses param file default)"
|
||||
exit 0 ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! "$ENVIRONMENT" =~ ^(DEV|IAT|Prod)$ ]]; then
|
||||
echo "❌ Environment must be DEV, IAT, or Prod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENV_LOWER=$(echo "$ENVIRONMENT" | tr '[:upper:]' '[:lower:]')
|
||||
PARAM_FILE="infra/parameters/${ENV_LOWER}.bicepparam"
|
||||
|
||||
if [[ ! -f "$PARAM_FILE" ]]; then
|
||||
echo "❌ Parameter file not found: $PARAM_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Deploying DB Planet MCP Server — $ENVIRONMENT"
|
||||
echo " Parameter file: $PARAM_FILE"
|
||||
[[ -n "$IMAGE_TAG" ]] && echo " Image tag: $IMAGE_TAG"
|
||||
echo ""
|
||||
|
||||
# Step 1: Deploy infrastructure (RG, Identity, Key Vault, App Insights, RBAC)
|
||||
DEPLOY_NAME="dbplanet-mcp-${ENV_LOWER}-$(date +%Y%m%d-%H%M%S)"
|
||||
echo "� Step 1: Deploying infrastructure..."
|
||||
|
||||
if ! az deployment sub create \
|
||||
--location "$LOCATION" \
|
||||
--parameters "$PARAM_FILE" \
|
||||
--name "$DEPLOY_NAME"; then
|
||||
echo "❌ Infrastructure deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Infrastructure deployed"
|
||||
echo ""
|
||||
|
||||
# Step 2: Deploy Container App
|
||||
echo "📦 Step 2: Deploying Container App..."
|
||||
|
||||
DEPLOY_ARGS=("--location" "$LOCATION" "--parameters" "$PARAM_FILE"
|
||||
"--parameters" "deployContainerApp=true"
|
||||
"--name" "${DEPLOY_NAME}-app")
|
||||
|
||||
[[ -n "$IMAGE_TAG" ]] && DEPLOY_ARGS+=("--parameters" "imageTag=$IMAGE_TAG")
|
||||
|
||||
if ! az deployment sub create "${DEPLOY_ARGS[@]}"; then
|
||||
echo "❌ Container App deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Container App deployed"
|
||||
echo ""
|
||||
|
||||
# Step 3: Verify
|
||||
echo "🔍 Step 3: Verifying deployment..."
|
||||
|
||||
CA_NAME=$(az deployment sub show --name "${DEPLOY_NAME}-app" \
|
||||
--query "properties.outputs.containerAppName.value" -o tsv)
|
||||
CA_FQDN=$(az deployment sub show --name "${DEPLOY_NAME}-app" \
|
||||
--query "properties.outputs.containerAppFqdn.value" -o tsv)
|
||||
|
||||
echo " Container App: $CA_NAME"
|
||||
echo " FQDN: $CA_FQDN"
|
||||
|
||||
# Wait for healthy revision
|
||||
RG_NAME="rg-dbplanet-mcp-${ENV_LOWER}"
|
||||
for i in $(seq 1 10); do
|
||||
STATE=$(az containerapp revision list --name "$CA_NAME" \
|
||||
--resource-group "$RG_NAME" \
|
||||
--query "[?properties.active].properties.runningState | [0]" -o tsv 2>/dev/null)
|
||||
if [[ "$STATE" == "Running" || "$STATE" == "RunningAtMaxScale" ]]; then
|
||||
echo " ✅ Revision is Running + Healthy"
|
||||
break
|
||||
fi
|
||||
echo " ⏳ Waiting for revision ($STATE)... ($i/10)"
|
||||
if [[ "$i" -eq 10 ]]; then
|
||||
echo " ❌ Revision did not reach Running state after 10 attempts"
|
||||
exit 1
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "🎉 Deployment complete!"
|
||||
echo " Health: https://${CA_FQDN}/health"
|
||||
@@ -0,0 +1,243 @@
|
||||
// DB Planet MCP Server Infrastructure
|
||||
// Deploys: Resource Group, Managed Identity, Key Vault,
|
||||
// Application Insights, Container App, Key Vault permissions
|
||||
|
||||
targetScope = 'subscription'
|
||||
|
||||
// ── Core parameters ──────────────────────────────────────────
|
||||
@description('Environment name')
|
||||
@allowed(['DEV', 'IAT', 'Prod'])
|
||||
param environment string
|
||||
|
||||
@description('Azure region')
|
||||
param location string = 'westeurope'
|
||||
|
||||
@description('Container image tag')
|
||||
param imageTag string = 'latest'
|
||||
|
||||
@description('Set to true on second deployment after infra + permissions are in place')
|
||||
param deployContainerApp bool = false
|
||||
|
||||
// ── Tags ─────────────────────────────────────────────────────
|
||||
@description('Tags applied to all resources')
|
||||
param tags object = {
|
||||
CostReference: 'H-450007-52-01'
|
||||
ReferenceID: 'A-113241'
|
||||
ReferenceName: 'DB Planet MCP Server'
|
||||
Environment: environment
|
||||
}
|
||||
|
||||
// ── Existing infrastructure references ───────────────────────
|
||||
@description('Central Key Vault name (shared secrets)')
|
||||
param centralKeyVaultName string
|
||||
|
||||
@description('Central Key Vault resource group')
|
||||
param centralKeyVaultResourceGroup string
|
||||
|
||||
@description('Container App Environment name')
|
||||
param containerAppEnvironmentName string
|
||||
|
||||
@description('Container App Environment resource group')
|
||||
param containerAppEnvironmentResourceGroup string
|
||||
|
||||
// ── Application configuration ────────────────────────────────
|
||||
@description('Artifactory server URL')
|
||||
param artifactoryServer string
|
||||
|
||||
@description('Registry username')
|
||||
param registryUsername string
|
||||
|
||||
@description('DB Planet base URL (staging vs production)')
|
||||
param dbplanetBaseUrl string
|
||||
|
||||
// ── Service Principal for Key Vault secrets management ───────
|
||||
#disable-next-line secure-secrets-in-params // Object ID, not a secret
|
||||
@description('Service Principal Object ID for Key Vault Secrets Officer role')
|
||||
param secretsOfficerPrincipalId string = ''
|
||||
|
||||
// ── Custom domain (optional) ─────────────────────────────────
|
||||
@description('Certificate name in Container App Environment')
|
||||
param certificateName string = ''
|
||||
|
||||
@description('DNS zone name for custom domain')
|
||||
param dnsZoneName string = ''
|
||||
|
||||
@description('DNS record name (subdomain)')
|
||||
param dnsRecordName string = ''
|
||||
|
||||
@description('Resource group containing the DNS zone')
|
||||
param dnsZoneResourceGroup string = 'Infrastructure'
|
||||
|
||||
// ── Derived values ───────────────────────────────────────────
|
||||
var resourceGroupName = 'rg-dbplanet-mcp-${toLower(environment)}'
|
||||
var resourceToken = toLower(uniqueString(subscription().id, resourceGroupName, location))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 1. Resource Group
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module rg 'modules/resource-group.bicep' = {
|
||||
name: 'deploy-rg'
|
||||
params: {
|
||||
resourceGroupName: resourceGroupName
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 2. Managed Identity
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module identity 'modules/managed-identity.bicep' = {
|
||||
name: 'deploy-identity'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
dependsOn: [rg]
|
||||
params: {
|
||||
environment: environment
|
||||
location: location
|
||||
tags: tags
|
||||
resourceToken: resourceToken
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 3. Key Vault (app-specific, public access)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module keyVault 'modules/keyvault.bicep' = {
|
||||
name: 'deploy-keyvault'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
dependsOn: [rg]
|
||||
params: {
|
||||
environment: environment
|
||||
location: location
|
||||
tags: tags
|
||||
resourceToken: resourceToken
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 4. Application Insights + Log Analytics
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module appInsights 'modules/application-insights.bicep' = {
|
||||
name: 'deploy-app-insights'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
params: {
|
||||
environment: environment
|
||||
location: location
|
||||
tags: tags
|
||||
resourceToken: resourceToken
|
||||
keyVaultName: keyVault.outputs.keyVaultName
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 5. Container App Certificate (existing, optional)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module certificate 'modules/container-app-certificate.bicep' = if (!empty(certificateName)) {
|
||||
name: 'deploy-certificate'
|
||||
scope: resourceGroup(containerAppEnvironmentResourceGroup)
|
||||
params: {
|
||||
containerAppEnvironmentName: containerAppEnvironmentName
|
||||
certificateName: certificateName
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 6. Container App
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var customDomainName = !empty(dnsRecordName) && !empty(dnsZoneName) ? '${dnsRecordName}.${dnsZoneName}' : ''
|
||||
|
||||
module containerApp 'modules/container-app.bicep' = if (deployContainerApp) {
|
||||
name: 'deploy-container-app'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
dependsOn: [rg]
|
||||
params: {
|
||||
environment: environment
|
||||
location: location
|
||||
tags: tags
|
||||
resourceToken: resourceToken
|
||||
centralKeyVaultName: centralKeyVaultName
|
||||
centralKeyVaultResourceGroup: centralKeyVaultResourceGroup
|
||||
appKeyVaultUri: keyVault.outputs.keyVaultUri
|
||||
containerAppEnvironmentName: containerAppEnvironmentName
|
||||
containerAppEnvironmentResourceGroup: containerAppEnvironmentResourceGroup
|
||||
artifactoryServer: artifactoryServer
|
||||
registryUsername: registryUsername
|
||||
managedIdentityId: identity.outputs.managedIdentityId
|
||||
imageTag: imageTag
|
||||
dbplanetBaseUrl: dbplanetBaseUrl
|
||||
customDomainName: !empty(certificateName) ? customDomainName : ''
|
||||
containerAppCertificateId: !empty(certificateName) ? certificate.outputs.certificateId! : ''
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 7. DNS Record
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
module dnsRecord 'modules/dns-record.bicep' = if (deployContainerApp && !empty(dnsRecordName)) {
|
||||
name: 'deploy-dns-record'
|
||||
scope: resourceGroup(dnsZoneResourceGroup)
|
||||
params: {
|
||||
privateDnsZoneName: dnsZoneName
|
||||
aRecordName: dnsRecordName
|
||||
ipv4Address: containerApp.outputs.containerAppEnvironmentStaticIp!
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 8. Key Vault Permissions
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
// 6a. Managed Identity → central Key Vault (read registry secrets)
|
||||
module centralKvPermissions 'modules/key-vault-permissions.bicep' = {
|
||||
name: 'deploy-central-kv-permissions'
|
||||
scope: resourceGroup(centralKeyVaultResourceGroup)
|
||||
params: {
|
||||
keyVaultName: centralKeyVaultName
|
||||
principalId: identity.outputs.managedIdentityPrincipalId
|
||||
roleName: 'Key Vault Secrets User'
|
||||
}
|
||||
}
|
||||
|
||||
// 6b. Managed Identity → app Key Vault (read app secrets)
|
||||
module appKvIdentityPermissions 'modules/key-vault-permissions.bicep' = {
|
||||
name: 'deploy-app-kv-identity-permissions'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
params: {
|
||||
keyVaultName: keyVault.outputs.keyVaultName
|
||||
principalId: identity.outputs.managedIdentityPrincipalId
|
||||
roleName: 'Key Vault Secrets User'
|
||||
}
|
||||
}
|
||||
|
||||
// 6c. Container App system identity → app Key Vault (read app secrets)
|
||||
module appKvContainerAppPermissions 'modules/key-vault-permissions.bicep' = if (deployContainerApp) {
|
||||
name: 'deploy-app-kv-ca-permissions'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
params: {
|
||||
keyVaultName: keyVault.outputs.keyVaultName
|
||||
principalId: containerApp.outputs.systemIdentityPrincipalId!
|
||||
roleName: 'Key Vault Secrets User'
|
||||
}
|
||||
}
|
||||
|
||||
// 6d. Service Principal → app Key Vault (read + write secrets for CI/CD)
|
||||
module appKvOfficerPermissions 'modules/key-vault-permissions.bicep' = if (!empty(secretsOfficerPrincipalId)) {
|
||||
name: 'deploy-app-kv-officer-permissions'
|
||||
scope: resourceGroup(resourceGroupName)
|
||||
params: {
|
||||
keyVaultName: keyVault.outputs.keyVaultName
|
||||
principalId: secretsOfficerPrincipalId
|
||||
roleName: 'Key Vault Secrets Officer'
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Outputs
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
output resourceGroupName string = rg.outputs.resourceGroupName
|
||||
output keyVaultName string = keyVault.outputs.keyVaultName
|
||||
output keyVaultUri string = keyVault.outputs.keyVaultUri
|
||||
output containerAppName string = deployContainerApp ? containerApp.outputs.containerAppName! : ''
|
||||
output containerAppFqdn string = deployContainerApp ? containerApp.outputs.containerAppFqdn! : ''
|
||||
output managedIdentityId string = identity.outputs.managedIdentityId
|
||||
output appInsightsName string = appInsights.outputs.applicationInsightsName
|
||||
@@ -0,0 +1,71 @@
|
||||
// Application Insights module
|
||||
|
||||
@description('The environment name (DEV, IAT, Prod)')
|
||||
param environment string
|
||||
|
||||
@description('The location for Application Insights')
|
||||
param location string
|
||||
|
||||
@description('Tags to apply to resources')
|
||||
param tags object
|
||||
|
||||
@description('Unique token for resource naming')
|
||||
param resourceToken string
|
||||
|
||||
@description('Key Vault name for storing connection string')
|
||||
param keyVaultName string
|
||||
|
||||
var appInsightsName = 'appi-dbp-${toLower(environment)}-${resourceToken}'
|
||||
var logAnalyticsWorkspaceName = 'log-dbp-${toLower(environment)}-${resourceToken}'
|
||||
|
||||
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2025-02-01' = {
|
||||
name: logAnalyticsWorkspaceName
|
||||
location: location
|
||||
tags: tags
|
||||
properties: {
|
||||
sku: {
|
||||
name: 'PerGB2018'
|
||||
}
|
||||
retentionInDays: environment == 'Prod' ? 90 : 30
|
||||
features: {
|
||||
enableLogAccessUsingOnlyResourcePermissions: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
|
||||
name: appInsightsName
|
||||
location: location
|
||||
tags: tags
|
||||
kind: 'web'
|
||||
properties: {
|
||||
Application_Type: 'web'
|
||||
WorkspaceResourceId: logAnalyticsWorkspace.id
|
||||
IngestionMode: 'LogAnalytics'
|
||||
publicNetworkAccessForIngestion: 'Enabled'
|
||||
publicNetworkAccessForQuery: 'Enabled'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' existing = {
|
||||
name: keyVaultName
|
||||
}
|
||||
|
||||
resource connectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2024-11-01' = {
|
||||
parent: keyVault
|
||||
name: 'applicationinsights-connection-string'
|
||||
properties: {
|
||||
value: applicationInsights.properties.ConnectionString
|
||||
attributes: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output applicationInsightsName string = applicationInsights.name
|
||||
output applicationInsightsId string = applicationInsights.id
|
||||
output connectionString string = applicationInsights.properties.ConnectionString
|
||||
output instrumentationKey string = applicationInsights.properties.InstrumentationKey
|
||||
output logAnalyticsWorkspaceId string = logAnalyticsWorkspace.id
|
||||
output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name
|
||||
@@ -0,0 +1,18 @@
|
||||
// Container App Environment Certificate module (existing)
|
||||
|
||||
@description('Container App Environment name')
|
||||
param containerAppEnvironmentName string
|
||||
|
||||
@description('Certificate name in the Container App Environment')
|
||||
param certificateName string
|
||||
|
||||
resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = {
|
||||
name: containerAppEnvironmentName
|
||||
}
|
||||
|
||||
resource containerAppCertificate 'Microsoft.App/managedEnvironments/certificates@2025-01-01' existing = {
|
||||
name: certificateName
|
||||
parent: containerAppEnvironment
|
||||
}
|
||||
|
||||
output certificateId string = containerAppCertificate.id
|
||||
@@ -0,0 +1,206 @@
|
||||
// Container App module for DB Planet MCP Server
|
||||
|
||||
@description('The environment name (DEV, IAT, Prod)')
|
||||
param environment string
|
||||
|
||||
@description('The location for the Container App')
|
||||
param location string
|
||||
|
||||
@description('Tags to apply to resources')
|
||||
param tags object
|
||||
|
||||
@description('Unique token for resource naming')
|
||||
param resourceToken string
|
||||
|
||||
@description('Existing central Key Vault name (shared secrets)')
|
||||
param centralKeyVaultName string
|
||||
|
||||
@description('Existing central Key Vault resource group')
|
||||
param centralKeyVaultResourceGroup string
|
||||
|
||||
@description('App-specific Key Vault URI (created by keyvault.bicep)')
|
||||
param appKeyVaultUri string
|
||||
|
||||
@description('Existing Container App Environment name')
|
||||
param containerAppEnvironmentName string
|
||||
|
||||
@description('Existing Container App Environment resource group')
|
||||
param containerAppEnvironmentResourceGroup string
|
||||
|
||||
@description('Artifactory server URL for container registry')
|
||||
param artifactoryServer string
|
||||
|
||||
@description('Registry username for container registry authentication')
|
||||
param registryUsername string
|
||||
|
||||
@description('Managed Identity ID for Key Vault access')
|
||||
param managedIdentityId string
|
||||
|
||||
@description('Container image tag/version')
|
||||
param imageTag string = 'latest'
|
||||
|
||||
@description('DB Planet base URL (staging vs production)')
|
||||
param dbplanetBaseUrl string
|
||||
|
||||
@description('Custom domain name for the Container App')
|
||||
param customDomainName string = ''
|
||||
|
||||
@description('Container App Certificate ID (if custom domain is configured)')
|
||||
param containerAppCertificateId string = ''
|
||||
|
||||
var containerAppName = 'ca-dbp-${toLower(environment)}-${take(resourceToken, 8)}'
|
||||
|
||||
// Custom domain configuration
|
||||
var customDomain = !empty(customDomainName) && !empty(containerAppCertificateId) ? [
|
||||
{
|
||||
name: customDomainName
|
||||
certificateId: containerAppCertificateId
|
||||
bindingType: 'SniEnabled'
|
||||
}
|
||||
] : []
|
||||
|
||||
// Reference existing central Key Vault
|
||||
resource centralKeyVault 'Microsoft.KeyVault/vaults@2024-11-01' existing = {
|
||||
name: centralKeyVaultName
|
||||
scope: resourceGroup(centralKeyVaultResourceGroup)
|
||||
}
|
||||
|
||||
|
||||
// Reference existing Container App Environment
|
||||
resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = {
|
||||
name: containerAppEnvironmentName
|
||||
scope: resourceGroup(containerAppEnvironmentResourceGroup)
|
||||
}
|
||||
|
||||
// Container App
|
||||
resource containerApp 'Microsoft.App/containerApps@2025-01-01' = {
|
||||
name: containerAppName
|
||||
location: location
|
||||
tags: tags
|
||||
identity: {
|
||||
type: 'SystemAssigned, UserAssigned'
|
||||
userAssignedIdentities: {
|
||||
'${managedIdentityId}': {}
|
||||
}
|
||||
}
|
||||
properties: {
|
||||
managedEnvironmentId: containerAppEnvironment.id
|
||||
workloadProfileName: 'Consumption'
|
||||
configuration: {
|
||||
ingress: {
|
||||
external: true
|
||||
targetPort: 4000
|
||||
allowInsecure: false
|
||||
customDomains: customDomain
|
||||
traffic: [
|
||||
{
|
||||
weight: 100
|
||||
latestRevision: true
|
||||
}
|
||||
]
|
||||
}
|
||||
secrets: [
|
||||
{
|
||||
name: 'artifactory-token'
|
||||
keyVaultUrl: '${centralKeyVault.properties.vaultUri}secrets/artifactory-registry-token'
|
||||
identity: managedIdentityId
|
||||
}
|
||||
{
|
||||
name: 'appinsights-connection-string'
|
||||
keyVaultUrl: '${appKeyVaultUri}secrets/applicationinsights-connection-string'
|
||||
identity: 'system'
|
||||
}
|
||||
{
|
||||
name: 'dbplanet-username'
|
||||
keyVaultUrl: '${appKeyVaultUri}secrets/DBPLANET-USERNAME'
|
||||
identity: 'system'
|
||||
}
|
||||
{
|
||||
name: 'dbplanet-password'
|
||||
keyVaultUrl: '${appKeyVaultUri}secrets/DBPLANET-PASSWORD'
|
||||
identity: 'system'
|
||||
}
|
||||
{
|
||||
name: 'dbplanet-client-id'
|
||||
keyVaultUrl: '${appKeyVaultUri}secrets/DBPLANET-CLIENT-ID'
|
||||
identity: 'system'
|
||||
}
|
||||
{
|
||||
name: 'dbplanet-client-secret'
|
||||
keyVaultUrl: '${appKeyVaultUri}secrets/DBPLANET-CLIENT-SECRET'
|
||||
identity: 'system'
|
||||
}
|
||||
]
|
||||
registries: [
|
||||
{
|
||||
server: artifactoryServer
|
||||
username: registryUsername
|
||||
passwordSecretRef: 'artifactory-token'
|
||||
}
|
||||
]
|
||||
}
|
||||
template: {
|
||||
containers: [
|
||||
{
|
||||
name: 'dbplanet-mcp'
|
||||
image: '${artifactoryServer}/db-planet-mcp-server:${imageTag}'
|
||||
resources: {
|
||||
cpu: json(environment == 'Prod' ? '1.0' : '0.5')
|
||||
memory: environment == 'Prod' ? '2Gi' : '1Gi'
|
||||
}
|
||||
env: [
|
||||
{ name: 'DBPLANET_BASE_URL', value: dbplanetBaseUrl }
|
||||
{ name: 'DBPLANET_USERNAME', secretRef: 'dbplanet-username' }
|
||||
{ name: 'DBPLANET_PASSWORD', secretRef: 'dbplanet-password' }
|
||||
{ name: 'DBPLANET_CLIENT_ID', secretRef: 'dbplanet-client-id' }
|
||||
{ name: 'DBPLANET_CLIENT_SECRET', secretRef: 'dbplanet-client-secret' }
|
||||
{ name: 'NODE_ENV', value: environment == 'Prod' ? 'production' : 'development' }
|
||||
{ name: 'HTTP_PROXY', value: 'http://webproxy.comp.db.de:8080' }
|
||||
{ name: 'HTTPS_PROXY', value: 'http://webproxy.comp.db.de:8080' }
|
||||
{ name: 'http_proxy', value: 'http://webproxy.comp.db.de:8080' }
|
||||
{ name: 'https_proxy', value: 'http://webproxy.comp.db.de:8080' }
|
||||
{ name: 'NO_PROXY', value: '169.254.169.254,169.254.170.2,cognitiveservices.azure.com,internal.deutschebahn.com,db.de' }
|
||||
{ name: 'no_proxy', value: '169.254.169.254,169.254.170.2,cognitiveservices.azure.com,internal.deutschebahn.com,db.de' }
|
||||
{ name: 'STREAMABLE_HTTP', value: 'true' }
|
||||
{ name: 'REMOTE_AUTHORIZATION', value: 'true' }
|
||||
{ name: 'HOST', value: '0.0.0.0' }
|
||||
{
|
||||
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
|
||||
secretRef: 'appinsights-connection-string'
|
||||
}
|
||||
]
|
||||
probes: [
|
||||
{
|
||||
type: 'Readiness'
|
||||
httpGet: {
|
||||
path: '/health'
|
||||
port: 4000
|
||||
}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
}
|
||||
{
|
||||
type: 'Liveness'
|
||||
httpGet: {
|
||||
path: '/health'
|
||||
port: 4000
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
scale: {
|
||||
minReplicas: 1
|
||||
maxReplicas: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output containerAppName string = containerApp.name
|
||||
output containerAppId string = containerApp.id
|
||||
output containerAppFqdn string = containerApp.properties.configuration.ingress.fqdn
|
||||
output systemIdentityPrincipalId string = containerApp.identity.principalId
|
||||
output containerAppEnvironmentStaticIp string = containerAppEnvironment.properties.staticIp
|
||||
@@ -0,0 +1,30 @@
|
||||
// DNS A Record module for Container App access
|
||||
|
||||
@description('Private DNS Zone name')
|
||||
param privateDnsZoneName string
|
||||
|
||||
@description('A Record name (subdomain)')
|
||||
param aRecordName string
|
||||
|
||||
@description('IPv4 address to point to')
|
||||
param ipv4Address string
|
||||
|
||||
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = {
|
||||
name: privateDnsZoneName
|
||||
}
|
||||
|
||||
resource aRecord 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
|
||||
name: aRecordName
|
||||
parent: privateDnsZone
|
||||
properties: {
|
||||
ttl: 3600
|
||||
aRecords: [
|
||||
{
|
||||
ipv4Address: ipv4Address
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
output recordName string = aRecord.name
|
||||
output fqdn string = '${aRecordName}.${privateDnsZoneName}'
|
||||
@@ -0,0 +1,59 @@
|
||||
// Key Vault permissions module - reusable for different roles and principals
|
||||
|
||||
@description('Key Vault name')
|
||||
param keyVaultName string
|
||||
|
||||
@description('Principal ID for Key Vault access')
|
||||
param principalId string
|
||||
|
||||
@description('Principal type (User, Group, ServicePrincipal)')
|
||||
@allowed(['User', 'Group', 'ServicePrincipal'])
|
||||
param principalType string = 'ServicePrincipal'
|
||||
|
||||
@description('Key Vault role to assign')
|
||||
@allowed([
|
||||
'Key Vault Administrator'
|
||||
'Key Vault Certificates Officer'
|
||||
'Key Vault Crypto Officer'
|
||||
'Key Vault Crypto Service Encryption User'
|
||||
'Key Vault Crypto User'
|
||||
'Key Vault Reader'
|
||||
'Key Vault Secrets Officer'
|
||||
'Key Vault Secrets User'
|
||||
])
|
||||
param roleName string = 'Key Vault Secrets User'
|
||||
|
||||
var roleDefinitions = {
|
||||
// gitleaks:allow
|
||||
'Key Vault Administrator': '00482a5a-887f-4fb3-b363-3b7fe8e74483'
|
||||
'Key Vault Certificates Officer': 'a4417e6f-fecd-4de8-b567-7b0420556985'
|
||||
'Key Vault Crypto Officer': '14b46e9e-c2b7-41b4-b07b-48a6ebf60603'
|
||||
'Key Vault Crypto Service Encryption User': 'e147488a-f6f5-4113-8e2d-b22465e65bf6'
|
||||
// gitleaks:allow
|
||||
'Key Vault Crypto User': '12338af0-0e69-4776-bea7-57ae8d297424'
|
||||
// gitleaks:allow
|
||||
'Key Vault Reader': '21090545-7ca7-4776-b22c-e363652d74d2'
|
||||
// gitleaks:allow
|
||||
'Key Vault Secrets Officer': 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7'
|
||||
// gitleaks:allow
|
||||
'Key Vault Secrets User': '4633458b-17de-408a-b874-0445c86b69e6'
|
||||
}
|
||||
|
||||
var roleDefinitionId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions[roleName])
|
||||
|
||||
resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' existing = {
|
||||
name: keyVaultName
|
||||
}
|
||||
|
||||
resource keyVaultRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
||||
name: guid(keyVault.id, principalId, roleName)
|
||||
scope: keyVault
|
||||
properties: {
|
||||
roleDefinitionId: roleDefinitionId
|
||||
principalId: principalId
|
||||
principalType: principalType
|
||||
}
|
||||
}
|
||||
|
||||
output roleAssignmentId string = keyVaultRoleAssignment.id
|
||||
output roleAssignmentName string = keyVaultRoleAssignment.name
|
||||
@@ -0,0 +1,47 @@
|
||||
// Azure Key Vault module (public access, no Private Endpoint)
|
||||
|
||||
@description('The environment name (DEV, IAT, Prod)')
|
||||
param environment string
|
||||
|
||||
@description('The location for the Key Vault')
|
||||
param location string
|
||||
|
||||
@description('Tags to apply to resources')
|
||||
param tags object
|
||||
|
||||
@description('Unique token for resource naming')
|
||||
param resourceToken string
|
||||
|
||||
@description('The tenant ID for Key Vault access')
|
||||
param tenantId string = subscription().tenantId
|
||||
|
||||
var keyVaultName = 'kv-dbp-${toLower(environment)}-${take(resourceToken, 8)}'
|
||||
|
||||
resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' = {
|
||||
name: keyVaultName
|
||||
location: location
|
||||
tags: tags
|
||||
properties: {
|
||||
sku: {
|
||||
family: 'A'
|
||||
name: 'standard'
|
||||
}
|
||||
tenantId: tenantId
|
||||
enabledForDeployment: false
|
||||
enabledForDiskEncryption: true
|
||||
enabledForTemplateDeployment: true
|
||||
enableSoftDelete: true
|
||||
softDeleteRetentionInDays: 7
|
||||
enablePurgeProtection: true
|
||||
enableRbacAuthorization: true
|
||||
publicNetworkAccess: 'Enabled'
|
||||
networkAcls: {
|
||||
defaultAction: 'Allow'
|
||||
bypass: 'AzureServices'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output keyVaultName string = keyVault.name
|
||||
output keyVaultId string = keyVault.id
|
||||
output keyVaultUri string = keyVault.properties.vaultUri
|
||||
@@ -0,0 +1,26 @@
|
||||
// User Managed Identity module
|
||||
|
||||
@description('The environment name (DEV, IAT, Prod)')
|
||||
param environment string
|
||||
|
||||
@description('The location for the managed identity')
|
||||
param location string
|
||||
|
||||
@description('Tags to apply to resources')
|
||||
param tags object
|
||||
|
||||
@description('Unique token for resource naming')
|
||||
param resourceToken string
|
||||
|
||||
var managedIdentityName = 'id-dbplanet-${toLower(environment)}-${resourceToken}'
|
||||
|
||||
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
|
||||
name: managedIdentityName
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
|
||||
output managedIdentityName string = managedIdentity.name
|
||||
output managedIdentityId string = managedIdentity.id
|
||||
output managedIdentityPrincipalId string = managedIdentity.properties.principalId
|
||||
output managedIdentityClientId string = managedIdentity.properties.clientId
|
||||
@@ -0,0 +1,21 @@
|
||||
// Resource Group module
|
||||
|
||||
targetScope = 'subscription'
|
||||
|
||||
@description('The resource group name')
|
||||
param resourceGroupName string
|
||||
|
||||
@description('The location for the resource group')
|
||||
param location string
|
||||
|
||||
@description('Tags to apply to the resource group')
|
||||
param tags object
|
||||
|
||||
resource resourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
|
||||
name: resourceGroupName
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
|
||||
output resourceGroupName string = resourceGroup.name
|
||||
output resourceGroupId string = resourceGroup.id
|
||||
@@ -0,0 +1,29 @@
|
||||
// Development environment parameters for DB Planet MCP Server
|
||||
using '../main.bicep'
|
||||
|
||||
param environment = 'DEV'
|
||||
param location = 'westeurope'
|
||||
param imageTag = 'latest'
|
||||
|
||||
// ── Existing infrastructure ──────────────────────────────────
|
||||
// gitleaks:allow
|
||||
param centralKeyVaultName = 'bahngpt-kv-c76j3yuqbfnmq'
|
||||
param centralKeyVaultResourceGroup = 'DevSecOps-DEV'
|
||||
param containerAppEnvironmentName = 'cae-devsecops-dev-c76j3yuq'
|
||||
param containerAppEnvironmentResourceGroup = 'DevSecOps-DEV'
|
||||
|
||||
// ── Registry ─────────────────────────────────────────────────
|
||||
param artifactoryServer = 'generativeai-docker-stage-dev-local.bahnhub.tech.rz.db.de'
|
||||
param registryUsername = 'techuser-generativeai'
|
||||
|
||||
// ── Application ──────────────────────────────────────────────
|
||||
param dbplanetBaseUrl = 'https://dbahn-staging.haiilo.cloud'
|
||||
|
||||
// ── Permissions ──────────────────────────────────────────────
|
||||
param secretsOfficerPrincipalId = ''
|
||||
|
||||
// ── Custom domain ────────────────────────────────────────────
|
||||
param certificateName = 'wildcard-cert'
|
||||
param dnsZoneName = 'genai-test.comp.db.de'
|
||||
param dnsRecordName = 'dbplanet-dev'
|
||||
param dnsZoneResourceGroup = 'Infrastructure'
|
||||
@@ -0,0 +1,29 @@
|
||||
// IAT environment parameters for DB Planet MCP Server
|
||||
using '../main.bicep'
|
||||
|
||||
param environment = 'IAT'
|
||||
param location = 'westeurope'
|
||||
param imageTag = 'latest'
|
||||
|
||||
// ── Existing infrastructure ──────────────────────────────────
|
||||
// gitleaks:allow
|
||||
param centralKeyVaultName = 'bahngpt-kv-6i2jt7tebagcq'
|
||||
param centralKeyVaultResourceGroup = 'DevSecOps-IAT'
|
||||
param containerAppEnvironmentName = 'cae-devsecops-iat-6i2jt7te'
|
||||
param containerAppEnvironmentResourceGroup = 'DevSecOps-IAT'
|
||||
|
||||
// ── Registry ─────────────────────────────────────────────────
|
||||
param artifactoryServer = 'generativeai-docker-stage-iat-local.bahnhub.tech.rz.db.de'
|
||||
param registryUsername = 'techuser-generativeai'
|
||||
|
||||
// ── Application ──────────────────────────────────────────────
|
||||
param dbplanetBaseUrl = 'https://dbahn-staging.haiilo.cloud'
|
||||
|
||||
// ── Permissions ──────────────────────────────────────────────
|
||||
param secretsOfficerPrincipalId = ''
|
||||
|
||||
// ── Custom domain ────────────────────────────────────────────
|
||||
param certificateName = 'wildcard-cert'
|
||||
param dnsZoneName = 'genai-test.comp.db.de'
|
||||
param dnsRecordName = 'dbplanet-iat'
|
||||
param dnsZoneResourceGroup = 'Infrastructure'
|
||||
Reference in New Issue
Block a user