diff --git a/modules/Microsoft.Web/sites/.test/webAppSlots/dependencies.bicep b/modules/Microsoft.Web/sites/.test/webAppSlots/dependencies.bicep new file mode 100644 index 0000000000..542edbb9e5 --- /dev/null +++ b/modules/Microsoft.Web/sites/.test/webAppSlots/dependencies.bicep @@ -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 diff --git a/modules/Microsoft.Web/sites/.test/webAppSlots/deploy.test.bicep b/modules/Microsoft.Web/sites/.test/webAppSlots/deploy.test.bicep new file mode 100644 index 0000000000..ab71981c17 --- /dev/null +++ b/modules/Microsoft.Web/sites/.test/webAppSlots/deploy.test.bicep @@ -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-<>-vnet-${serviceShort}' + managedIdentityName: 'dep-<>-msi-${serviceShort}' + serverFarmName: 'dep-<>-sf-${serviceShort}' + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../.shared/dependencyConstructs/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep<>diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-<>-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-<>-evh-${serviceShort}' + eventHubNamespaceName: 'dep-<>-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../deploy.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '<>${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}': {} + } + } +} diff --git a/modules/Microsoft.Web/sites/deploy.bicep b/modules/Microsoft.Web/sites/deploy.bicep index d3eb91c50b..c2936ffc2b 100644 --- a/modules/Microsoft.Web/sites/deploy.bicep +++ b/modules/Microsoft.Web/sites/deploy.bicep @@ -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 = {} @@ -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: { @@ -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 diff --git a/modules/Microsoft.Web/sites/readme.md b/modules/Microsoft.Web/sites/readme.md index fa55456e0d..adbb1bf2a7 100644 --- a/modules/Microsoft.Web/sites/readme.md +++ b/modules/Microsoft.Web/sites/readme.md @@ -15,12 +15,16 @@ This module deploys a web or function app. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Authorization/locks` | [2020-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/locks` | [2017-04-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2017-04-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2020-10-01-preview](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-10-01-preview/roleAssignments) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | | `Microsoft.Network/privateEndpoints` | [2022-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-05-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2022-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-05-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Web/sites` | [2021-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/2021-03-01/sites) | | `Microsoft.Web/sites/config` | [2020-12-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/slots` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots) | +| `Microsoft.Web/sites/slots/config` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | ## Parameters @@ -58,6 +62,7 @@ This module deploys a web or function app. | `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | | `setAzureWebJobsDashboard` | bool | `[if(contains(parameters('kind'), 'functionapp'), true(), false())]` | | For function apps. If true the app settings "AzureWebJobsDashboard" will be set. If false not. In case you use Application Insights it can make sense to not set it for performance reasons. | | `siteConfig` | object | `{object}` | | The site config object. | +| `slots` | _[slots](slots/readme.md)_ array | `[]` | | Configuration for deployment slots for an app. | | `storageAccountId` | string | `''` | | Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. | | `storageAccountRequired` | bool | `False` | | Checks if Customer provided storage account is required. | | `systemAssignedIdentity` | bool | `False` | | Enables system assigned managed identity on the resource. | @@ -389,6 +394,9 @@ userAssignedIdentities: { | `name` | string | The name of the site. | | `resourceGroupName` | string | The resource group the site was deployed into. | | `resourceId` | string | The resource ID of the site. | +| `slotResourceIds` | array | The list of the slot resource ids. | +| `slots` | array | The list of the slots. | +| `slotSystemAssignedPrincipalIds` | array | The principal ID of the system assigned identity of slots. | | `systemAssignedPrincipalId` | string | The principal ID of the system assigned identity. | ## Cross-referenced modules @@ -971,3 +979,168 @@ module sites './Microsoft.Web/sites/deploy.bicep' = {

+ +

Example 5: Webappslots

