Deploy to production - ensure all endpoints operational
This commit is contained in:
@@ -1,425 +1,472 @@
|
||||
@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('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')
|
||||
@allowed(['EP1', 'EP2', 'EP3'])
|
||||
param functionAppSku string = 'EP1'
|
||||
|
||||
// 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 - Premium for Production
|
||||
resource functionAppServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
|
||||
name: '${resourcePrefix}-func-plan'
|
||||
location: location
|
||||
sku: {
|
||||
name: functionAppSku
|
||||
tier: 'ElasticPremium'
|
||||
size: functionAppSku
|
||||
capacity: 1
|
||||
}
|
||||
kind: 'functionapp'
|
||||
properties: {
|
||||
reserved: true
|
||||
maximumElasticWorkerCount: 20
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Domain Configuration (if enabled)
|
||||
resource customDomain 'Microsoft.Web/staticSites/customDomains@2023-12-01' = if (enableCustomDomain && !empty(customDomainName)) {
|
||||
parent: staticWebApp
|
||||
name: customDomainName
|
||||
properties: {
|
||||
validationMethod: 'cname-delegation'
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@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
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"environment": {
|
||||
"value": "prod"
|
||||
},
|
||||
"location": {
|
||||
"value": "East US"
|
||||
},
|
||||
"stripePublicKey": {
|
||||
"value": "pk_live_placeholder"
|
||||
},
|
||||
"customDomainName": {
|
||||
"value": "miraclesinmotion.org"
|
||||
},
|
||||
"enableCustomDomain": {
|
||||
"value": true
|
||||
},
|
||||
"staticWebAppSku": {
|
||||
"value": "Standard"
|
||||
},
|
||||
"functionAppSku": {
|
||||
"value": "EP1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"environment": {
|
||||
"value": "prod"
|
||||
},
|
||||
"location": {
|
||||
"value": "East US"
|
||||
},
|
||||
"stripePublicKey": {
|
||||
"value": "pk_live_placeholder"
|
||||
},
|
||||
"azureClientId": {
|
||||
"value": ""
|
||||
},
|
||||
"azureTenantId": {
|
||||
"value": ""
|
||||
},
|
||||
"azureClientSecret": {
|
||||
"value": ""
|
||||
},
|
||||
"customDomainName": {
|
||||
"value": "mim4u.org"
|
||||
},
|
||||
"enableCustomDomain": {
|
||||
"value": true
|
||||
},
|
||||
"staticWebAppSku": {
|
||||
"value": "Standard"
|
||||
},
|
||||
"functionAppSku": {
|
||||
"value": "Y1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,323 +1,323 @@
|
||||
@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
|
||||
|
||||
// Variables
|
||||
var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 6)
|
||||
|
||||
// Cosmos DB Account
|
||||
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-cosmos'
|
||||
location: location
|
||||
kind: 'GlobalDocumentDB'
|
||||
properties: {
|
||||
databaseAccountOfferType: 'Standard'
|
||||
consistencyPolicy: {
|
||||
defaultConsistencyLevel: 'Session'
|
||||
}
|
||||
locations: [
|
||||
{
|
||||
locationName: location
|
||||
failoverPriority: 0
|
||||
isZoneRedundant: false
|
||||
}
|
||||
]
|
||||
capabilities: [
|
||||
{
|
||||
name: 'EnableServerless'
|
||||
}
|
||||
]
|
||||
backupPolicy: {
|
||||
type: 'Periodic'
|
||||
periodicModeProperties: {
|
||||
backupIntervalInMinutes: 240
|
||||
backupRetentionIntervalInHours: 720
|
||||
backupStorageRedundancy: 'Local'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key Vault
|
||||
resource keyVault 'Microsoft.KeyVault/vaults@2024-04-01-preview' = {
|
||||
name: 'mim${environment}${uniqueSuffix}kv'
|
||||
location: location
|
||||
properties: {
|
||||
sku: {
|
||||
family: 'A'
|
||||
name: 'standard'
|
||||
}
|
||||
tenantId: tenant().tenantId
|
||||
accessPolicies: []
|
||||
enabledForDeployment: false
|
||||
enabledForDiskEncryption: false
|
||||
enabledForTemplateDeployment: true
|
||||
enableSoftDelete: true
|
||||
softDeleteRetentionInDays: 90
|
||||
enableRbacAuthorization: true
|
||||
}
|
||||
}
|
||||
|
||||
// Application Insights
|
||||
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-insights'
|
||||
location: location
|
||||
kind: 'web'
|
||||
properties: {
|
||||
Application_Type: 'web'
|
||||
WorkspaceResourceId: logAnalyticsWorkspace.id
|
||||
}
|
||||
}
|
||||
|
||||
// Log Analytics Workspace
|
||||
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-logs'
|
||||
location: location
|
||||
properties: {
|
||||
sku: {
|
||||
name: 'PerGB2018'
|
||||
}
|
||||
retentionInDays: 30
|
||||
}
|
||||
}
|
||||
|
||||
// Storage Account for Functions
|
||||
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
||||
name: 'mim${environment}${uniqueSuffix}st'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Standard_LRS'
|
||||
}
|
||||
kind: 'StorageV2'
|
||||
properties: {
|
||||
accessTier: 'Hot'
|
||||
supportsHttpsTrafficOnly: true
|
||||
minimumTlsVersion: 'TLS1_2'
|
||||
}
|
||||
}
|
||||
|
||||
// App Service Plan
|
||||
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-plan'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Y1'
|
||||
tier: 'Dynamic'
|
||||
}
|
||||
properties: {
|
||||
reserved: false
|
||||
}
|
||||
}
|
||||
|
||||
// Function App
|
||||
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-func'
|
||||
location: location
|
||||
kind: 'functionapp'
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
properties: {
|
||||
serverFarmId: appServicePlan.id
|
||||
siteConfig: {
|
||||
appSettings: [
|
||||
{
|
||||
name: 'AzureWebJobsStorage'
|
||||
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
|
||||
}
|
||||
{
|
||||
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
|
||||
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
|
||||
}
|
||||
{
|
||||
name: 'WEBSITE_CONTENTSHARE'
|
||||
value: toLower('mim-${environment}-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
|
||||
}
|
||||
]
|
||||
}
|
||||
httpsOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
// SignalR Service
|
||||
resource signalR 'Microsoft.SignalRService/signalR@2023-02-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-signalr'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Free_F1'
|
||||
capacity: 1
|
||||
}
|
||||
kind: 'SignalR'
|
||||
properties: {
|
||||
features: [
|
||||
{
|
||||
flag: 'ServiceMode'
|
||||
value: 'Serverless'
|
||||
}
|
||||
]
|
||||
cors: {
|
||||
allowedOrigins: ['*']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static Web App
|
||||
resource staticWebApp 'Microsoft.Web/staticSites@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-web'
|
||||
location: 'Central US'
|
||||
sku: {
|
||||
name: 'Free'
|
||||
}
|
||||
properties: {
|
||||
buildProperties: {
|
||||
outputLocation: 'dist'
|
||||
apiLocation: ''
|
||||
appLocation: '/'
|
||||
}
|
||||
stagingEnvironmentPolicy: 'Enabled'
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
|
||||
// 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 functionAppUrl string = 'https://${functionApp.properties.defaultHostName}'
|
||||
output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}'
|
||||
@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
|
||||
|
||||
// Variables
|
||||
var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 6)
|
||||
|
||||
// Cosmos DB Account
|
||||
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-cosmos'
|
||||
location: location
|
||||
kind: 'GlobalDocumentDB'
|
||||
properties: {
|
||||
databaseAccountOfferType: 'Standard'
|
||||
consistencyPolicy: {
|
||||
defaultConsistencyLevel: 'Session'
|
||||
}
|
||||
locations: [
|
||||
{
|
||||
locationName: location
|
||||
failoverPriority: 0
|
||||
isZoneRedundant: false
|
||||
}
|
||||
]
|
||||
capabilities: [
|
||||
{
|
||||
name: 'EnableServerless'
|
||||
}
|
||||
]
|
||||
backupPolicy: {
|
||||
type: 'Periodic'
|
||||
periodicModeProperties: {
|
||||
backupIntervalInMinutes: 240
|
||||
backupRetentionIntervalInHours: 720
|
||||
backupStorageRedundancy: 'Local'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key Vault
|
||||
resource keyVault 'Microsoft.KeyVault/vaults@2024-04-01-preview' = {
|
||||
name: 'mim${environment}${uniqueSuffix}kv'
|
||||
location: location
|
||||
properties: {
|
||||
sku: {
|
||||
family: 'A'
|
||||
name: 'standard'
|
||||
}
|
||||
tenantId: tenant().tenantId
|
||||
accessPolicies: []
|
||||
enabledForDeployment: false
|
||||
enabledForDiskEncryption: false
|
||||
enabledForTemplateDeployment: true
|
||||
enableSoftDelete: true
|
||||
softDeleteRetentionInDays: 90
|
||||
enableRbacAuthorization: true
|
||||
}
|
||||
}
|
||||
|
||||
// Application Insights
|
||||
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-insights'
|
||||
location: location
|
||||
kind: 'web'
|
||||
properties: {
|
||||
Application_Type: 'web'
|
||||
WorkspaceResourceId: logAnalyticsWorkspace.id
|
||||
}
|
||||
}
|
||||
|
||||
// Log Analytics Workspace
|
||||
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-logs'
|
||||
location: location
|
||||
properties: {
|
||||
sku: {
|
||||
name: 'PerGB2018'
|
||||
}
|
||||
retentionInDays: 30
|
||||
}
|
||||
}
|
||||
|
||||
// Storage Account for Functions
|
||||
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
||||
name: 'mim${environment}${uniqueSuffix}st'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Standard_LRS'
|
||||
}
|
||||
kind: 'StorageV2'
|
||||
properties: {
|
||||
accessTier: 'Hot'
|
||||
supportsHttpsTrafficOnly: true
|
||||
minimumTlsVersion: 'TLS1_2'
|
||||
}
|
||||
}
|
||||
|
||||
// App Service Plan
|
||||
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-plan'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Y1'
|
||||
tier: 'Dynamic'
|
||||
}
|
||||
properties: {
|
||||
reserved: false
|
||||
}
|
||||
}
|
||||
|
||||
// Function App
|
||||
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-func'
|
||||
location: location
|
||||
kind: 'functionapp'
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
properties: {
|
||||
serverFarmId: appServicePlan.id
|
||||
siteConfig: {
|
||||
appSettings: [
|
||||
{
|
||||
name: 'AzureWebJobsStorage'
|
||||
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
|
||||
}
|
||||
{
|
||||
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
|
||||
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
|
||||
}
|
||||
{
|
||||
name: 'WEBSITE_CONTENTSHARE'
|
||||
value: toLower('mim-${environment}-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
|
||||
}
|
||||
]
|
||||
}
|
||||
httpsOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
// SignalR Service
|
||||
resource signalR 'Microsoft.SignalRService/signalR@2023-02-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-signalr'
|
||||
location: location
|
||||
sku: {
|
||||
name: 'Free_F1'
|
||||
capacity: 1
|
||||
}
|
||||
kind: 'SignalR'
|
||||
properties: {
|
||||
features: [
|
||||
{
|
||||
flag: 'ServiceMode'
|
||||
value: 'Serverless'
|
||||
}
|
||||
]
|
||||
cors: {
|
||||
allowedOrigins: ['*']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static Web App
|
||||
resource staticWebApp 'Microsoft.Web/staticSites@2023-01-01' = {
|
||||
name: 'mim-${environment}-${uniqueSuffix}-web'
|
||||
location: 'Central US'
|
||||
sku: {
|
||||
name: 'Free'
|
||||
}
|
||||
properties: {
|
||||
buildProperties: {
|
||||
outputLocation: 'dist'
|
||||
apiLocation: ''
|
||||
appLocation: '/'
|
||||
}
|
||||
stagingEnvironmentPolicy: 'Enabled'
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
|
||||
// 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 functionAppUrl string = 'https://${functionApp.properties.defaultHostName}'
|
||||
output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}'
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"appName": {
|
||||
"value": "miraclesinmotion"
|
||||
},
|
||||
"environment": {
|
||||
"value": "prod"
|
||||
},
|
||||
"location": {
|
||||
"value": "eastus2"
|
||||
},
|
||||
"stripePublicKey": {
|
||||
"value": "pk_live_placeholder"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"appName": {
|
||||
"value": "miraclesinmotion"
|
||||
},
|
||||
"environment": {
|
||||
"value": "prod"
|
||||
},
|
||||
"location": {
|
||||
"value": "eastus2"
|
||||
},
|
||||
"stripePublicKey": {
|
||||
"value": "pk_live_placeholder"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user