Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@description('Optional. The location to deploy resources to.')
param location string = resourceGroup().location

@description('Required. The name of the Virtual Network to create.')
param virtualNetworkName string

@description('Required. The name of the Managed Identity to create.')
param managedIdentityName string

@description('Required. The name of the Server Farm to create.')
param serverFarmName string

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = {
name: virtualNetworkName
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/24'
]
}
subnets: [
{
name: 'defaultSubnet'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
]
}
}

resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.azurewebsites.net'
location: 'global'

resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = {
name: '${virtualNetwork.name}-vnetlink'
location: 'global'
properties: {
virtualNetwork: {
id: virtualNetwork.id
}
registrationEnabled: false
}
}
}

resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: managedIdentityName
location: location
}

resource serverFarm 'Microsoft.Web/serverfarms@2022-03-01' = {
name: serverFarmName
location: location
sku: {
name: 'S1'
tier: 'Standard'
size: 'S1'
family: 'S'
capacity: 1
}
properties: {}
}

@description('The resource ID of the created Virtual Network Subnet.')
output subnetResourceId string = virtualNetwork.properties.subnets[0].id

@description('The principal ID of the created Managed Identity.')
output managedIdentityPrincipalId string = managedIdentity.properties.principalId

@description('The resource ID of the created Managed Identity.')
output managedIdentityResourceId string = managedIdentity.id

@description('The resource ID of the created Server Farm.')
output serverFarmResourceId string = serverFarm.id

@description('The resource ID of the created Private DNS Zone.')
output privateDNSZoneResourceId string = privateDNSZone.id
111 changes: 111 additions & 0 deletions modules/Microsoft.Web/sites/.test/webAppSlots/deploy.test.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
targetScope = 'subscription'

// ========== //
// Parameters //
// ========== //
@description('Optional. The name of the resource group to deploy for testing purposes.')
@maxLength(90)
param resourceGroupName string = 'ms.web.sites-${serviceShort}-rg'

@description('Optional. The location to deploy resources to.')
param location string = deployment().location

@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.')
param serviceShort string = 'wswa'

@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).')
param enableDefaultTelemetry bool = true

// =========== //
// Deployments //
// =========== //

// General resources
// =================
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: location
}

module resourceGroupResources 'dependencies.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-paramNested'
params: {
virtualNetworkName: 'dep-<<namePrefix>>-vnet-${serviceShort}'
managedIdentityName: 'dep-<<namePrefix>>-msi-${serviceShort}'
serverFarmName: 'dep-<<namePrefix>>-sf-${serviceShort}'
}
}

// Diagnostics
// ===========
module diagnosticDependencies '../../../../.shared/dependencyConstructs/diagnostic.dependencies.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-diagnosticDependencies'
params: {
storageAccountName: 'dep<<namePrefix>>diasa${serviceShort}01'
logAnalyticsWorkspaceName: 'dep-<<namePrefix>>-law-${serviceShort}'
eventHubNamespaceEventHubName: 'dep-<<namePrefix>>-evh-${serviceShort}'
eventHubNamespaceName: 'dep-<<namePrefix>>-evhns-${serviceShort}'
location: location
}
}

// ============== //
// Test Execution //
// ============== //

module testDeployment '../../deploy.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name)}-test-${serviceShort}'
params: {
enableDefaultTelemetry: enableDefaultTelemetry
name: '<<namePrefix>>${serviceShort}001'
kind: 'app'
serverFarmResourceId: resourceGroupResources.outputs.serverFarmResourceId
diagnosticLogsRetentionInDays: 7
diagnosticStorageAccountId: diagnosticDependencies.outputs.storageAccountResourceId
diagnosticWorkspaceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId
diagnosticEventHubAuthorizationRuleId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId
diagnosticEventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName
httpsOnly: true
slots: [
{
name: 'slot1'
}
]
privateEndpoints: [
{
service: 'sites'
subnetResourceId: resourceGroupResources.outputs.subnetResourceId
privateDnsZoneGroup: {
privateDNSResourceIds: [
resourceGroupResources.outputs.privateDNSZoneResourceId
]
}
}
]
roleAssignments: [
{
roleDefinitionIdOrName: 'Reader'
principalIds: [
resourceGroupResources.outputs.managedIdentityPrincipalId
]
principalType: 'ServicePrincipal'
}
]
siteConfig: {
alwaysOn: true
metadata: [
{
name: 'CURRENT_STACK'
value: 'dotnetcore'
}
]
}
systemAssignedIdentity: true
userAssignedIdentities: {
'${resourceGroupResources.outputs.managedIdentityResourceId}': {}
}
}
}
51 changes: 51 additions & 0 deletions modules/Microsoft.Web/sites/deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ param lock string = ''
@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.')
param privateEndpoints array = []

// List of slots
@description('Optional. Configuration for deployment slots for an app.')
param slots array = []

// Tags
@description('Optional. Tags of the resource.')
param tags object = {}
Expand Down Expand Up @@ -228,6 +232,44 @@ module app_authsettingsv2 'config-authsettingsv2/deploy.bicep' = if (!empty(auth
}
}