+ +
+ +via Bicep module + +```bicep +module sites './Microsoft.Web/sites/deploy.bicep' = { + name: '${uniqueString(deployment().name)}-test-wswa' + params: { + // Required parameters + kind: 'app' + name: '<>wswa001' + serverFarmResourceId: '' + // Non-required parameters + diagnosticEventHubAuthorizationRuleId: '' + diagnosticEventHubName: '' + diagnosticLogsRetentionInDays: 7 + diagnosticStorageAccountId: '' + diagnosticWorkspaceId: '' + enableDefaultTelemetry: '' + httpsOnly: true + privateEndpoints: [ + { + privateDnsZoneGroup: { + privateDNSResourceIds: [ + '' + ] + } + service: 'sites' + subnetResourceId: '' + } + ] + roleAssignments: [ + { + principalIds: [ + '' + ] + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + siteConfig: { + alwaysOn: true + metadata: [ + { + name: 'CURRENT_STACK' + value: 'dotnetcore' + } + ] + } + slots: [ + { + name: 'slot1' + } + ] + systemAssignedIdentity: true + userAssignedIdentities: { + '': {} + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "kind": { + "value": "app" + }, + "name": { + "value": "<>wswa001" + }, + "serverFarmResourceId": { + "value": "" + }, + // Non-required parameters + "diagnosticEventHubAuthorizationRuleId": { + "value": "" + }, + "diagnosticEventHubName": { + "value": "" + }, + "diagnosticLogsRetentionInDays": { + "value": 7 + }, + "diagnosticStorageAccountId": { + "value": "" + }, + "diagnosticWorkspaceId": { + "value": "" + }, + "enableDefaultTelemetry": { + "value": "" + }, + "httpsOnly": { + "value": true + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ + "" + ] + }, + "service": "sites", + "subnetResourceId": "" + } + ] + }, + "roleAssignments": { + "value": [ + { + "principalIds": [ + "" + ], + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "siteConfig": { + "value": { + "alwaysOn": true, + "metadata": [ + { + "name": "CURRENT_STACK", + "value": "dotnetcore" + } + ] + } + }, + "slots": { + "value": [ + { + "name": "slot1" + } + ] + }, + "systemAssignedIdentity": { + "value": true + }, + "userAssignedIdentities": { + "value": { + "": {} + } + } + } +} +``` + +
+

