421 строка
13 KiB
Bicep
421 строка
13 KiB
Bicep
targetScope = 'subscription'
|
|
|
|
@description('The Azure region to target for the deployment. Replaces {loc} in namingConvention.')
|
|
param location string = 'eastus'
|
|
|
|
@description('The environment designator for the deployment. Replaces {env} in namingConvention.')
|
|
@allowed([
|
|
'test'
|
|
'demo'
|
|
'prod'
|
|
])
|
|
param environment string = 'demo'
|
|
@description('The workload name. Replaces {workloadName} in namingConvention.')
|
|
param workloadName string = 'redcap'
|
|
@description('The Azure resource naming convention. Include the following placeholders (case-sensitive): {workloadName}, {env}, {rtype}, {loc}, {seq}.')
|
|
param namingConvention string = '{workloadName}-{env}-{rtype}-{loc}-{seq}'
|
|
@description('A sequence number for the deployment. Used to distinguish multiple deployed versions of the same workload. Replaces {seq} in namingConvention.')
|
|
@minValue(1)
|
|
@maxValue(99)
|
|
param sequence int = 1
|
|
|
|
@description('A valid Entra ID object ID, which will be assigned RBAC permissions on the deployed resources.')
|
|
param identityObjectId string
|
|
|
|
@description('The address space for the virtual network. Subnets will be carved out. Minimum IPv4 size: /24.')
|
|
param vnetAddressSpace string
|
|
@description('If available, the public URL to download the REDCap zip file from. Used for debugging purposes. Does not need to be specified when downloading from the REDCap community using a username and password.')
|
|
@secure()
|
|
param redcapZipUrl string = ''
|
|
@description('REDCap Community site username for downloading the REDCap zip file.')
|
|
@secure()
|
|
param redcapCommunityUsername string
|
|
|
|
@description('REDCap Community site password for downloading the REDCap zip file.')
|
|
@secure()
|
|
param redcapCommunityPassword string
|
|
@description('Github Repo URL where build scripts are downloaded from')
|
|
param scmRepoUrl string = 'https://github.com/microsoft/azure-redcap-paas'
|
|
@description('Github Repo Branch where build scripts are downloaded from')
|
|
param scmRepoBranch string = 'main'
|
|
@description('The command before build to be run on the web app with an elevated privilege. This is used to install the required packages for REDCap operation.')
|
|
param prerequisiteCommand string = '/home/startup.sh'
|
|
|
|
param deploymentTime string = utcNow()
|
|
|
|
@description('The password to use for the MySQL Flexible Server admin account \'sqladmin\'.')
|
|
@secure()
|
|
param sqlPassword string
|
|
|
|
@description('The MySQL Flexible Server admin user account name. Defaults to \'sqladmin\'.')
|
|
param sqlAdmin string = 'sqladmin'
|
|
|
|
@description('The outgoing SMTP server FQDN or IP address.')
|
|
param smtpFQDN string = ''
|
|
@description('The outgoing SMTP server port.')
|
|
param smtpPort string = ''
|
|
@description('The email address to use as the sender for outgoing emails.')
|
|
param smtpFromEmailAddress string = ''
|
|
|
|
var sequenceFormatted = format('{0:00}', sequence)
|
|
var rgNamingStructure = replace(replace(replace(replace(replace(namingConvention, '{rtype}', 'rg'), '{workloadName}', '${workloadName}-{rgName}'), '{loc}', location), '{seq}', sequenceFormatted), '{env}', environment)
|
|
var vnetName = nameModule[0].outputs.shortName
|
|
var strgName = nameModule[1].outputs.shortName
|
|
var webAppName = nameModule[2].outputs.shortName
|
|
var kvName = nameModule[3].outputs.shortName
|
|
var sqlName = nameModule[4].outputs.shortName
|
|
var planName = nameModule[5].outputs.shortName
|
|
var uamiName = nameModule[6].outputs.shortName
|
|
var dplscrName = nameModule[7].outputs.shortName
|
|
var lawName = nameModule[8].outputs.shortName
|
|
|
|
var deploymentNameStructure = '${workloadName}-${environment}-${sequenceFormatted}-{rtype}-${deploymentTime}'
|
|
|
|
var subnets = {
|
|
// TODO: Define securityRules
|
|
PrivateLinkSubnet: {
|
|
addressPrefix: cidrSubnet(vnetAddressSpace, 27, 0)
|
|
serviceEndpoints: [
|
|
{
|
|
service: 'Microsoft.KeyVault'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Storage'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
]
|
|
}
|
|
ComputeSubnet: {
|
|
addressPrefix: cidrSubnet(vnetAddressSpace, 27, 1)
|
|
serviceEndpoints: [
|
|
{
|
|
service: 'Microsoft.KeyVault'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Storage'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Web'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
]
|
|
}
|
|
IntegrationSubnet: {
|
|
// Two /27 have already been created, which add up to a /26. This the second /26 (index = 1).
|
|
addressPrefix: cidrSubnet(vnetAddressSpace, 26, 1)
|
|
serviceEndpoints: [
|
|
{
|
|
service: 'Microsoft.KeyVault'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Storage'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Web'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
]
|
|
delegation: 'Microsoft.Web/serverFarms'
|
|
}
|
|
MySQLFlexSubnet: {
|
|
// TODO: /29 seems very small
|
|
// Two /26 have been allocated; that's equivalent to sixteen /29s.
|
|
addressPrefix: cidrSubnet(vnetAddressSpace, 29, 16)
|
|
serviceEndpoints: [
|
|
{
|
|
service: 'Microsoft.KeyVault'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
{
|
|
service: 'Microsoft.Storage'
|
|
locations: [
|
|
location
|
|
]
|
|
}
|
|
]
|
|
delegation: 'Microsoft.DBforMySQL/flexibleServers'
|
|
}
|
|
}
|
|
|
|
var tags = {
|
|
workload: workloadName
|
|
environment: environment
|
|
}
|
|
|
|
var secrets = {
|
|
sqlAdminName: mySqlModule.outputs.sqlAdmin
|
|
sqlPassword: sqlPassword
|
|
redcapCommunityUsername: redcapCommunityUsername
|
|
redcapCommunityPassword: redcapCommunityPassword
|
|
}
|
|
|
|
var resourceTypes = [
|
|
'vnet'
|
|
'st'
|
|
'app'
|
|
'kv'
|
|
'mysql'
|
|
'plan'
|
|
'uami'
|
|
'dplscr'
|
|
'law'
|
|
]
|
|
|
|
@batchSize(1)
|
|
module nameModule 'modules/common/createValidAzResourceName.bicep' = [for workload in resourceTypes: {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'nameGen-${workload}'), 64)
|
|
params: {
|
|
location: location
|
|
environment: environment
|
|
namingConvention: namingConvention
|
|
resourceType: workload
|
|
sequence: sequence
|
|
workloadName: workloadName
|
|
addRandomChars: 4
|
|
}
|
|
}]
|
|
|
|
module rolesModule './modules/common/roles.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'roles'), 64)
|
|
}
|
|
|
|
var storageAccountKeySecretName = 'storageKey'
|
|
// The secrets object is converted to an array using the items() function, which alphabetically sorts it
|
|
var defaultSecretNames = map(items(secrets), s => s.key)
|
|
var additionalSecretNames = [ storageAccountKeySecretName ]
|
|
var secretNames = concat(defaultSecretNames, additionalSecretNames)
|
|
|
|
// The output will be in alphabetical order
|
|
// LATER: Output an object instead
|
|
module kvSecretReferencesModule './modules/common/appSvcKeyVaultRefs.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'kv-secrets'), 64)
|
|
params: {
|
|
keyVaultName: kvName
|
|
secretNames: secretNames
|
|
}
|
|
}
|
|
|
|
module virtualNetworkModule './modules/networking/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'network'), 64)
|
|
params: {
|
|
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'network')
|
|
virtualNetworkName: vnetName
|
|
vnetAddressPrefix: vnetAddressSpace
|
|
location: location
|
|
subnets: subnets
|
|
customDnsIPs: []
|
|
tags: tags
|
|
customTags: {
|
|
workloadType: 'networking'
|
|
}
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
}
|
|
}
|
|
|
|
module monitoring './modules/monitoring/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'monitoring'), 64)
|
|
params: {
|
|
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'monitoring')
|
|
appInsightsName: 'appInsights-${webAppName}'
|
|
logAnalyticsWorkspaceName: lawName
|
|
logAnalyticsWorkspaceSku: 'PerGB2018'
|
|
retentionInDays: 30
|
|
location: location
|
|
tags: tags
|
|
customTags: {
|
|
workloadType: 'monitoring'
|
|
}
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
}
|
|
}
|
|
|
|
module storageAccountModule './modules/storage/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'storage'), 64)
|
|
params: {
|
|
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'storage')
|
|
location: location
|
|
storageAccountName: strgName
|
|
peSubnetId: virtualNetworkModule.outputs.subnets.PrivateLinkSubnet.id
|
|
storageContainerName: 'redcap'
|
|
kind: 'StorageV2'
|
|
storageAccountSku: 'Standard_LRS'
|
|
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
|
|
privateDnsZoneName: 'privatelink.blob.${az.environment().suffixes.storage}'
|
|
tags: tags
|
|
customTags: {
|
|
workloadType: 'storageAccount'
|
|
}
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
|
|
keyVaultSecretName: storageAccountKeySecretName
|
|
keyVaultId: keyVaultModule.outputs.id
|
|
}
|
|
}
|
|
|
|
module keyVaultModule './modules/kv/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'keyVault'), 64)
|
|
params: {
|
|
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'keyVault')
|
|
keyVaultName: kvName
|
|
location: location
|
|
tags: tags
|
|
customTags: {
|
|
workloadType: 'keyVault'
|
|
}
|
|
peSubnetId: virtualNetworkModule.outputs.subnets.PrivateLinkSubnet.id
|
|
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
|
|
roleAssignments: [
|
|
{
|
|
RoleDefinitionId: rolesModule.outputs.roles['Key Vault Administrator']
|
|
objectId: identityObjectId
|
|
}
|
|
{
|
|
RoleDefinitionId: rolesModule.outputs.roles['Key Vault Secrets User']
|
|
objectId: uamiModule.outputs.principalId
|
|
}
|
|
]
|
|
privateDnsZoneName: 'privatelink.vaultcore.azure.net'
|
|
secrets: secrets
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
}
|
|
}
|
|
|
|
module mySqlModule './modules/sql/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'mysql'), 64)
|
|
params: {
|
|
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'database')
|
|
flexibleSqlServerName: sqlName
|
|
location: location
|
|
tags: tags
|
|
|
|
customTags: {
|
|
workloadType: 'mySqlFlexibleServer'
|
|
}
|
|
skuName: 'Standard_B1s'
|
|
SkuTier: 'Burstable'
|
|
StorageSizeGB: 20
|
|
StorageIops: 396
|
|
peSubnetId: virtualNetworkModule.outputs.subnets.MySQLFlexSubnet.id
|
|
privateDnsZoneName: 'privatelink.mysql.database.azure.com'
|
|
sqlAdminUser: sqlAdmin
|
|
sqlAdminPasword: sqlPassword
|
|
mysqlVersion: '8.0.21'
|
|
// TODO: Consider using workloadname + 'db'
|
|
databaseName: 'redcapdb'
|
|
|
|
roles: rolesModule.outputs.roles
|
|
|
|
uamiId: uamiModule.outputs.id
|
|
uamiPrincipalId: uamiModule.outputs.principalId
|
|
|
|
deploymentScriptName: dplscrName
|
|
|
|
// Required charset and collation for REDCap
|
|
database_charset: 'utf8'
|
|
database_collation: 'utf8_general_ci'
|
|
|
|
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
}
|
|
}
|
|
|
|
resource webAppResourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = {
|
|
name: replace(rgNamingStructure, '{rgName}', 'web')
|
|
location: location
|
|
tags: union(tags, {
|
|
workloadType: 'web'
|
|
})
|
|
}
|
|
|
|
module webAppModule './modules/webapp/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'appService'), 64)
|
|
scope: webAppResourceGroup
|
|
params: {
|
|
webAppName: webAppName
|
|
appServicePlanName: planName
|
|
location: location
|
|
// TODO: Consider deploying as P0V3 to ensure the deployment runs on a scale unit that supports P_v3 for future upgrades. GH issue #50
|
|
skuName: 'S1'
|
|
skuTier: 'Standard'
|
|
peSubnetId: virtualNetworkModule.outputs.subnets.PrivateLinkSubnet.id
|
|
appInsights_connectionString: monitoring.outputs.appInsightsResourceId
|
|
appInsights_instrumentationKey: monitoring.outputs.appInsightsInstrumentationKey
|
|
linuxFxVersion: 'php|8.2'
|
|
tags: tags
|
|
customTags: {
|
|
workloadType: 'webApp'
|
|
}
|
|
privateDnsZoneName: 'privatelink.azurewebsites.net'
|
|
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
|
|
redcapZipUrl: redcapZipUrl
|
|
dbHostName: mySqlModule.outputs.fqdn
|
|
dbName: mySqlModule.outputs.databaseName
|
|
|
|
dbUserNameSecretRef: kvSecretReferencesModule.outputs.keyVaultRefs[2]
|
|
dbPasswordSecretRef: kvSecretReferencesModule.outputs.keyVaultRefs[3]
|
|
|
|
redcapCommunityUsernameSecretRef: kvSecretReferencesModule.outputs.keyVaultRefs[1]
|
|
redcapCommunityPasswordSecretRef: kvSecretReferencesModule.outputs.keyVaultRefs[0]
|
|
|
|
storageAccountKeySecretRef: kvSecretReferencesModule.outputs.keyVaultRefs[4]
|
|
storageAccountContainerName: storageAccountModule.outputs.containerName
|
|
storageAccountName: storageAccountModule.outputs.name
|
|
|
|
// Enable VNet integration
|
|
integrationSubnetId: virtualNetworkModule.outputs.subnets.IntegrationSubnet.id
|
|
|
|
scmRepoUrl: scmRepoUrl
|
|
scmRepoBranch: scmRepoBranch
|
|
prerequisiteCommand: prerequisiteCommand
|
|
|
|
smtpFQDN: smtpFQDN
|
|
smtpFromEmailAddress: smtpFromEmailAddress
|
|
smtpPort: smtpPort
|
|
|
|
deploymentNameStructure: deploymentNameStructure
|
|
|
|
uamiId: uamiModule.outputs.id
|
|
}
|
|
}
|
|
|
|
module uamiModule 'modules/uami/main.bicep' = {
|
|
name: take(replace(deploymentNameStructure, '{rtype}', 'uami'), 64)
|
|
scope: webAppResourceGroup
|
|
params: {
|
|
tags: tags
|
|
location: location
|
|
uamiName: uamiName
|
|
}
|
|
}
|
|
|
|
// The web app URL
|
|
output webAppUrl string = webAppModule.outputs.webAppUrl
|