Files
miracles_in_motion/infrastructure/main-production.bicep

473 lines
14 KiB
Bicep

@description('Environment (dev, staging, prod)')
param environment string = 'prod'
@description('Azure region for resources')
param location string = resourceGroup().location
@description('Stripe public key for payments')
@secure()
param stripePublicKey string
@description('Azure AD Client ID for authentication')
param azureClientId string = ''
@description('Azure AD Tenant ID')
param azureTenantId string = subscription().tenantId
@description('Azure AD Client Secret (optional, for server-side flows)')
@secure()
param azureClientSecret string = ''
@description('Custom domain name for the application')
param customDomainName string = ''
@description('Enable custom domain configuration')
param enableCustomDomain bool = false
@description('Static Web App SKU')
@allowed(['Standard'])
param staticWebAppSku string = 'Standard'
@description('Function App SKU (Y1 for Consumption, EP1/EP2/EP3 for Premium)')
@allowed(['Y1', 'EP1', 'EP2', 'EP3'])
param functionAppSku string = 'Y1'
// Variables
var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 6)
var resourcePrefix = 'mim-${environment}-${uniqueSuffix}'
// Log Analytics Workspace (needed first for Application Insights)
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: '${resourcePrefix}-logs'
location: location
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 30
features: {
searchVersion: 1
legacy: 0
enableLogAccessUsingOnlyResourcePermissions: true
}
}
}
// Application Insights
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: '${resourcePrefix}-appinsights'
location: location
kind: 'web'
properties: {
Application_Type: 'web'
Flow_Type: 'Redfield'
Request_Source: 'IbizaAIExtension'
RetentionInDays: 90
WorkspaceResourceId: logAnalyticsWorkspace.id
IngestionMode: 'LogAnalytics'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}
// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: '${resourcePrefix}-kv'
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
enableRbacAuthorization: true
enableSoftDelete: true
softDeleteRetentionInDays: 90
enablePurgeProtection: true
networkAcls: {
defaultAction: 'Allow'
bypass: 'AzureServices'
}
}
}
// Cosmos DB Account - Production Ready
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
name: '${resourcePrefix}-cosmos'
location: location
kind: 'GlobalDocumentDB'
properties: {
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
}
locations: [
{
locationName: location
failoverPriority: 0
isZoneRedundant: true
}
]
enableAutomaticFailover: true
enableMultipleWriteLocations: false
backupPolicy: {
type: 'Periodic'
periodicModeProperties: {
backupIntervalInMinutes: 240
backupRetentionIntervalInHours: 720
backupStorageRedundancy: 'Geo'
}
}
networkAclBypass: 'AzureServices'
publicNetworkAccess: 'Enabled'
}
}
// Cosmos DB Database
resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = {
parent: cosmosAccount
name: 'MiraclesInMotion'
properties: {
resource: {
id: 'MiraclesInMotion'
}
}
}
// Cosmos DB Containers
resource donationsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = {
parent: cosmosDatabase
name: 'donations'
properties: {
resource: {
id: 'donations'
partitionKey: {
paths: ['/id']
kind: 'Hash'
}
indexingPolicy: {
indexingMode: 'consistent'
automatic: true
includedPaths: [
{
path: '/*'
}
]
}
}
}
}
resource volunteersContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = {
parent: cosmosDatabase
name: 'volunteers'
properties: {
resource: {
id: 'volunteers'
partitionKey: {
paths: ['/id']
kind: 'Hash'
}
}
}
}
resource programsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = {
parent: cosmosDatabase
name: 'programs'
properties: {
resource: {
id: 'programs'
partitionKey: {
paths: ['/id']
kind: 'Hash'
}
}
}
}
resource studentsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = {
parent: cosmosDatabase
name: 'students'
properties: {
resource: {
id: 'students'
partitionKey: {
paths: ['/schoolId']
kind: 'Hash'
}
}
}
}
// Function App Service Plan - Consumption Plan (Y1) for Production
// Note: Changed from Premium to Consumption to avoid quota issues
// Premium can be enabled later by requesting quota increase
resource functionAppServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: '${resourcePrefix}-func-plan'
location: location
sku: {
name: functionAppSku
tier: functionAppSku == 'Y1' ? 'Dynamic' : 'ElasticPremium'
size: functionAppSku != 'Y1' ? functionAppSku : null
capacity: functionAppSku != 'Y1' ? 1 : null
}
kind: 'functionapp'
properties: {
reserved: functionAppSku != 'Y1'
maximumElasticWorkerCount: functionAppSku != 'Y1' ? 20 : null
}
}
// Storage Account for Function App
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: replace('${resourcePrefix}stor', '-', '')
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}
// Function App with Enhanced Configuration
resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
name: '${resourcePrefix}-func'
location: location
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: functionAppServicePlan.id
siteConfig: {
linuxFxVersion: 'NODE|22'
appSettings: [
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: toLower('${resourcePrefix}-func')
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'node'
}
{
name: 'WEBSITE_NODE_DEFAULT_VERSION'
value: '~22'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'COSMOS_CONNECTION_STRING'
value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString
}
{
name: 'COSMOS_DATABASE_NAME'
value: 'MiraclesInMotion'
}
{
name: 'KEY_VAULT_URL'
value: keyVault.properties.vaultUri
}
{
name: 'STRIPE_PUBLIC_KEY'
value: stripePublicKey
}
]
cors: {
allowedOrigins: ['*']
supportCredentials: false
}
use32BitWorkerProcess: false
ftpsState: 'FtpsOnly'
minTlsVersion: '1.2'
}
httpsOnly: true
clientAffinityEnabled: false
}
}
// SignalR Service - Standard for Production
resource signalR 'Microsoft.SignalRService/signalR@2023-02-01' = {
name: '${resourcePrefix}-signalr'
location: location
sku: {
name: 'Standard_S1'
capacity: 1
}
kind: 'SignalR'
properties: {
features: [
{
flag: 'ServiceMode'
value: 'Serverless'
}
]
cors: {
allowedOrigins: ['*']
}
networkACLs: {
defaultAction: 'Allow'
}
}
}
// Static Web App - Production Ready with Custom Domain Support
resource staticWebApp 'Microsoft.Web/staticSites@2023-12-01' = {
name: '${resourcePrefix}-web'
location: 'Central US'
sku: {
name: staticWebAppSku
tier: staticWebAppSku
}
properties: {
buildProperties: {
appLocation: '/'
apiLocation: 'api'
outputLocation: 'dist'
}
stagingEnvironmentPolicy: 'Enabled'
allowConfigFileUpdates: true
enterpriseGradeCdnStatus: 'Enabled'
}
}
// Note: Static Web App authentication is configured via staticwebapp.config.json
// and Azure Portal. App settings are configured separately through Azure Portal
// or during deployment. The azureClientId and azureTenantId parameters are
// stored in Key Vault for reference and can be used to configure authentication
// in the Azure Portal after deployment.
// Custom Domain Configuration (if enabled)
// Note: Using TXT validation for Enterprise Grade Edge compatibility
resource customDomain 'Microsoft.Web/staticSites/customDomains@2023-12-01' = if (enableCustomDomain && !empty(customDomainName)) {
parent: staticWebApp
name: customDomainName
properties: {
validationMethod: 'txt-token'
}
}
// Key Vault Secrets
resource cosmosConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'cosmos-connection-string'
properties: {
value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString
}
}
resource signalRConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'signalr-connection-string'
properties: {
value: signalR.listKeys().primaryConnectionString
}
}
resource stripeSecretKeySecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'stripe-secret-key'
properties: {
value: 'sk_live_placeholder' // Replace with actual secret key
}
}
// Azure AD Configuration Secrets
resource azureClientIdSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = if (!empty(azureClientId)) {
parent: keyVault
name: 'azure-client-id'
properties: {
value: azureClientId
}
}
resource azureTenantIdSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'azure-tenant-id'
properties: {
value: azureTenantId
}
}
resource azureClientSecretSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = if (!empty(azureClientSecret)) {
parent: keyVault
name: 'azure-client-secret'
properties: {
value: azureClientSecret
}
}
// RBAC Assignments for Function App
resource keyVaultSecretsUserRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, functionApp.id, 'Key Vault Secrets User')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource cosmosContributorRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(cosmosAccount.id, functionApp.id, 'Cosmos DB Built-in Data Contributor')
scope: cosmosAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00000000-0000-0000-0000-000000000002') // Cosmos DB Built-in Data Contributor
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
// Outputs
output resourceGroupName string = resourceGroup().name
output cosmosAccountName string = cosmosAccount.name
output functionAppName string = functionApp.name
output staticWebAppName string = staticWebApp.name
output keyVaultName string = keyVault.name
output appInsightsName string = appInsights.name
output signalRName string = signalR.name
output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name
output functionAppUrl string = 'https://${functionApp.properties.defaultHostName}'
output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}'
output customDomainName string = enableCustomDomain ? customDomainName : ''
output applicationInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey
output applicationInsightsConnectionString string = appInsights.properties.ConnectionString
output azureClientId string = azureClientId
output azureTenantId string = azureTenantId
output keyVaultUri string = keyVault.properties.vaultUri