azure-redcap-paas/main.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