@batchSize(1)
module app_slots 'slots/deploy.bicep' = [for (slot, index) in slots: {
name: '${uniqueString(deployment().name, location)}-Slot-${slot.name}'
params: {
name: slot.name
appName: app.name
location: location
kind: kind
serverFarmResourceId: serverFarmResourceId
httpsOnly: contains(slot, 'httpsOnly') ? slot.httpsOnly : httpsOnly
appServiceEnvironmentId: !empty(appServiceEnvironmentId) ? appServiceEnvironmentId : ''
clientAffinityEnabled: contains(slot, 'clientAffinityEnabled') ? slot.clientAffinityEnabled : clientAffinityEnabled
systemAssignedIdentity: contains(slot, 'systemAssignedIdentity') ? slot.systemAssignedIdentity : systemAssignedIdentity
userAssignedIdentities: contains(slot, 'userAssignedIdentities') ? slot.userAssignedIdentities : userAssignedIdentities
keyVaultAccessIdentityResourceId: contains(slot, 'keyVaultAccessIdentityResourceId') ? slot.keyVaultAccessIdentityResourceId : keyVaultAccessIdentityResourceId
storageAccountRequired: contains(slot, 'storageAccountRequired') ? slot.storageAccountRequired : storageAccountRequired
virtualNetworkSubnetId: contains(slot, 'virtualNetworkSubnetId') ? slot.virtualNetworkSubnetId : virtualNetworkSubnetId
siteConfig: contains(slot, 'siteConfig') ? slot.siteConfig : siteConfig
storageAccountId: contains(slot, 'storageAccountId') ? slot.storageAccountId : storageAccountId
appInsightId: contains(slot, 'appInsightId') ? slot.appInsightId : appInsightId
setAzureWebJobsDashboard: contains(slot, 'setAzureWebJobsDashboard') ? slot.setAzureWebJobsDashboard : setAzureWebJobsDashboard
authSettingV2Configuration: contains(slot, 'authSettingV2Configuration') ? slot.authSettingV2Configuration : authSettingV2Configuration
enableDefaultTelemetry: enableReferencedModulesTelemetry
diagnosticLogsRetentionInDays: contains(slot, 'diagnosticLogsRetentionInDays') ? slot.diagnosticLogsRetentionInDays : diagnosticLogsRetentionInDays
diagnosticStorageAccountId: contains(slot, 'diagnosticStorageAccountId') ? slot.diagnosticStorageAccountId : diagnosticStorageAccountId
diagnosticWorkspaceId: contains(slot, 'diagnosticWorkspaceId') ? slot.diagnosticWorkspaceId : diagnosticWorkspaceId
diagnosticEventHubAuthorizationRuleId: contains(slot, 'diagnosticEventHubAuthorizationRuleId') ? slot.diagnosticEventHubAuthorizationRuleId : diagnosticEventHubAuthorizationRuleId
diagnosticEventHubName: contains(slot, 'diagnosticEventHubName') ? slot.diagnosticEventHubName : diagnosticEventHubName
diagnosticLogCategoriesToEnable: contains(slot, 'diagnosticLogCategoriesToEnable') ? slot.diagnosticLogCategoriesToEnable : diagnosticLogCategoriesToEnable
diagnosticMetricsToEnable: contains(slot, 'diagnosticMetricsToEnable') ? slot.diagnosticMetricsToEnable : diagnosticMetricsToEnable
roleAssignments: contains(slot, 'roleAssignments') ? slot.roleAssignments : roleAssignments
appSettingsKeyValuePairs: contains(slot, 'appSettingsKeyValuePairs') ? slot.appSettingsKeyValuePairs : appSettingsKeyValuePairs
lock: contains(slot, 'lock') ? slot.lock : lock
privateEndpoints: contains(slot, 'privateEndpoints') ? slot.privateEndpoints : privateEndpoints
tags: tags
}
}]

resource app_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) {
name: '${app.name}-${lock}-lock'
properties: {
Expand Down Expand Up @@ -292,12 +334,21 @@ output name string = app.name
@description('The resource ID of the site.')
output resourceId string = app.id

@description('The list of the slots.')
output slots array = [for (slot, index) in slots: app_slots[index].name]

@description('The list of the slot resource ids.')
output slotResourceIds array = [for (slot, index) in slots: app_slots[index].outputs.resourceId]

@description('The resource group the site was deployed into.')
output resourceGroupName string = resourceGroup().name

@description('The principal ID of the system assigned identity.')
output systemAssignedPrincipalId string = systemAssignedIdentity && contains(app.identity, 'principalId') ? app.identity.principalId : ''

@description('The principal ID of the system assigned identity of slots.')
output slotSystemAssignedPrincipalIds array = [for (slot, index) in slots: app_slots[index].outputs.systemAssignedPrincipalId]

@description('The location the resource was deployed into.')
output location string = app.location

Expand Down
Loading