Skip to content

Commit

Permalink
Support for integrating with existing networking; deployment script f…
Browse files Browse the repository at this point in the history
…ixes for cron and time zones (#88)

* Existing VNet use for WebApp, Storage, KV, MySQL 
* Update deploy.sh: using env vars for DB settings
  Using /home/site/ini for additional PHP INI
* Updated deployment scripts
* Use conditional access .?
* Bicep linting
* Make web app PE optional
* Organize web app settings
* Update default SKU to B1ms
* Only MySQL version 8.0.21 supported
* Export db env vars to /etc/environment for cron
* Configure `date.timezone` in php.ini on startup
* Support for time zone setting
  • Loading branch information
SvenAelterman authored Nov 12, 2024
1 parent ffa5130 commit 7434d50
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 116 deletions.
2 changes: 1 addition & 1 deletion deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ $DeploymentResult = New-AzDeployment @CmdLetParameters
if ($DeploymentResult.ProvisioningState -eq 'Succeeded') {
Write-Host "🔥 Deployment succeeded."

$DeploymentResult.Outputs
$DeploymentResult.Outputs | Format-Table -Property Key, @{Name = 'Value'; Expression = { $_.Value.Value } }
}
else {
$DeploymentResult
Expand Down
2 changes: 2 additions & 0 deletions main-sample.bicepparam
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ param smtpFromEmailAddress = '<Specify valid SMTP From Email Address>'
// This parameter is required to ensure the parameter file is valid, but should be blank so the password doesn't leak.
// A new password is generated for each deployment and stored in Key Vault.
param sqlPassword = ''

param appServiceTimeZone = 'UTC'
115 changes: 83 additions & 32 deletions main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ param prerequisiteCommand string = '/home/startup.sh'

param deploymentTime string = utcNow()

param enableAppServicePrivateEndpoint bool = true

@description('The password to use for the MySQL Flexible Server admin account \'sqladmin\'.')
@secure()
param sqlPassword string
Expand All @@ -57,9 +59,30 @@ param smtpPort string = ''
@description('The email address to use as the sender for outgoing emails.')
param smtpFromEmailAddress string = ''

param existingPrivateDnsZonesResourceGroupId string = ''
param existingVirtualNetworkId string = ''

param appServiceTimeZone string = 'UTC'

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 rgNamingStructure = replace(
replace(
replace(
replace(replace(namingConvention, '{rtype}', 'rg'), '{workloadName}', '${workloadName}-{rgName}'),
'{loc}',
location
),
'{seq}',
sequenceFormatted
),
'{env}',
environment
)
// The name of the VNet is either a new name or the name of the existing VNet parsed from the resource ID
var vnetName = empty(existingVirtualNetworkId)
? nameModule[0].outputs.shortName
: split(existingVirtualNetworkId, '/')[8]

var strgName = nameModule[1].outputs.shortName
var webAppName = nameModule[2].outputs.shortName
var kvName = nameModule[3].outputs.shortName
Expand All @@ -71,8 +94,10 @@ var lawName = nameModule[8].outputs.shortName

var deploymentNameStructure = '${workloadName}-${environment}-${sequenceFormatted}-{rtype}-${deploymentTime}'

var subnets = {
// TODO: Define type
param subnets object = {
// TODO: Define securityRules
// TODO: Add existingSubnetName property for existing subnet
PrivateLinkSubnet: {
addressPrefix: cidrSubnet(vnetAddressSpace, 27, 0)
serviceEndpoints: [
Expand Down Expand Up @@ -185,18 +210,20 @@ var resourceTypes = [
]

@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 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)
Expand All @@ -205,7 +232,7 @@ module rolesModule './modules/common/roles.bicep' = {
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 additionalSecretNames = [storageAccountKeySecretName]
var secretNames = concat(defaultSecretNames, additionalSecretNames)

// The output will be in alphabetical order
Expand All @@ -218,7 +245,7 @@ module kvSecretReferencesModule './modules/common/appSvcKeyVaultRefs.bicep' = {
}
}

module virtualNetworkModule './modules/networking/main.bicep' = {
module virtualNetworkModule './modules/networking/main.bicep' = if (empty(existingVirtualNetworkId)) {
name: take(replace(deploymentNameStructure, '{rtype}', 'network'), 64)
params: {
resourceGroupName: replace(rgNamingStructure, '{rgName}', 'network')
Expand Down Expand Up @@ -254,18 +281,29 @@ module monitoring './modules/monitoring/main.bicep' = {
}
}

var privateEndpointSubnetId = empty(existingVirtualNetworkId)
? virtualNetworkModule.outputs.subnets.PrivateLinkSubnet.id
: '${existingVirtualNetworkId}/subnets/${subnets.PrivateLinkSubnet.existingSubnetName}'

var virtualNetworkId = empty(existingVirtualNetworkId)
? virtualNetworkModule.outputs.virtualNetworkId
: existingVirtualNetworkId

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
peSubnetId: privateEndpointSubnetId
storageContainerName: 'redcap'
kind: 'StorageV2'
storageAccountSku: 'Standard_LRS'
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId

virtualNetworkId: virtualNetworkId
privateDnsZoneName: 'privatelink.blob.${az.environment().suffixes.storage}'
existingPrivateDnsZonesResourceGroupId: existingPrivateDnsZonesResourceGroupId

tags: tags
customTags: {
workloadType: 'storageAccount'
Expand All @@ -288,8 +326,9 @@ module keyVaultModule './modules/kv/main.bicep' = {
customTags: {
workloadType: 'keyVault'
}
peSubnetId: virtualNetworkModule.outputs.subnets.PrivateLinkSubnet.id
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
peSubnetId: privateEndpointSubnetId
virtualNetworkId: virtualNetworkId
existingPrivateDnsZonesResourceGroupId: existingPrivateDnsZonesResourceGroupId
roleAssignments: [
{
RoleDefinitionId: rolesModule.outputs.roles['Key Vault Administrator']
Expand All @@ -298,6 +337,7 @@ module keyVaultModule './modules/kv/main.bicep' = {
{
RoleDefinitionId: rolesModule.outputs.roles['Key Vault Secrets User']
objectId: uamiModule.outputs.principalId
principtalType: 'ServicePrincipal'
}
]
privateDnsZoneName: 'privatelink.vaultcore.azure.net'
Expand All @@ -318,12 +358,15 @@ module mySqlModule './modules/sql/main.bicep' = {
customTags: {
workloadType: 'mySqlFlexibleServer'
}
skuName: 'Standard_B1s'
skuName: 'Standard_B1ms'
SkuTier: 'Burstable'
StorageSizeGB: 20
StorageIops: 396
peSubnetId: virtualNetworkModule.outputs.subnets.MySQLFlexSubnet.id
peSubnetId: empty(existingVirtualNetworkId)
? virtualNetworkModule.outputs.subnets.MySQLFlexSubnet.id
: '${existingVirtualNetworkId}/subnets/${subnets.MySQLFlexSubnet.existingSubnetName}'
privateDnsZoneName: 'privatelink.mysql.database.azure.com'
existingPrivateDnsZonesResourceGroupId: existingPrivateDnsZonesResourceGroupId
sqlAdminUser: sqlAdmin
sqlAdminPasword: sqlPassword
mysqlVersion: '8.0.21'
Expand All @@ -341,7 +384,7 @@ module mySqlModule './modules/sql/main.bicep' = {
database_charset: 'utf8'
database_collation: 'utf8_general_ci'

virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
virtualNetworkId: virtualNetworkId

deploymentNameStructure: deploymentNameStructure
}
Expand All @@ -351,8 +394,8 @@ resource webAppResourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: replace(rgNamingStructure, '{rgName}', 'web')
location: location
tags: union(tags, {
workloadType: 'web'
})
workloadType: 'web'
})
}

module webAppModule './modules/webapp/main.bicep' = {
Expand All @@ -362,19 +405,21 @@ module webAppModule './modules/webapp/main.bicep' = {
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
// Deploy as P0V3 to ensure the deployment runs on a scale unit that supports P_v3 for future upgrades. GH issue #50
skuName: 'P0V3'
peSubnetId: privateEndpointSubnetId
appInsights_connectionString: monitoring.outputs.appInsightsResourceId
appInsights_instrumentationKey: monitoring.outputs.appInsightsInstrumentationKey
linuxFxVersion: 'php|8.2'
tags: tags
customTags: {
workloadType: 'webApp'
}

existingPrivateDnsZonesResourceGroupId: existingPrivateDnsZonesResourceGroupId
privateDnsZoneName: 'privatelink.azurewebsites.net'
virtualNetworkId: virtualNetworkModule.outputs.virtualNetworkId
virtualNetworkId: virtualNetworkId

redcapZipUrl: redcapZipUrl
dbHostName: mySqlModule.outputs.fqdn
dbName: mySqlModule.outputs.databaseName
Expand All @@ -390,7 +435,9 @@ module webAppModule './modules/webapp/main.bicep' = {
storageAccountName: storageAccountModule.outputs.name

// Enable VNet integration
integrationSubnetId: virtualNetworkModule.outputs.subnets.IntegrationSubnet.id
integrationSubnetId: empty(existingVirtualNetworkId)
? virtualNetworkModule.outputs.subnets.IntegrationSubnet.id
: '${existingVirtualNetworkId}/subnets/${subnets.IntegrationSubnet.existingSubnetName}'

scmRepoUrl: scmRepoUrl
scmRepoBranch: scmRepoBranch
Expand All @@ -403,6 +450,10 @@ module webAppModule './modules/webapp/main.bicep' = {
deploymentNameStructure: deploymentNameStructure

uamiId: uamiModule.outputs.id

enablePrivateEndpoint: enableAppServicePrivateEndpoint

timeZone: appServiceTimeZone
}
}

Expand All @@ -416,5 +467,5 @@ module uamiModule 'modules/uami/main.bicep' = {
}
}

// The web app URL
// // The web app URL
output webAppUrl string = webAppModule.outputs.webAppUrl
25 changes: 16 additions & 9 deletions modules/kv/kv.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ param peSubnetId string

param deploymentNameStructure string

param roleAssignments array = [ {
param roleAssignments array = [
{
RoleDefinitionId: ''
objectId: ''
} ]
}
]
param privateDnsZoneId string

@secure()
Expand Down Expand Up @@ -60,14 +62,19 @@ module keyVaultSecretsModule 'kvSecrets.bicep' = {
}
}

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for roleAssignment in roleAssignments: {
scope: keyVault
name: guid(keyVault.id, roleAssignment.objectId, roleAssignment.RoleDefinitionId)
properties: {
roleDefinitionId: roleAssignment.RoleDefinitionId
principalId: roleAssignment.objectId
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [
for roleAssignment in roleAssignments: {
scope: keyVault
name: guid(keyVault.id, roleAssignment.objectId, roleAssignment.RoleDefinitionId)
properties: {
roleDefinitionId: roleAssignment.RoleDefinitionId
principalId: roleAssignment.objectId
principalType: contains(roleAssignment, 'principalType') && !empty(roleAssignment.principalType)
? roleAssignment.principalType
: null
}
}
}]
]

resource pekeyVault 'Microsoft.Network/privateEndpoints@2022-07-01' = {
name: 'pe-${keyVaultName}'
Expand Down
14 changes: 10 additions & 4 deletions modules/kv/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ param tags object
param customTags object
param keyVaultName string
param peSubnetId string
param roleAssignments array = [ {
param roleAssignments array = [
{
RoleDefinitionId: ''
objectId: ''
} ]
}
]
@secure()
param secrets object
param privateDnsZoneName string
param virtualNetworkId string

param existingPrivateDnsZonesResourceGroupId string = ''

param deploymentNameStructure string

var mergeTags = union(tags, customTags)
Expand All @@ -33,14 +37,16 @@ module keyVaultModule './kv.bicep' = {
location: location
tags: tags
peSubnetId: peSubnetId
privateDnsZoneId: keyVaultPrivateDnsModule.outputs.privateDnsId
privateDnsZoneId: empty(existingPrivateDnsZonesResourceGroupId)
? keyVaultPrivateDnsModule.outputs.privateDnsId
: '${existingPrivateDnsZonesResourceGroupId}/providers/Microsoft.Network/privateDnsZones/${privateDnsZoneName}'
secrets: secrets
roleAssignments: roleAssignments
deploymentNameStructure: deploymentNameStructure
}
}

module keyVaultPrivateDnsModule '../pdns/main.bicep' = {
module keyVaultPrivateDnsModule '../pdns/main.bicep' = if (empty(existingPrivateDnsZonesResourceGroupId)) {
name: take(replace(deploymentNameStructure, '{rtype}', 'kv-dns'), 64)
scope: resourceGroup
params: {
Expand Down
1 change: 1 addition & 0 deletions modules/networking/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ module vNetModule 'vnet.bicep' = {

output virtualNetworkId string = vNetModule.outputs.virtualNetworkId
output subnets object = reduce(vNetModule.outputs.subnets, {}, (cur, next) => union(cur, next))
output resourceGroupId string = resourceGroup.id
Loading

0 comments on commit 7434d50

Please sign in to comment.