diff --git a/.gitignore b/.gitignore index 2407635..91a682b 100644 --- a/.gitignore +++ b/.gitignore @@ -257,5 +257,6 @@ pub/ #Ignore marker-file used to know which docker files we have. .eshopdocker_* +.devcontainer .azure diff --git a/azure.yaml b/azure.yaml index da49708..9590449 100644 --- a/azure.yaml +++ b/azure.yaml @@ -6,9 +6,3 @@ services: project: ./src/Web language: csharp host: appservice -infra: - provider: "" - path: "" - module: "" -pipeline: - provider: "" diff --git a/infra/app/catalog-db.bicep b/infra/app/catalog-db.bicep new file mode 100644 index 0000000..631f138 --- /dev/null +++ b/infra/app/catalog-db.bicep @@ -0,0 +1,31 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param databaseName string = 'CatalogDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module sqlServer1 '../core/database/sqlserver/sqlserver1.bicep' = { + name: 'sqlServer01' + params: { + name: name + location: location + tags: tags + databaseName: actualDatabaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlCatalogConnectionStringKey string = sqlServer1.outputs.connectionStringKey +output sqlCatalogDatabase1Name string = sqlServer1.outputs.databaseName diff --git a/infra/app/dbCatalog.bicep b/infra/app/dbCatalog.bicep deleted file mode 100644 index e320f63..0000000 --- a/infra/app/dbCatalog.bicep +++ /dev/null @@ -1,25 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param databaseName string = 'CatalogDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword string - -module sqlServer1 '../core/database/sqlserver1.bicep' = { - name: 'sqlServer1' - params: { - environmentName: environmentName - location: location - dbName: databaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlConnectionStringKey string = sqlServer1.outputs.sqlConnectionStringKey -output sqlDatabase1Name string = databaseName diff --git a/infra/app/dbIdentity.bicep b/infra/app/dbIdentity.bicep deleted file mode 100644 index a9cb741..0000000 --- a/infra/app/dbIdentity.bicep +++ /dev/null @@ -1,25 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param databaseName string = 'IdentityDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword string - -module sqlServer2 '../core/database/sqlserver2.bicep' = { - name: 'sqlServer2' - params: { - environmentName: environmentName - location: location - dbName: databaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlConnectionStringKey string = sqlServer2.outputs.sqlConnectionStringKey -output sqlDatabase2Name string = databaseName diff --git a/infra/app/identity-db.bicep b/infra/app/identity-db.bicep new file mode 100644 index 0000000..1673c53 --- /dev/null +++ b/infra/app/identity-db.bicep @@ -0,0 +1,31 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param databaseName string = 'IdentityDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module sqlServer2 '../core/database/sqlserver/sqlserver2.bicep' = { + name: 'sqlServer02' + params: { + name: name + location: location + tags: tags + databaseName: actualDatabaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlCatalogConnectionStringKey string = sqlServer2.outputs.connectionStringKey +output sqlCatalogDatabase1Name string = sqlServer2.outputs.databaseName diff --git a/infra/app/web.bicep b/infra/app/web.bicep index 828b282..f33a276 100644 --- a/infra/app/web.bicep +++ b/infra/app/web.bicep @@ -1,18 +1,23 @@ -param environmentName string +param name string param location string = resourceGroup().location -param appServicePlanId string - +param tags object = {} param serviceName string = 'web' +param appCommandLine string = 'pm2 serve /home/site/wwwroot --no-daemon --spa' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} -module web '../core/host/appservice-dotnet.bicep' = { - name: '${serviceName}-appservice-dotnet-module' +module web '../core/host/appservice.bicep' = { + name: '${name}-deployment' params: { - environmentName: environmentName + name: name location: location appServicePlanId: appServicePlanId - serviceName: serviceName + runtimeName: 'dotnetcore' + runtimeVersion: '6.0' + tags: union(tags, { 'azd-service-name': serviceName }) + scmDoBuildDuringDeployment: false } } -output WEB_NAME string = web.outputs.name -output WEB_URI string = web.outputs.uri \ No newline at end of file +output REACT_APP_WEB_BASE_URL string = web.outputs.uri diff --git a/infra/core/database/cosmos/cosmos-account.bicep b/infra/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 0000000..6bc1f2e --- /dev/null +++ b/infra/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,48 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.0' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 0000000..bd2a2b5 --- /dev/null +++ b/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,22 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 0000000..2c9688e --- /dev/null +++ b/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,46 @@ +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/infra/core/database/cosmos/sql/cosmos-sql-account.bicep b/infra/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 0000000..e8b030f --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,21 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/infra/core/database/cosmos/sql/cosmos-sql-db.bicep b/infra/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 0000000..5a4de20 --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,73 @@ +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefintion 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefintion.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefintion.outputs.id diff --git a/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 0000000..6855edf --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,18 @@ +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 0000000..cfb4033 --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,29 @@ +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/infra/core/database/sqlserver/sqlserver.bicep b/infra/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 0000000..821a908 --- /dev/null +++ b/infra/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,129 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/infra/core/database/sqlserver1.bicep b/infra/core/database/sqlserver/sqlserver1.bicep similarity index 76% rename from infra/core/database/sqlserver1.bicep rename to infra/core/database/sqlserver/sqlserver1.bicep index 671371c..891aa45 100644 --- a/infra/core/database/sqlserver1.bicep +++ b/infra/core/database/sqlserver/sqlserver1.bicep @@ -1,23 +1,20 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param appUser string = 'appUser' -param dbName string +param databaseName string param keyVaultName string param sqlAdmin string = 'sqlAdmin' -param sqlConnectionStringKey string = 'AZURE-SQL-CATALOG-CONNECTION-STRING' +param connectionStringKey string = 'AZURE-SQL-CATALOG-CONNECTION-STRING' @secure() param sqlAdminPassword string @secure() param appUserPassword string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { - name: '${abbrs.sqlServers}${resourceToken}-Catalog' +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name location: location tags: tags properties: { @@ -29,7 +26,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource database 'databases' = { - name: dbName + name: databaseName location: location } @@ -46,7 +43,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { - name: 'script-${resourceToken}-Catalog' + name: '${name}-deployment-script' location: location kind: 'AzureCLI' properties: { @@ -65,7 +62,7 @@ resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' } { name: 'DBNAME' - value: dbName + value: databaseName } { name: 'DBSERVER' @@ -117,9 +114,9 @@ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { parent: keyVault - name: sqlConnectionStringKey + name: connectionStringKey properties: { - value: '${azureSqlConnectionString}; Password=${appUserPassword}' + value: '${connectionString}; Password=${appUserPassword}' } } @@ -127,5 +124,6 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } -var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' -output sqlConnectionStringKey string = sqlConnectionStringKey +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/infra/core/database/sqlserver2.bicep b/infra/core/database/sqlserver/sqlserver2.bicep similarity index 76% rename from infra/core/database/sqlserver2.bicep rename to infra/core/database/sqlserver/sqlserver2.bicep index 430f48f..305c97b 100644 --- a/infra/core/database/sqlserver2.bicep +++ b/infra/core/database/sqlserver/sqlserver2.bicep @@ -1,23 +1,20 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param appUser string = 'appUser' -param dbName string +param databaseName string param keyVaultName string param sqlAdmin string = 'sqlAdmin' -param sqlConnectionStringKey string = 'AZURE-SQL-IDENTITY-CONNECTION-STRING' +param connectionStringKey string = 'AZURE-SQL-IDENTITY-CONNECTION-STRING' @secure() param sqlAdminPassword string @secure() param appUserPassword string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { - name: '${abbrs.sqlServers}${resourceToken}-Identity' +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name location: location tags: tags properties: { @@ -29,7 +26,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource database 'databases' = { - name: dbName + name: databaseName location: location } @@ -46,7 +43,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { - name: 'script-${resourceToken}-Identity' + name: '${name}-deployment-script' location: location kind: 'AzureCLI' properties: { @@ -65,7 +62,7 @@ resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' } { name: 'DBNAME' - value: dbName + value: databaseName } { name: 'DBSERVER' @@ -117,9 +114,9 @@ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { parent: keyVault - name: sqlConnectionStringKey + name: connectionStringKey properties: { - value: '${azureSqlConnectionString}; Password=${appUserPassword}' + value: '${connectionString}; Password=${appUserPassword}' } } @@ -127,5 +124,6 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } -var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' -output sqlConnectionStringKey string = sqlConnectionStringKey +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/infra/core/host/appservice-config-cosmos.bicep b/infra/core/host/appservice-config-cosmos.bicep deleted file mode 100644 index 65e91f9..0000000 --- a/infra/core/host/appservice-config-cosmos.bicep +++ /dev/null @@ -1,18 +0,0 @@ -param appServiceName string -param cosmosConnectionStringKey string = '' -param cosmosDatabaseName string = '' -param cosmosEndpoint string = '' - -module appServiceConfigCosmosSettings 'appservice-config-union.bicep' = { - name: '${appServiceName}-appservice-config-cosmos-settings' - params: { - appServiceName: appServiceName - configName: 'appsettings' - currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties - additionalConfigProperties: { - AZURE_COSMOS_CONNECTION_STRING_KEY: cosmosConnectionStringKey - AZURE_COSMOS_DATABASE_NAME: cosmosDatabaseName - AZURE_COSMOS_ENDPOINT: cosmosEndpoint - } - } -} diff --git a/infra/core/host/appservice-config-logs.bicep b/infra/core/host/appservice-config-logs.bicep deleted file mode 100644 index fde8940..0000000 --- a/infra/core/host/appservice-config-logs.bicep +++ /dev/null @@ -1,11 +0,0 @@ -param appServiceName string - -resource siteConfigLogs 'Microsoft.Web/sites/config@2022-03-01' = { - name: '${appServiceName}/logs' - properties: { - applicationLogs: { fileSystem: { level: 'Verbose' } } - detailedErrorMessages: { enabled: true } - failedRequestsTracing: { enabled: true } - httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } - } -} diff --git a/infra/core/host/appservice-config-sqlserver.bicep b/infra/core/host/appservice-config-sqlserver.bicep deleted file mode 100644 index 3d8c8d0..0000000 --- a/infra/core/host/appservice-config-sqlserver.bicep +++ /dev/null @@ -1,14 +0,0 @@ -param appServiceName string -param sqlConnectionStringKey string - -module appServiceConfigSqlServerSettings 'appservice-config-union.bicep' = { - name: '${appServiceName}-appservice-config-sqlserver-settings' - params: { - appServiceName: appServiceName - configName: 'appsettings' - currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties - additionalConfigProperties: { - AZURE_SQL_CONNECTION_STRING_KEY: sqlConnectionStringKey - } - } -} diff --git a/infra/core/host/appservice-config-union.bicep b/infra/core/host/appservice-config-union.bicep deleted file mode 100644 index 3e0bcee..0000000 --- a/infra/core/host/appservice-config-union.bicep +++ /dev/null @@ -1,9 +0,0 @@ -param additionalConfigProperties object -param appServiceName string -param configName string -param currentConfigProperties object - -resource siteConfigUnion 'Microsoft.Web/sites/config@2022-03-01' = { - name: '${appServiceName}/${configName}' - properties: union(currentConfigProperties, additionalConfigProperties) -} diff --git a/infra/core/host/appservice-dotnet.bicep b/infra/core/host/appservice-dotnet.bicep deleted file mode 100644 index 012a452..0000000 --- a/infra/core/host/appservice-dotnet.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'DOTNETCORE|6.0' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = false -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-dotnet' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-node.bicep b/infra/core/host/appservice-node.bicep deleted file mode 100644 index 6f56d46..0000000 --- a/infra/core/host/appservice-node.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'NODE|16-lts' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = false -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-node' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-python.bicep b/infra/core/host/appservice-python.bicep deleted file mode 100644 index 767c127..0000000 --- a/infra/core/host/appservice-python.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'PYTHON|3.8' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = true -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-python' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice.bicep b/infra/core/host/appservice.bicep index 3eece63..62e34a6 100644 --- a/infra/core/host/appservice.bicep +++ b/infra/core/host/appservice.bicep @@ -1,34 +1,42 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config param allowedOrigins array = [] param alwaysOn bool = true param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string param appSettings object = {} param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') param functionAppScaleLimit int = -1 -param keyVaultName string = '' -param kind string = 'app,linux' -param linuxFxVersion string = '' -param managedIdentity bool = !(empty(keyVaultName)) +param linuxFxVersion string = runtimeNameAndVersion param minimumElasticInstanceCount int = -1 param numberOfWorkers int = -1 param scmDoBuildDuringDeployment bool = false -param serviceName string param use32BitWorkerProcess bool = false -var abbrs = loadJsonContent('../../abbreviations.json') -var tags = { 'azd-env-name': environmentName } -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) - -var prefix = contains(kind, 'function') ? abbrs.webSitesFunctions : abbrs.webSitesAppService - resource appService 'Microsoft.Web/sites@2022-03-01' = { - name: '${prefix}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) + tags: tags kind: kind properties: { serverFarmId: appServicePlanId @@ -49,41 +57,30 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = { httpsOnly: true } - identity: managedIdentity ? { type: 'SystemAssigned' } : null + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } - resource appSettings 'config' = { + resource configAppSettings 'config' = { name: 'appsettings' - properties: union({ + properties: union(appSettings, + { SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) }, - !(empty(applicationInsightsName)) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, - !(empty(keyVaultName)) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) } -} -module appSettingsUnion 'appservice-config-union.bicep' = if (!empty(appSettings)) { - name: '${serviceName}-app-settings-union' - params: { - appServiceName: appService.name - configName: 'appsettings' - currentConfigProperties: appService::appSettings.list().properties - additionalConfigProperties: appSettings - } -} - -module siteConfigLogs 'appservice-config-logs.bicep' = { - name: '${serviceName}-appservice-config-logs' - params: { - appServiceName: appService.name - } -} - -module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { - name: '${serviceName}-appservice-keyvault-access' - params: { - principalId: appService.identity.principalId - environmentName: environmentName - location: location + resource configLogs 'config' = { + name: 'logs' + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [ + configAppSettings + ] } } @@ -91,7 +88,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty( name: keyVaultName } -resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!(empty(applicationInsightsName))) { +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { name: applicationInsightsName } diff --git a/infra/core/host/appserviceplan-functions.bicep b/infra/core/host/appserviceplan-functions.bicep deleted file mode 100644 index 6693e6b..0000000 --- a/infra/core/host/appserviceplan-functions.bicep +++ /dev/null @@ -1,21 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param sku object = { - name: 'Y1' - tier: 'Dynamic' - size: 'Y1' - family: 'Y' -} - -module appServicePlanFunctions 'appserviceplan.bicep' = { - name: 'appserviceplan-functions' - params: { - environmentName: environmentName - location: location - sku: sku - kind: 'functionapp' - } -} - -output appServicePlanId string = appServicePlanFunctions.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan-sites.bicep b/infra/core/host/appserviceplan-sites.bicep deleted file mode 100644 index bd35137..0000000 --- a/infra/core/host/appserviceplan-sites.bicep +++ /dev/null @@ -1,15 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param sku object = { name: 'B1' } - -module appServicePlanSites 'appserviceplan.bicep' = { - name: 'appserviceplan-sites' - params: { - environmentName: environmentName - location: location - sku: sku - } -} - -output appServicePlanId string = appServicePlanSites.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan.bicep b/infra/core/host/appserviceplan.bicep index c72fcbd..69c35d7 100644 --- a/infra/core/host/appserviceplan.bicep +++ b/infra/core/host/appserviceplan.bicep @@ -1,16 +1,13 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param kind string = '' param reserved bool = true param sku object -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { - name: '${abbrs.webServerFarms}${resourceToken}' + name: name location: location tags: tags sku: sku @@ -20,4 +17,4 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { } } -output appServicePlanId string = appServicePlan.id +output id string = appServicePlan.id diff --git a/infra/core/host/container-app.bicep b/infra/core/host/container-app.bicep index fdfcf67..dde1bab 100644 --- a/infra/core/host/container-app.bicep +++ b/infra/core/host/container-app.bicep @@ -1,25 +1,28 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param containerAppsEnvironmentName string = '' +param containerName string = 'main' param containerRegistryName string = '' param env array = [] param external bool = true param imageName string param keyVaultName string = '' -param managedIdentity bool = !(empty(keyVaultName)) +param managedIdentity bool = !empty(keyVaultName) param targetPort int = 80 -param serviceName string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +@description('CPU cores allocated to a single container instance, e.g. 0.5') +param containerCpuCoreCount string = '0.5' + +@description('Memory allocated to a single container instance, e.g. 1Gi') +param containerMemory string = '1.0Gi' resource app 'Microsoft.App/containerApps@2022-03-01' = { - name: '${abbrs.appContainerApps}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) - identity: managedIdentity ? { type: 'SystemAssigned' } : null + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } properties: { managedEnvironmentId: containerAppsEnvironment.id configuration: { @@ -47,33 +50,28 @@ resource app 'Microsoft.App/containerApps@2022-03-01' = { containers: [ { image: imageName - name: 'main' + name: containerName env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } } ] } } } -module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { - name: '${serviceName}-appservice-keyvault-access' - params: { - environmentName: environmentName - location: location - keyVaultName: keyVaultName - principalId: app.identity.principalId - } -} - resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = { - name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + name: containerAppsEnvironmentName } // 2022-02-01-preview needed for anonymousPullEnabled resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { - name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + name: containerRegistryName } output identityPrincipalId string = managedIdentity ? app.identity.principalId : '' +output imageName string = imageName output name string = app.name output uri string = 'https://${app.properties.configuration.ingress.fqdn}' diff --git a/infra/core/host/container-apps-environment.bicep b/infra/core/host/container-apps-environment.bicep index b1e0578..2dd858c 100644 --- a/infra/core/host/container-apps-environment.bicep +++ b/infra/core/host/container-apps-environment.bicep @@ -1,15 +1,11 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param containerAppsEnvironmentName string = '' param logAnalyticsWorkspaceName string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { - name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + name: name location: location tags: tags properties: { @@ -23,8 +19,8 @@ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' } } -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { name: logAnalyticsWorkspaceName } -output containerAppsEnvironmentName string = containerAppsEnvironment.name +output name string = containerAppsEnvironment.name diff --git a/infra/core/host/container-apps.bicep b/infra/core/host/container-apps.bicep index a916c7f..395af70 100644 --- a/infra/core/host/container-apps.bicep +++ b/infra/core/host/container-apps.bicep @@ -1,30 +1,30 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param containerAppsEnvironmentName string = '' -param containerAppsGroupName string = 'app' param containerRegistryName string = '' param logAnalyticsWorkspaceName string = '' module containerAppsEnvironment 'container-apps-environment.bicep' = { - name: '${containerAppsGroupName}-container-apps-environment' + name: '${name}-container-apps-environment' params: { - environmentName: environmentName + name: containerAppsEnvironmentName location: location - containerAppsEnvironmentName: containerAppsEnvironmentName + tags: tags logAnalyticsWorkspaceName: logAnalyticsWorkspaceName } } module containerRegistry 'container-registry.bicep' = { - name: '${containerAppsGroupName}-container-registry' + name: '${name}-container-registry' params: { - environmentName: environmentName + name: containerRegistryName location: location - containerRegistryName: containerRegistryName + tags: tags } } -output containerAppsEnvironmentName string = containerAppsEnvironment.outputs.containerAppsEnvironmentName -output containerRegistryEndpoint string = containerRegistry.outputs.containerRegistryEndpoint -output containerRegistryName string = containerRegistry.outputs.containerRegistryName +output environmentName string = containerAppsEnvironment.outputs.name +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/infra/core/host/container-registry.bicep b/infra/core/host/container-registry.bicep index 115da50..01c3213 100644 --- a/infra/core/host/container-registry.bicep +++ b/infra/core/host/container-registry.bicep @@ -1,9 +1,9 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param adminUserEnabled bool = true param anonymousPullEnabled bool = false -param containerRegistryName string = '' param dataEndpointEnabled bool = false param encryption object = { status: 'disabled' @@ -11,17 +11,13 @@ param encryption object = { param networkRuleBypassOptions string = 'AzureServices' param publicNetworkAccess string = 'Enabled' param sku object = { - name: 'Standard' + name: 'Basic' } param zoneRedundancy string = 'Disabled' -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - // 2022-02-01-preview needed for anonymousPullEnabled resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { - name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + name: name location: location tags: tags sku: sku @@ -36,5 +32,5 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-pr } } -output containerRegistryEndpoint string = containerRegistry.properties.loginServer -output containerRegistryName string = containerRegistry.name +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/infra/core/host/functions-node.bicep b/infra/core/host/functions-node.bicep deleted file mode 100644 index e4731f4..0000000 --- a/infra/core/host/functions-node.bicep +++ /dev/null @@ -1,34 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'NODE|16' -param managedIdentity bool = !(empty(keyVaultName)) -param serviceName string -param storageAccountName string - -module functions 'functions.bicep' = { - name: '${serviceName}-functions-node' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - functionsWorkerRuntime: 'node' - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - serviceName: serviceName - storageAccountName: storageAccountName - } -} - -output identityPrincipalId string = functions.outputs.identityPrincipalId -output name string = functions.outputs.name -output uri string = functions.outputs.uri diff --git a/infra/core/host/functions-python.bicep b/infra/core/host/functions-python.bicep deleted file mode 100644 index 2ff79bd..0000000 --- a/infra/core/host/functions-python.bicep +++ /dev/null @@ -1,34 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'PYTHON|3.8' -param managedIdentity bool = !(empty(keyVaultName)) -param serviceName string -param storageAccountName string - -module functions 'functions.bicep' = { - name: '${serviceName}-functions-python' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - functionsWorkerRuntime: 'python' - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - serviceName: serviceName - storageAccountName: storageAccountName - } -} - -output identityPrincipalId string = functions.outputs.identityPrincipalId -output name string = functions.outputs.name -output uri string = functions.outputs.uri diff --git a/infra/core/host/functions.bicep b/infra/core/host/functions.bicep index 818485f..28a581b 100644 --- a/infra/core/host/functions.bicep +++ b/infra/core/host/functions.bicep @@ -1,41 +1,63 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param allowedOrigins array = [] -param alwaysOn bool = false +// Reference Properties param applicationInsightsName string = '' param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' param appSettings object = {} param clientAffinityEnabled bool = false -param functionAppScaleLimit int = 200 -param functionsExtensionVersion string = '~4' -param functionsWorkerRuntime string -param kind string = 'functionapp,linux' -param linuxFxVersion string = '' -param keyVaultName string = '' -param managedIdentity bool = !(empty(keyVaultName)) -param minimumElasticInstanceCount int = 0 -param numberOfWorkers int = 1 +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 param scmDoBuildDuringDeployment bool = true -param serviceName string -param storageAccountName string param use32BitWorkerProcess bool = false module functions 'appservice.bicep' = { - name: '${serviceName}-functions' + name: '${name}-functions' params: { - environmentName: environmentName + name: name location: location + tags: tags allowedOrigins: allowedOrigins alwaysOn: alwaysOn + appCommandLine: appCommandLine applicationInsightsName: applicationInsightsName appServicePlanId: appServicePlanId appSettings: union(appSettings, { AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' - FUNCTIONS_EXTENSION_VERSION: functionsExtensionVersion - FUNCTIONS_WORKER_RUNTIME: functionsWorkerRuntime + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName }) clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild functionAppScaleLimit: functionAppScaleLimit keyVaultName: keyVaultName kind: kind @@ -43,8 +65,10 @@ module functions 'appservice.bicep' = { managedIdentity: managedIdentity minimumElasticInstanceCount: minimumElasticInstanceCount numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName use32BitWorkerProcess: use32BitWorkerProcess } } diff --git a/infra/core/host/staticwebapp.bicep b/infra/core/host/staticwebapp.bicep index 3537fa9..91c2d0d 100644 --- a/infra/core/host/staticwebapp.bicep +++ b/infra/core/host/staticwebapp.bicep @@ -1,20 +1,16 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param serviceName string param sku object = { name: 'Free' tier: 'Free' } -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource web 'Microsoft.Web/staticSites@2022-03-01' = { - name: '${abbrs.webStaticSites}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) + tags: tags sku: sku properties: { provider: 'Custom' diff --git a/infra/core/monitor/applicationinsights-dashboard.bicep b/infra/core/monitor/applicationinsights-dashboard.bicep index 11a6512..b7af2c1 100644 --- a/infra/core/monitor/applicationinsights-dashboard.bicep +++ b/infra/core/monitor/applicationinsights-dashboard.bicep @@ -1,14 +1,11 @@ -param environmentName string -param location string = resourceGroup().location +param name string param applicationInsightsName string - -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +param location string = resourceGroup().location +param tags object = {} // 2020-09-01-preview because that is the latest valid version resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { - name: '${abbrs.portalDashboards}${resourceToken}' + name: name location: location tags: tags properties: { diff --git a/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep index b9ce1fd..f76b292 100644 --- a/infra/core/monitor/applicationinsights.bicep +++ b/infra/core/monitor/applicationinsights.bicep @@ -1,13 +1,12 @@ -param environmentName string +param name string +param dashboardName string param location string = resourceGroup().location +param tags object = {} + param logAnalyticsWorkspaceId string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { - name: '${abbrs.insightsComponents}${resourceToken}' + name: name location: location tags: tags kind: 'web' @@ -20,11 +19,12 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = { name: 'application-insights-dashboard' params: { - environmentName: environmentName + name: dashboardName location: location applicationInsightsName: applicationInsights.name } } -output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString -output applicationInsightsName string = applicationInsights.name +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/infra/core/monitor/loganalytics.bicep b/infra/core/monitor/loganalytics.bicep index a36912c..770544c 100644 --- a/infra/core/monitor/loganalytics.bicep +++ b/infra/core/monitor/loganalytics.bicep @@ -1,12 +1,9 @@ -param environmentName string +param name string param location string = resourceGroup().location - -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +param tags object = {} resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { - name: '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + name: name location: location tags: tags properties: any({ @@ -20,5 +17,5 @@ resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-previ }) } -output logAnalyticsWorkspaceId string = logAnalytics.id -output logAnalyticsWorkspaceName string = logAnalytics.name +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep index 1c5ae3e..96ba11e 100644 --- a/infra/core/monitor/monitoring.bicep +++ b/infra/core/monitor/monitoring.bicep @@ -1,24 +1,31 @@ -param environmentName string +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string param location string = resourceGroup().location +param tags object = {} module logAnalytics 'loganalytics.bicep' = { name: 'loganalytics' params: { - environmentName: environmentName + name: logAnalyticsName location: location + tags: tags } } module applicationInsights 'applicationinsights.bicep' = { name: 'applicationinsights' params: { - environmentName: environmentName + name: applicationInsightsName location: location - logAnalyticsWorkspaceId: logAnalytics.outputs.logAnalyticsWorkspaceId + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id } } -output applicationInsightsConnectionString string = applicationInsights.outputs.applicationInsightsConnectionString -output applicationInsightsName string = applicationInsights.outputs.applicationInsightsName -output logAnalyticsWorkspaceId string = logAnalytics.outputs.logAnalyticsWorkspaceId -output logAnalyticsWorkspaceName string = logAnalytics.outputs.logAnalyticsWorkspaceName +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep index 30c00f4..96c9cf7 100644 --- a/infra/core/security/keyvault-access.bicep +++ b/infra/core/security/keyvault-access.bicep @@ -1,16 +1,12 @@ -param environmentName string -param location string = resourceGroup().location +param name string = 'add' param keyVaultName string = '' param permissions object = { secrets: [ 'get', 'list' ] } param principalId string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) - resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { parent: keyVault - name: 'add' + name: name properties: { accessPolicies: [ { objectId: principalId @@ -21,5 +17,5 @@ resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-0 } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + name: keyVaultName } diff --git a/infra/core/security/keyvault.bicep b/infra/core/security/keyvault.bicep index 96b8e84..0eb4a86 100644 --- a/infra/core/security/keyvault.bicep +++ b/infra/core/security/keyvault.bicep @@ -1,15 +1,11 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param keyVaultName string = '' param principalId string = '' -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { - name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + name: name location: location tags: tags properties: { @@ -25,5 +21,5 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } } -output keyVaultEndpoint string = keyVault.properties.vaultUri -output keyVaultName string = keyVault.name +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/infra/core/storage/storage-account.bicep b/infra/core/storage/storage-account.bicep index 7d2eb91..a41972c 100644 --- a/infra/core/storage/storage-account.bicep +++ b/infra/core/storage/storage-account.bicep @@ -1,17 +1,15 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param allowBlobPublicAccess bool = false +param containers array = [] param kind string = 'StorageV2' param minimumTlsVersion string = 'TLS1_2' param sku object = { name: 'Standard_LRS' } -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { - name: '${abbrs.storageStorageAccounts}${resourceToken}' +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name location: location tags: tags kind: kind @@ -24,6 +22,17 @@ resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { defaultAction: 'Allow' } } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } } output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/infra/main.bicep b/infra/main.bicep index a47a36f..55f8ddd 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -9,6 +9,18 @@ param environmentName string @description('Primary location for all resources') param location string +// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param resourceGroupName string = '' +param webServiceName string = '' +param sqlServer1Name string = 'sqlServer-catalog-01' +param sqlServer2Name string = 'sqlServer-identity-01' +param sqlDatabaseName string = '' +param appServicePlanName string = '' +param keyVaultName string = '' + @description('Id of the user or app to assign application roles') param principalId string = '' @@ -21,26 +33,86 @@ param sqlAdminPassword string param appUserPassword string var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) var tags = { 'azd-env-name': environmentName } +// Organize resources in a resource group resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { - name: '${abbrs.resourcesResourceGroups}${environmentName}' + name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' location: location tags: tags } -module resources 'resources.bicep' = { - name: 'resources' +// The application frontend +module web './app/web.bicep' = { + name: 'web' scope: rg params: { - environmentName: environmentName + name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}' location: location - principalId: principalId + tags: tags + appServicePlanId: appServicePlan.outputs.id + } +} + +// The application database: Catalog +module sqlServer1 './app/catalog-db.bicep' = { + name: 'sql-catalog' + scope: rg + params: { + name: !empty(sqlServer1Name) ? sqlServer1Name : '${abbrs.sqlServers}${resourceToken}' + databaseName: sqlDatabaseName + location: location + tags: tags sqlAdminPassword: sqlAdminPassword appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.name + } +} + +// The application database: Identity +module sqlServer2 './app/identity-db.bicep' = { + name: 'sql-identity' + scope: rg + params: { + name: !empty(sqlServer2Name) ? sqlServer2Name : '${abbrs.sqlServers}${resourceToken}' + databaseName: sqlDatabaseName + location: location + tags: tags + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.name + } +} + + + +// Store secrets in a keyvault +module keyVault './core/security/keyvault.bicep' = { + name: 'keyvault' + scope: rg + params: { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + location: location + tags: tags + principalId: principalId + } +} + + +// Create an App Service Plan to group applications under the same payment plan and SKU +module appServicePlan './core/host/appserviceplan.bicep' = { + name: 'appserviceplan' + scope: rg + params: { + name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}' + location: location + tags: tags + sku: { + name: 'B1' + } } } output AZURE_LOCATION string = location output AZURE_TENANT_ID string = tenant().tenantId -output REACT_APP_WEB_BASE_URL string = resources.outputs.WEB_URI diff --git a/infra/resources.bicep b/infra/resources.bicep deleted file mode 100644 index 0eafb44..0000000 --- a/infra/resources.bicep +++ /dev/null @@ -1,82 +0,0 @@ -param environmentName string -param location string = resourceGroup().location -param principalId string = '' - -@secure() -param sqlAdminPassword string - -@secure() -param appUserPassword string - -// The application frontend -module web './app/web.bicep' = { - name: 'web' - params: { - environmentName: environmentName - location: location - appServicePlanId: appServicePlan.outputs.appServicePlanId - } -} - -// The application database: Catalog -module sqlServer1 './app/dbCatalog.bicep' = { - name: 'sqlCatalog' - params: { - environmentName: environmentName - location: location - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - keyVaultName: keyVault.outputs.keyVaultName - } -} - -// The application database: Identity -module sqlServer2 './app/dbIdentity.bicep' = { - name: 'sqlIdentity' - params: { - environmentName: environmentName - location: location - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - keyVaultName: keyVault.outputs.keyVaultName - } -} - -// Configure web to use sqlCatalog -module apiSqlServerConfig1 './core/host/appservice-config-sqlserver.bicep' = { - name: 'web-sqlserver-config-1' - params: { - appServiceName: web.outputs.WEB_NAME - sqlConnectionStringKey: sqlServer1.outputs.sqlConnectionStringKey - } -} - -// Configure web to use sqlIdentity -module apiSqlServerConfig2 './core/host/appservice-config-sqlserver.bicep' = { - name: 'web-sqlserver-config-2' - params: { - appServiceName: web.outputs.WEB_NAME - sqlConnectionStringKey: sqlServer2.outputs.sqlConnectionStringKey - } -} - -// Store secrets in a keyvault -module keyVault './core/security/keyvault.bicep' = { - name: 'keyvault' - params: { - environmentName: environmentName - location: location - principalId: principalId - } -} - -// Create an App Service Plan to group applications under the same payment plan and SKU -module appServicePlan './core/host/appserviceplan-sites.bicep' = { - name: 'appserviceplan' - params: { - environmentName: environmentName - location: location - } -} - -output WEB_URI string = web.outputs.WEB_URI