diff --git a/modules/Microsoft.Web/sites/slots/.bicep/nested_roleAssignments.bicep b/modules/Microsoft.Web/sites/slots/.bicep/nested_roleAssignments.bicep new file mode 100644 index 0000000000..610a0e6f5f --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/.bicep/nested_roleAssignments.bicep @@ -0,0 +1,60 @@ +@sys.description('Required. The IDs of the principals to assign the role to.') +param principalIds array + +@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') +param roleDefinitionIdOrName string + +@sys.description('Required. The resource ID of the resource to apply the role assignment to.') +param resourceId string + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +var builtInRoleNames = { + 'Owner': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + 'Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') + 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') + 'Logic App Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '87a39d53-fc1b-424a-814c-f7e04687dc9e') + 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e') + 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae') + 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44') + 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa') + 'Monitoring Metrics Publisher': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb') + 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') + 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Website Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') +} + +var appName = split(resourceId, '/')[indexOf(split(resourceId, '/'), 'sites') + 1] + +resource app 'Microsoft.Web/sites@2020-12-01' existing = { + name: appName + resource slot 'slots' existing = { + name: last(split(resourceId, '/')) + } +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = [for principalId in principalIds: { + name: guid(app::slot.id, principalId, roleDefinitionIdOrName) + properties: { + description: description + roleDefinitionId: contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName + principalId: principalId + principalType: !empty(principalType) ? any(principalType) : null + } + scope: app::slot +}] diff --git a/modules/Microsoft.Web/sites/slots/config-appsettings/deploy.bicep b/modules/Microsoft.Web/sites/slots/config-appsettings/deploy.bicep new file mode 100644 index 0000000000..4a5b8279f2 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-appsettings/deploy.bicep @@ -0,0 +1,103 @@ +// ================ // +// Parameters // +// ================ // +@description('Required. Slot name to be configured.') +param slotName string + +@description('Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment.') +param appName string + +@description('Required. Type of slot to deploy.') +@allowed([ + 'functionapp' // function app windows os + 'functionapp,linux' // function app linux os + 'functionapp,workflowapp' // logic app workflow + 'functionapp,workflowapp,linux' // logic app docker container + 'app' // normal web app +]) +param kind string + +@description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') +param storageAccountId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightId string = '' + +@description('Optional. For function apps. If true the app settings "AzureWebJobsDashboard" will be set. If false not. In case you use Application Insights it can make sense to not set it for performance reasons.') +param setAzureWebJobsDashboard bool = contains(kind, 'functionapp') ? true : false + +@description('Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING.') +param appSettingsKeyValuePairs object = {} + +@description('Optional. Enable telemetry via the Customer Usage Attribution ID (GUID).') +param enableDefaultTelemetry bool = true + +// =========== // +// Variables // +// =========== // +var azureWebJobsValues = !empty(storageAccountId) ? union({ + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};' + }, ((setAzureWebJobsDashboard == true) ? { + AzureWebJobsDashboard: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};' + } : {})) : {} + +var appInsightsValues = !empty(appInsightId) ? { + APPINSIGHTS_INSTRUMENTATIONKEY: appInsight.properties.InstrumentationKey + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsight.properties.ConnectionString +} : {} + +var expandedAppSettings = union(appSettingsKeyValuePairs, azureWebJobsValues, appInsightsValues) + +// =========== // +// Existing resources // +// =========== // +resource app 'Microsoft.Web/sites@2020-12-01' existing = { + name: appName + resource slot 'slots' existing = { + name: slotName + } +} + +resource appInsight 'microsoft.insights/components@2020-02-02' existing = if (!empty(appInsightId)) { + name: last(split(appInsightId, '/')) + scope: resourceGroup(split(appInsightId, '/')[2], split(appInsightId, '/')[4]) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' existing = if (!empty(storageAccountId)) { + name: last(split(storageAccountId, '/')) + scope: resourceGroup(split(storageAccountId, '/')[2], split(storageAccountId, '/')[4]) +} + +// =========== // +// Deployments // +// =========== // +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource slotSettings 'Microsoft.Web/sites/slots/config@2022-03-01' = { + name: 'appsettings' + kind: kind + parent: app::slot + properties: expandedAppSettings +} + +// =========== // +// Outputs // +// =========== // +@description('The name of the slot config.') +output name string = slotSettings.name + +@description('The resource ID of the slot config.') +output resourceId string = slotSettings.id + +@description('The resource group the slot config was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/Microsoft.Web/sites/slots/config-appsettings/readme.md b/modules/Microsoft.Web/sites/slots/config-appsettings/readme.md new file mode 100644 index 0000000000..76ddf2c317 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-appsettings/readme.md @@ -0,0 +1,100 @@ +# Site Config `[Microsoft.Web/sites/slots/config-appsettings]` + +This module deploys the app settings. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/sites/slots/config` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Allowed Values | Description | +| :-- | :-- | :-- | :-- | +| `kind` | string | `[app, functionapp, functionapp,linux, functionapp,workflowapp, functionapp,workflowapp,linux]` | Type of slot to deploy. | +| `slotName` | string | | Slot name to be configured. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `appName` | string | The name of the parent site resource. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `appInsightId` | string | `''` | Resource ID of the app insight to leverage for this resource. | +| `appSettingsKeyValuePairs` | object | `{object}` | The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via the Customer Usage Attribution ID (GUID). | +| `setAzureWebJobsDashboard` | bool | `[if(contains(parameters('kind'), 'functionapp'), true(), false())]` | For function apps. If true the app settings "AzureWebJobsDashboard" will be set. If false not. In case you use Application Insights it can make sense to not set it for performance reasons. | +| `storageAccountId` | string | `''` | Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. | + + +### Parameter Usage: `appSettingsKeyValuePairs` + +AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING are set separately (check parameters storageAccountId, setAzureWebJobsDashboard, appInsightId). +For all other app settings key-value pairs use this object. + +

+ +Parameter JSON format + +```json +"appSettingsKeyValuePairs": { + "value": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +appSettingsKeyValuePairs: [ + { + name: 'key1' + value: 'val1' + } + { + name: 'key2' + value: 'val2' + } +] +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the slot config. | +| `resourceGroupName` | string | The resource group the slot config was deployed into. | +| `resourceId` | string | The resource ID of the slot config. | + +## Cross-referenced modules + +_None_ diff --git a/modules/Microsoft.Web/sites/slots/config-appsettings/version.json b/modules/Microsoft.Web/sites/slots/config-appsettings/version.json new file mode 100644 index 0000000000..56f8d9ca40 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-appsettings/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.4" +} diff --git a/modules/Microsoft.Web/sites/slots/config-authsettingsv2/deploy.bicep b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/deploy.bicep new file mode 100644 index 0000000000..830a5e6215 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/deploy.bicep @@ -0,0 +1,68 @@ +// ================ // +// Parameters // +// ================ // +@description('Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment.') +param appName string + +@description('Required. Slot name to be configured.') +param slotName string + +@description('Required. Type of slot to deploy.') +@allowed([ + 'functionapp' // function app windows os + 'functionapp,linux' // function app linux os + 'functionapp,workflowapp' // logic app workflow + 'functionapp,workflowapp,linux' // logic app docker container + 'app' // normal web app +]) +param kind string + +@description('Required. The auth settings V2 configuration.') +param authSettingV2Configuration object + +@description('Optional. Enable telemetry via the Customer Usage Attribution ID (GUID).') +param enableDefaultTelemetry bool = true + +// =========== // +// Existing resources // +// =========== // +resource app 'Microsoft.Web/sites@2020-12-01' existing = { + name: appName + resource slot 'slots' existing = { + name: slotName + } +} + +// =========== // +// Deployments // +// =========== // +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource slotSettings 'Microsoft.Web/sites/slots/config@2022-03-01' = { + name: 'authsettingsV2' + kind: kind + parent: app::slot + properties: authSettingV2Configuration +} + +// =========== // +// Outputs // +// =========== // +@description('The name of the slot config.') +output name string = slotSettings.name + +@description('The resource ID of the slot config.') +output resourceId string = slotSettings.id + +@description('The resource group the slot config was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/Microsoft.Web/sites/slots/config-authsettingsv2/readme.md b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/readme.md new file mode 100644 index 0000000000..30201e9c7f --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/readme.md @@ -0,0 +1,82 @@ +# Site Config `[Microsoft.Web/sites/slots/config-authsettingsv2]` + +This module deploys the auth settings v2. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/sites/slots/config` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Allowed Values | Description | +| :-- | :-- | :-- | :-- | +| `authSettingV2Configuration` | object | | The auth settings V2 configuration. | +| `kind` | string | `[app, functionapp, functionapp,linux, functionapp,workflowapp, functionapp,workflowapp,linux]` | Type of slot to deploy. | +| `slotName` | string | | Slot name to be configured. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `appName` | string | The name of the parent site resource. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via the Customer Usage Attribution ID (GUID). | + + +### Parameter Usage: `authSettingV2Configuration` + +The auth settings V2 configuration. + +

+ +Parameter JSON format + +```json +"siteConfig": { + "value": [ + // Check out https://docs.microsoft.com/en-us/azure/templates/microsoft.web/sites/config-authsettingsv2?tabs=bicep#siteauthsettingsv2properties for possible properties + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +siteConfig: [ + // Check out https://docs.microsoft.com/en-us/azure/templates/microsoft.web/sites/config-authsettingsv2?tabs=bicep#siteauthsettingsv2properties for possible properties +] +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the slot config. | +| `resourceGroupName` | string | The resource group the slot config was deployed into. | +| `resourceId` | string | The resource ID of the slot config. | + +## Cross-referenced modules + +_None_ diff --git a/modules/Microsoft.Web/sites/slots/config-authsettingsv2/version.json b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/version.json new file mode 100644 index 0000000000..56f8d9ca40 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/config-authsettingsv2/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.4" +} diff --git a/modules/Microsoft.Web/sites/slots/deploy.bicep b/modules/Microsoft.Web/sites/slots/deploy.bicep new file mode 100644 index 0000000000..78c68957a9 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/deploy.bicep @@ -0,0 +1,313 @@ +// ================ // +// Parameters // +// ================ // +// General +@description('Required. Name of the slot.') +param name string + +@description('Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment.') +param appName string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Required. Type of slot to deploy.') +@allowed([ + 'functionapp' // function app windows os + 'functionapp,linux' // function app linux os + 'functionapp,workflowapp' // logic app workflow + 'functionapp,workflowapp,linux' // logic app docker container + 'app' // normal web app +]) +param kind string + +@description('Optional. The resource ID of the app service plan to use for the slot.') +param serverFarmResourceId string = '' + +@description('Optional. Configures a slot to accept only HTTPS requests. Issues redirect for HTTP requests.') +param httpsOnly bool = true + +@description('Optional. If client affinity is enabled.') +param clientAffinityEnabled bool = true + +@description('Optional. The resource ID of the app service environment to use for this resource.') +param appServiceEnvironmentId string = '' + +@description('Optional. Enables system assigned managed identity on the resource.') +param systemAssignedIdentity bool = false + +@description('Optional. The ID(s) to assign to the resource.') +param userAssignedIdentities object = {} + +@description('Optional. The resource ID of the assigned identity to be used to access a key vault with.') +param keyVaultAccessIdentityResourceId string = '' + +@description('Optional. Checks if Customer provided storage account is required.') +param storageAccountRequired bool = false + +@description('Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration. This must be of the form /subscriptions/{subscriptionName}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}.') +param virtualNetworkSubnetId string = '' + +// slot Config +@description('Optional. The site config object.') +param siteConfig object = {} + +@description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') +param storageAccountId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightId string = '' + +@description('Optional. For function apps. If true the app settings "AzureWebJobsDashboard" will be set. If false not. In case you use Application Insights it can make sense to not set it for performance reasons.') +param setAzureWebJobsDashboard bool = contains(kind, 'functionapp') ? true : false + +@description('Optional. The app settings-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING.') +param appSettingsKeyValuePairs object = {} + +@description('Optional. The auth settings V2 configuration.') +param authSettingV2Configuration object = {} + +// Lock +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +// Private Endpoints +@description('Optional. Configuration details for private endpoints.') +param privateEndpoints array = [] + +// Tags +@description('Optional. Tags of the resource.') +param tags object = {} + +// PID +@description('Optional. Enable telemetry via the Customer Usage Attribution ID (GUID).') +param enableDefaultTelemetry bool = true + +// Role Assignments +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +// Diagnostic Settings +@description('Optional. Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely.') +@minValue(0) +@maxValue(365) +param diagnosticLogsRetentionInDays int = 365 + +@description('Optional. Resource ID of the diagnostic storage account.') +param diagnosticStorageAccountId string = '' + +@description('Optional. Resource ID of log analytics workspace.') +param diagnosticWorkspaceId string = '' + +@description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') +param diagnosticEventHubAuthorizationRuleId string = '' + +@description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category.') +param diagnosticEventHubName string = '' + +@description('Optional. The name of logs that will be streamed.') +@allowed([ + 'AppServiceHTTPLogs' + 'AppServiceConsoleLogs' + 'AppServiceAppLogs' + 'AppServiceAuditLogs' + 'AppServiceIPSecAuditLogs' + 'AppServicePlatformLogs' + 'FunctionAppLogs' +]) +param diagnosticLogCategoriesToEnable array = kind == 'functionapp' ? [ + 'FunctionAppLogs' +] : [ + 'AppServiceHTTPLogs' + 'AppServiceConsoleLogs' + 'AppServiceAppLogs' + 'AppServiceAuditLogs' + 'AppServiceIPSecAuditLogs' + 'AppServicePlatformLogs' +] + +@description('Optional. The name of metrics that will be streamed.') +@allowed([ + 'AllMetrics' +]) +param diagnosticMetricsToEnable array = [ + 'AllMetrics' +] + +@description('Optional. The name of the diagnostic setting, if deployed.') +param diagnosticSettingsName string = 'slot-${name}-diagnosticSettings' + +// =========== // +// Variables // +// =========== // +var diagnosticsLogs = [for category in diagnosticLogCategoriesToEnable: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: diagnosticLogsRetentionInDays + } +}] + +var diagnosticsMetrics = [for metric in diagnosticMetricsToEnable: { + category: metric + timeGrain: null + enabled: true + retentionPolicy: { + enabled: true + days: diagnosticLogsRetentionInDays + } +}] + +var identityType = systemAssignedIdentity ? (!empty(userAssignedIdentities) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(userAssignedIdentities) ? 'UserAssigned' : 'None') + +var identity = identityType != 'None' ? { + type: identityType + userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null +} : null + +var enableReferencedModulesTelemetry = false + +// ================== // +// Existing resources // +// ================== // +resource app 'Microsoft.Web/sites@2021-03-01' existing = { + name: appName +} + +// =========== // +// Deployments // +// =========== // +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource slot 'Microsoft.Web/sites/slots@2022-03-01' = { + name: name + parent: app + location: location + kind: kind + tags: tags + identity: identity + properties: { + serverFarmId: serverFarmResourceId + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: httpsOnly + hostingEnvironmentProfile: !empty(appServiceEnvironmentId) ? { + id: appServiceEnvironmentId + } : null + storageAccountRequired: storageAccountRequired + keyVaultReferenceIdentity: !empty(keyVaultAccessIdentityResourceId) ? keyVaultAccessIdentityResourceId : any(null) + virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : any(null) + siteConfig: siteConfig + } +} + +module slot_appsettings 'config-appsettings/deploy.bicep' = if (!empty(appSettingsKeyValuePairs)) { + name: '${uniqueString(deployment().name, location)}-Slot-${name}-Config-AppSettings' + params: { + slotName: slot.name + appName: app.name + kind: kind + storageAccountId: storageAccountId + appInsightId: appInsightId + setAzureWebJobsDashboard: setAzureWebJobsDashboard + appSettingsKeyValuePairs: appSettingsKeyValuePairs + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module slot_authsettingsv2 'config-authsettingsv2/deploy.bicep' = if (!empty(authSettingV2Configuration)) { + name: '${uniqueString(deployment().name, location)}-Slot-${name}-Config-AuthSettingsV2' + params: { + slotName: slot.name + appName: app.name + kind: kind + authSettingV2Configuration: authSettingV2Configuration + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +resource slot_lock 'Microsoft.Authorization/locks@2017-04-01' = if (!empty(lock)) { + name: '${slot.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: slot +} + +resource slot_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(diagnosticStorageAccountId) || !empty(diagnosticWorkspaceId) || !empty(diagnosticEventHubAuthorizationRuleId) || !empty(diagnosticEventHubName)) { + name: diagnosticSettingsName + properties: { + storageAccountId: !empty(diagnosticStorageAccountId) ? diagnosticStorageAccountId : null + workspaceId: !empty(diagnosticWorkspaceId) ? diagnosticWorkspaceId : null + eventHubAuthorizationRuleId: !empty(diagnosticEventHubAuthorizationRuleId) ? diagnosticEventHubAuthorizationRuleId : null + eventHubName: !empty(diagnosticEventHubName) ? diagnosticEventHubName : null + metrics: diagnosticsMetrics + logs: diagnosticsLogs + } + scope: slot +} + +module slot_rbac '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { + name: '${uniqueString(deployment().name, location)}-Slot-${name}-Rbac-${index}' + params: { + description: contains(roleAssignment, 'description') ? roleAssignment.description : '' + principalIds: roleAssignment.principalIds + principalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : '' + roleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName + resourceId: slot.id + } +}] + +module slot_privateEndpoints '../../../Microsoft.Network/privateEndpoints/deploy.bicep' = [for (privateEndpoint, index) in privateEndpoints: { + name: '${uniqueString(deployment().name, location)}-Slot-${name}-PrivateEndpoint-${index}' + params: { + groupIds: [ + '${privateEndpoint.service}-${name}' + ] + name: contains(privateEndpoint, 'name') ? privateEndpoint.name : 'pe-${last(split(slot.id, '/'))}-${privateEndpoint.service}-${index}' + serviceResourceId: app.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: enableReferencedModulesTelemetry + location: reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: contains(privateEndpoint, 'lock') ? privateEndpoint.lock : lock + privateDnsZoneGroup: contains(privateEndpoint, 'privateDnsZoneGroup') ? privateEndpoint.privateDnsZoneGroup : {} + roleAssignments: contains(privateEndpoint, 'roleAssignments') ? privateEndpoint.roleAssignments : [] + tags: contains(privateEndpoint, 'tags') ? privateEndpoint.tags : {} + manualPrivateLinkServiceConnections: contains(privateEndpoint, 'manualPrivateLinkServiceConnections') ? privateEndpoint.manualPrivateLinkServiceConnections : [] + customDnsConfigs: contains(privateEndpoint, 'customDnsConfigs') ? privateEndpoint.customDnsConfigs : [] + } +}] + +// =========== // +// Outputs // +// =========== // +@description('The name of the slot.') +output name string = slot.name + +@description('The resource ID of the slot.') +output resourceId string = slot.id + +@description('The resource group the slot was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The principal ID of the system assigned identity.') +output systemAssignedPrincipalId string = systemAssignedIdentity && (contains(slot, 'identity') ? contains(slot.identity, 'principalId') : false) ? slot.identity.principalId : '' + +@description('The location the resource was deployed into.') +output location string = slot.location diff --git a/modules/Microsoft.Web/sites/slots/readme.md b/modules/Microsoft.Web/sites/slots/readme.md new file mode 100644 index 0000000000..ee2e27419e --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/readme.md @@ -0,0 +1,410 @@ +# Web/Function Apps `[Microsoft.Web/sites/slots]` + +This module deploys a web or function app. + +## Navigation + +- [Resource types](#Resource-types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2017-04-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2017-04-01/locks) | +| `Microsoft.Authorization/locks` | [2020-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2020-10-01-preview](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-10-01-preview/roleAssignments) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/privateEndpoints` | [2022-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-05-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2022-05-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-05-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Web/sites/slots` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/sites/slots) | +| `Microsoft.Web/sites/slots/config` | [2022-03-01](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Allowed Values | Description | +| :-- | :-- | :-- | :-- | +| `kind` | string | `[app, functionapp, functionapp,linux, functionapp,workflowapp, functionapp,workflowapp,linux]` | Type of slot to deploy. | +| `name` | string | | Name of the slot. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `appName` | string | The name of the parent site resource. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `appInsightId` | string | `''` | | Resource ID of the app insight to leverage for this resource. | +| `appServiceEnvironmentId` | string | `''` | | The resource ID of the app service environment to use for this resource. | +| `appSettingsKeyValuePairs` | object | `{object}` | | The app settings-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. | +| `authSettingV2Configuration` | object | `{object}` | | The auth settings V2 configuration. | +| `clientAffinityEnabled` | bool | `True` | | If client affinity is enabled. | +| `diagnosticEventHubAuthorizationRuleId` | string | `''` | | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| `diagnosticEventHubName` | string | `''` | | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. | +| `diagnosticLogCategoriesToEnable` | array | `[if(equals(parameters('kind'), 'functionapp'), createArray('FunctionAppLogs'), createArray('AppServiceHTTPLogs', 'AppServiceConsoleLogs', 'AppServiceAppLogs', 'AppServiceAuditLogs', 'AppServiceIPSecAuditLogs', 'AppServicePlatformLogs'))]` | `[AppServiceAppLogs, AppServiceAuditLogs, AppServiceConsoleLogs, AppServiceHTTPLogs, AppServiceIPSecAuditLogs, AppServicePlatformLogs, FunctionAppLogs]` | The name of logs that will be streamed. | +| `diagnosticLogsRetentionInDays` | int | `365` | | Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely. | +| `diagnosticMetricsToEnable` | array | `[AllMetrics]` | `[AllMetrics]` | The name of metrics that will be streamed. | +| `diagnosticSettingsName` | string | `[format('slot-{0}-diagnosticSettings', parameters('name'))]` | | The name of the diagnostic setting, if deployed. | +| `diagnosticStorageAccountId` | string | `''` | | Resource ID of the diagnostic storage account. | +| `diagnosticWorkspaceId` | string | `''` | | Resource ID of log analytics workspace. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via the Customer Usage Attribution ID (GUID). | +| `httpsOnly` | bool | `True` | | Configures a slot to accept only HTTPS requests. Issues redirect for HTTP requests. | +| `keyVaultAccessIdentityResourceId` | string | `''` | | The resource ID of the assigned identity to be used to access a key vault with. | +| `location` | string | `[resourceGroup().location]` | | Location for all Resources. | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `privateEndpoints` | array | `[]` | | Configuration details for private endpoints. | +| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `serverFarmResourceId` | string | `''` | | The resource ID of the app service plan to use for the slot. | +| `setAzureWebJobsDashboard` | bool | `[if(contains(parameters('kind'), 'functionapp'), true(), false())]` | | For function apps. If true the app settings "AzureWebJobsDashboard" will be set. If false not. In case you use Application Insights it can make sense to not set it for performance reasons. | +| `siteConfig` | object | `{object}` | | The site config object. | +| `storageAccountId` | string | `''` | | Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. | +| `storageAccountRequired` | bool | `False` | | Checks if Customer provided storage account is required. | +| `systemAssignedIdentity` | bool | `False` | | Enables system assigned managed identity on the resource. | +| `tags` | object | `{object}` | | Tags of the resource. | +| `userAssignedIdentities` | object | `{object}` | | The ID(s) to assign to the resource. | +| `virtualNetworkSubnetId` | string | `''` | | Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration. This must be of the form /subscriptions/{subscriptionName}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}. | + + +### Parameter Usage: `appSettingsKeyValuePairs` + +AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING are set separately (check parameters storageAccountId, setAzureWebJobsDashboard, appInsightId). +For all other app settings key-value pairs use this object. + +

+ +Parameter JSON format + +```json +"appSettingsKeyValuePairs": { + "value": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +appSettingsKeyValuePairs: [ + { + name: 'key1' + value: 'val1' + } + { + name: 'key2' + value: 'val2' + } +] +``` + +
+

+ +### Parameter Usage: `authSettingV2Configuration` + +The auth settings V2 configuration. + +

+ +Parameter JSON format + +```json +"siteConfig": { + "value": [ + // Check out https://docs.microsoft.com/en-us/azure/templates/microsoft.web/sites/config-authsettingsv2?tabs=bicep#siteauthsettingsv2properties for possible properties + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +siteConfig: [ + // Check out https://docs.microsoft.com/en-us/azure/templates/microsoft.web/sites/config-authsettingsv2?tabs=bicep#siteauthsettingsv2properties for possible properties +] +``` + +
+

+ +### Parameter Usage: `siteConfig` + +The site config. + +

+ +Parameter JSON format + +```json +"siteConfig": { + "value": [ + // Check out https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites?tabs=bicep#siteconfig for possible properties + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +siteConfig: [ + // Check out https://docs.microsoft.com/en-us/azure/templates/Microsoft.Web/sites?tabs=bicep#siteconfig for possible properties +] +``` + +
+

+ +### Parameter Usage: `privateEndpoints` + +To use Private Endpoint the following dependencies must be deployed: + +- Destination subnet must be created with the following configuration option - `"privateEndpointNetworkPolicies": "Disabled"`. Setting this option acknowledges that NSG rules are not applied to Private Endpoints (this capability is coming soon). A full example is available in the Virtual Network Module. +- Although not strictly required, it is highly recommended to first create a private DNS Zone to host Private Endpoint DNS records. See [Azure Private Endpoint DNS configuration](https://docs.microsoft.com/en-us/azure/private-link/private-endpoint-dns) for more information. + +

+ +Parameter JSON format + +```json +"privateEndpoints": { + "value": [ + // Example showing all available fields + { + "name": "sxx-az-pe", // Optional: Name will be automatically generated if one is not provided here + "subnetResourceId": "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "", // e.g. vault, registry, blob + "privateDnsZoneGroup": { + "privateDNSResourceIds": [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/" // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + }, + "customDnsConfigs": [ // Optional + { + "fqdn": "customname.test.local", + "ipAddresses": [ + "10.10.10.10" + ] + } + ] + }, + // Example showing only mandatory fields + { + "subnetResourceId": "/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001", + "service": "" // e.g. vault, registry, blob + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +privateEndpoints: [ + // Example showing all available fields + { + name: 'sxx-az-pe' // Optional: Name will be automatically generated if one is not provided here + subnetResourceId: '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + privateDnsZoneGroup: { + privateDNSResourceIds: [ // Optional: No DNS record will be created if a private DNS zone Resource ID is not specified + '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/privateDnsZones/' // e.g. privatelink.vaultcore.azure.net, privatelink.azurecr.io, privatelink.blob.core.windows.net + ] + } + // Optional + customDnsConfigs: [ + { + fqdn: 'customname.test.local' + ipAddresses: [ + '10.10.10.10' + ] + } + ] + } + // Example showing only mandatory fields + { + subnetResourceId: '/subscriptions/<>/resourceGroups/validation-rg/providers/Microsoft.Network/virtualNetworks/sxx-az-vnet-x-001/subnets/sxx-az-subnet-x-001' + service: '' // e.g. vault, registry, blob + } +] +``` + +
+

+ +### Parameter Usage: `roleAssignments` + +Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure. + +

+ +Parameter JSON format + +```json +"roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Reader", + "description": "Reader Role Assignment", + "principalIds": [ + "12345678-1234-1234-1234-123456789012", // object 1 + "78945612-1234-1234-1234-123456789012" // object 2 + ] + }, + { + "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11", + "principalIds": [ + "12345678-1234-1234-1234-123456789012" // object 1 + ], + "principalType": "ServicePrincipal" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + description: 'Reader Role Assignment' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + '78945612-1234-1234-1234-123456789012' // object 2 + ] + } + { + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + ] + principalType: 'ServicePrincipal' + } +] +``` + +
+

+ +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +

+ +Parameter JSON format + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +
+ +
+ +Bicep format + +```bicep +tags: { + Environment: 'Non-Prod' + Contact: 'test.user@testcompany.com' + PurchaseOrder: '1234' + CostCenter: '7890' + ServiceName: 'DeploymentValidation' + Role: 'DeploymentValidation' +} +``` + +
+

+ +### Parameter Usage: `userAssignedIdentities` + +You can specify multiple user assigned identities to a resource by providing additional resource IDs using the following format: + +

+ +Parameter JSON format + +```json +"userAssignedIdentities": { + "value": { + "/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001": {}, + "/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002": {} + } +} +``` + +
+ +
+ +Bicep format + +```bicep +userAssignedIdentities: { + '/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001': {} + '/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002': {} +} +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the slot. | +| `resourceGroupName` | string | The resource group the slot was deployed into. | +| `resourceId` | string | The resource ID of the slot. | +| `systemAssignedPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +_None_ diff --git a/modules/Microsoft.Web/sites/slots/version.json b/modules/Microsoft.Web/sites/slots/version.json new file mode 100644 index 0000000000..56f8d9ca40 --- /dev/null +++ b/modules/Microsoft.Web/sites/slots/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.4" +}