diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml index e3090ff..5642e83 100644 --- a/.github/workflows/mega-linter.yml +++ b/.github/workflows/mega-linter.yml @@ -51,6 +51,7 @@ jobs: DISABLE: COPYPASTE,SPELL # Uncomment to disable copy-paste and spell checks DISABLE_LINTERS: YAML_V8R,YAML_YAMLLINT,YAML_PRETTIER,REPOSITORY_CHECKOV,POWERSHELL_POWERSHELL,ACTION_ACTIONLINT REPOSITORY_KICS_DISABLE_ERRORS: true + REPOSITORY_GITLEAKS_PR_COMMITS_SCAN: true # Upload MegaLinter artifacts - name: Archive production artifacts diff --git a/README.md b/README.md index ab0c70e..b20e8d3 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ Where possible, examples will be complimented by a blog post giving a deeper div [Awesome Azure Bicep list](https://github.com/ElYusubov/AWESOME-Azure-Bicep) +[Azure Bicep Cheat Sheet](https://github.com/johnlokerse/azure-bicep-cheat-sheet) + ## 🖌️ Authors - [Rios Engineer](https://www.github.com/riosengineer) diff --git a/bicep-examples/dependencies/README.md b/bicep-examples/dependencies/README.md new file mode 100644 index 0000000..fdfe2e6 --- /dev/null +++ b/bicep-examples/dependencies/README.md @@ -0,0 +1,91 @@ +# Resource dependencies in Azure Bicep 🦾 + +There's two types of dependencies `implicit` and `explicit` in Azure Bicep. Within the `main.bicep` file example you'll notice many implicit and some explicit dependencies that you can review as a real example of how these two play a role in your Azure Bicep deployments. + +> [!TIP] +> If you're interested in this examples solution and what it does then there is more information in this template repo [here](https://github.com/riosengineer/bicep-quickstart-frontdoor-private-endpoint-appservice) with supporting documentation and architectural drawing. + +## Implicit 🔗 + +With `implicit` dependencies we are referencing another Azure resource within the same deployment, which means we'll not need to declare an explicit dependency. There are two common ways this is accomplished. For example: + +```javascript +module appInsights 'modules/appInsights/appinsights.bicep' = { + name: '${uniqueString(deployment().name, location)}-appInsights' + params: { + name: appInsightsName + location: location + workspaceResourceId: logAnalytics.outputs.id + kind: 'web' + applicationType: 'web' + } +} +``` + +Note the `logAnalytics.outputs.id` symbolic name defined is referencing a previous module for this resources properties. This is how an implicit dependency is created and ARM will deploy in resources in their dependent order. + +```javascript +resource frontDoorOriginGroup 'Microsoft.Cdn/profiles/originGroups@2021-06-01' = { + name: frontDoorOriginGroupName + parent: frontDoorProfile + properties: { + loadBalancingSettings: { + sampleSize: 4 + successfulSamplesRequired: 3 + } + healthProbeSettings: { + probePath: '/' + probeRequestType: 'HEAD' + probeProtocol: 'Http' + probeIntervalInSeconds: 100 + } + } +} +``` + +Lastly, notice the `parent:` property defined in this Azure Front Door resource block above, where it's defining the symbolic name from the Azure CDN profile object. This is also an implicit dependency created between the two objects. + +## Explicit 🖇️ + +```javascript +resource frontDoorProfile 'Microsoft.Cdn/profiles@2021-06-01' = { + name: frontDoorProfileName + location: 'global' + sku: { + name: frontDoorSkuName + } + dependsOn: [ + webApp + webAppPlan + ] +} +``` + +For explicit dependencies, we can use the `dependsOn` property to describe explicitly which resources we want this deployment to depend on. + +In the case above, I don't want my Front Door deployment to start before the App service and App Plan have been deployed first, as I need them to exist for my origin backend. + +## Deployment 🚀 + +> [!WARNING] +> This example deploys Azure Front Door Premium SKU which is circa $300 for the month. Do not leave running if you don't want to incur charges. Make sure to delete as soon as possible after deployment and you'll likely see very minimal costs. + +Define the parameters in the top of the file before deploying. + +In VisualStudio Code open a terminal and run: + +CLI + +```bash +az login +az set --subscription 'your subscription name' +az deployment create --confirm-with-what-if -g 'your resource group name' -f .\main.bicep +``` + +or PowerShell + +```powershell +Connect-AzAccount +Set-AzContext -Subsription "your subsription name" +New-AzResourceGroupDeployment -Confirm -ResourceGroup "your resource group name" -TemplateFile "main.bicep" +``` diff --git a/bicep-examples/dependencies/main.bicep b/bicep-examples/dependencies/main.bicep new file mode 100644 index 0000000..8d9d83c --- /dev/null +++ b/bicep-examples/dependencies/main.bicep @@ -0,0 +1,229 @@ +targetScope = 'resourceGroup' + +// Change the below params to suit your deployment needs +// Go to the modules to amend IP schema, app plan sku/app code stack etc. +@description('Azure UK South region.') +param location string = resourceGroup().location + +@description('Web App resource group name.') +param rg_web_workload string = 'rg-webapp-prod' + +@description('Workload / corp / core landing zone subid.') +param workloadsSubId string = '00000000-0000-0000-0000-000000000000' + +@description('Log analytics workspace name.') +param alaName string = 'ala-workspace-name' + +@description('App service application insights name.') +param appInsightsName string = 'appinsights-name' + +@description('Azure app service name.') +param webAppName string = 'webapp-001' + +@description('The name of the Front Door endpoint to create. This must be globally unique.') +param afdWebEndpoint string = 'afd-${uniqueString(resourceGroup().id)}' + +@description('The name of the SKU to use when creating the Front Door profile.') +@allowed([ + 'Standard_AzureFrontDoor' + 'Premium_AzureFrontDoor' +]) +param frontDoorSkuName string = 'Premium_AzureFrontDoor' + +var frontDoorProfileName = 'afdpremium-web' +var frontDoorOriginGroupName = 'webapp-origin-group' +var frontDoorOriginName = 'webapp-origin-group' +var frontDoorRouteName = 'webapp-route' + +/////////////// +// Resources // +/////////////// + +// Azure App Service components + +// vNet for integration +module vnet 'br/public:network/virtual-network:1.1.3' = { + name: '${uniqueString(deployment().name, location)}-webVnet' + scope: resourceGroup(workloadsSubId, rg_web_workload) + params: { + name: 'webapp-vnet' + addressPrefixes: [ + '10.1.0.0/21' + ] + subnets: [ + { + name: 'webapp-snet' + addressPrefix: '10.1.1.0/24' + delegations: [ + { + name: 'Microsoft.Web.serverFarms' + properties: { + serviceName: 'Microsoft.Web/serverFarms' + } + } + ] + } + ] + } +} + +// Log Analytics workspace +module logAnalytics 'br/public:storage/log-analytics-workspace:1.0.3' = { + name: '${uniqueString(deployment().name, location)}-ala' + scope: resourceGroup(rg_web_workload) + params: { + name: alaName + location: location + } +} + +// Application Insight +module appInsights 'modules/appInsights/appinsights.bicep' = { + name: '${uniqueString(deployment().name, location)}-appInsights' + scope: resourceGroup(workloadsSubId, rg_web_workload) + params: { + name: appInsightsName + location: location + workspaceResourceId: logAnalytics.outputs.id + kind: 'web' + applicationType: 'web' + } +} + +// Azure App Plan +module webAppPlan 'modules/webApp/appPlan.bicep' = { + name: '${uniqueString(deployment().name, location)}-appPlan' + scope: resourceGroup(workloadsSubId, rg_web_workload) + params: { + name: 'appPlan' + location: location + sku: { + name: 'S1' + } + kind: 'App' + } +} + +// Web App resource +module webApp 'modules/webApp/webApp.bicep' = { + name: '${uniqueString(deployment().name, location)}-webApp' + scope: resourceGroup(workloadsSubId, rg_web_workload) + params: { + name: webAppName + location: location + kind: 'app' + serverFarmResourceId: webAppPlan.outputs.resourceId + httpsOnly: true + publicNetworkAccess: 'Disabled' + appInsightResourceId: appInsights.outputs.resourceId + virtualNetworkSubnetId: vnet.outputs.subnetResourceIds[0] + siteConfig: { + detailedErrorLoggingEnabled: true + httpLoggingEnabled: true + requestTracingEnabled: true + ftpsState: 'Disabled' + minTlsVersion: '1.2' + alwaysOn: true + } + appSettingsKeyValuePairs: { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: appInsights.outputs.instrumentationKey + } + managedIdentities: { + systemAssigned: true + } + } +} + + +// Front Door resource +resource frontDoorProfile 'Microsoft.Cdn/profiles@2021-06-01' = { + name: frontDoorProfileName + location: 'global' + sku: { + name: frontDoorSkuName + } + dependsOn: [ + webApp + webAppPlan + ] +} + +// Front Door endpoint(s) +resource frontDoorEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2021-06-01' = { + name: afdWebEndpoint + parent: frontDoorProfile + location: 'global' + properties: { + enabledState: 'Enabled' + } +} + +// Front Door origin group +resource frontDoorOriginGroup 'Microsoft.Cdn/profiles/originGroups@2021-06-01' = { + name: frontDoorOriginGroupName + parent: frontDoorProfile + properties: { + loadBalancingSettings: { + sampleSize: 4 + successfulSamplesRequired: 3 + } + healthProbeSettings: { + probePath: '/' + probeRequestType: 'HEAD' + probeProtocol: 'Http' + probeIntervalInSeconds: 100 + } + } +} + +// Front Door backend - Azure Web App +resource frontDoorOrigin 'Microsoft.Cdn/profiles/originGroups/origins@2022-11-01-preview' = { + name: frontDoorOriginName + parent: frontDoorOriginGroup + properties: { + hostName: webApp.outputs.defaultHostname + httpPort: 80 + httpsPort: 443 + originHostHeader: webApp.outputs.defaultHostname + priority: 1 + weight: 1000 + sharedPrivateLinkResource: { + groupId: 'sites' + privateLink: { + id: webApp.outputs.resourceId + } + privateLinkLocation: location + requestMessage: 'AFD PE to Web App' + status: 'Pending' + } + } +} + +// Front Door route +resource frontDoorRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2021-06-01' = { + name: frontDoorRouteName + parent: frontDoorEndpoint + dependsOn: [ + frontDoorOrigin // This explicit dependency is required to ensure that the origin group is not empty when the route is created. + ] + properties: { + originGroup: { + id: frontDoorOriginGroup.id + } + supportedProtocols: [ + 'Http' + 'Https' + ] + patternsToMatch: [ + '/*' + ] + forwardingProtocol: 'HttpsOnly' + linkToDefaultDomain: 'Enabled' + httpsRedirect: 'Enabled' + } +} + +// Output FQDNs +output appServiceHostName string = webApp.outputs.defaultHostname +output frontDoorEndpointHostName string = frontDoorEndpoint.properties.hostName diff --git a/bicep-examples/dependencies/modules/appInsights/appinsights.bicep b/bicep-examples/dependencies/modules/appInsights/appinsights.bicep new file mode 100644 index 0000000..5ca3a75 --- /dev/null +++ b/bicep-examples/dependencies/modules/appInsights/appinsights.bicep @@ -0,0 +1,223 @@ +metadata name = 'Application Insights' +metadata description = 'This component deploys an Application Insights instance.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the Application Insights.') +param name string + +@description('Optional. Application type.') +@allowed([ + 'web' + 'other' +]) +param applicationType string = 'web' + +@description('Required. Resource ID of the log analytics workspace which the data will be ingested to. This property is required to create an application with this API version. Applications from older versions will not have this property.') +param workspaceResourceId string + +@description('Optional. The network access type for accessing Application Insights ingestion. - Enabled or Disabled.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param publicNetworkAccessForIngestion string = 'Enabled' + +@description('Optional. The network access type for accessing Application Insights query. - Enabled or Disabled.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param publicNetworkAccessForQuery string = 'Enabled' + +@description('Optional. Retention period in days.') +@allowed([ + 30 + 60 + 90 + 120 + 180 + 270 + 365 + 550 + 730 +]) +param retentionInDays int = 365 + +@description('Optional. Percentage of the data produced by the application being monitored that is being sampled for Application Insights telemetry.') +@minValue(0) +@maxValue(100) +param samplingPercentage int = 100 + +@description('Optional. The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone.') +param kind string = '' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@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 roleAssignmentType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + +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 appInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: kind + properties: { + Application_Type: applicationType + WorkspaceResourceId: workspaceResourceId + publicNetworkAccessForIngestion: publicNetworkAccessForIngestion + publicNetworkAccessForQuery: publicNetworkAccessForQuery + RetentionInDays: retentionInDays + SamplingPercentage: samplingPercentage + } +} + +resource appInsights_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(appInsights.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: appInsights +}] + +resource appInsights_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: appInsights +}] +@description('The name of the application insights component.') +output name string = appInsights.name + +@description('The resource ID of the application insights component.') +output resourceId string = appInsights.id + +@description('The resource group the application insights component was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The application ID of the application insights component.') +output applicationId string = appInsights.properties.AppId + +@description('The location the resource was deployed into.') +output location string = appInsights.location + +@description('Application Insights Instrumentation key. A read-only value that applications can use to identify the destination for all telemetry sent to Azure Application Insights. This value will be supplied upon construction of each new Application Insights component.') +output instrumentationKey string = appInsights.properties.InstrumentationKey +// =============== // +// Definitions // +// =============== // + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics' | null)? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: 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.') + eventHubAuthorizationRuleResourceId: 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. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/main.bicep b/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/main.bicep new file mode 100644 index 0000000..49a089a --- /dev/null +++ b/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/main.bicep @@ -0,0 +1,57 @@ +metadata name = 'Private Endpoint Private DNS Zone Groups' +metadata description = 'This module deploys a Private Endpoint Private DNS Zone Group.' +metadata owner = 'Azure/module-maintainers' + +@description('Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment.') +param privateEndpointName string + +@description('Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones.') +@minLength(1) +@maxLength(5) +param privateDNSResourceIds array + +@description('Optional. The name of the private DNS zone group.') +param name string = 'default' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableDefaultTelemetry bool = true + +var privateDnsZoneConfigs = [for privateDNSResourceId in privateDNSResourceIds: { + name: last(split(privateDNSResourceId, '/'))! + properties: { + privateDnsZoneId: privateDNSResourceId + } +}] + +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 privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' existing = { + name: privateEndpointName +} + +resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-04-01' = { + name: name + parent: privateEndpoint + properties: { + privateDnsZoneConfigs: privateDnsZoneConfigs + } +} + +@description('The name of the private endpoint DNS zone group.') +output name string = privateDnsZoneGroup.name + +@description('The resource ID of the private endpoint DNS zone group.') +output resourceId string = privateDnsZoneGroup.id + +@description('The resource group the private endpoint DNS zone group was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/version.json b/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/version.json new file mode 100644 index 0000000..04a0dd1 --- /dev/null +++ b/bicep-examples/dependencies/modules/privateEndpoint/private-dns-zone-group/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.5", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/privateEndpoint/privateEndpoint.bicep b/bicep-examples/dependencies/modules/privateEndpoint/privateEndpoint.bicep new file mode 100644 index 0000000..9979785 --- /dev/null +++ b/bicep-examples/dependencies/modules/privateEndpoint/privateEndpoint.bicep @@ -0,0 +1,210 @@ +metadata name = 'Private Endpoints' +metadata description = 'This module deploys a Private Endpoint.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the private endpoint resource to create.') +param name string + +@description('Required. Resource ID of the subnet where the endpoint needs to be created.') +param subnetResourceId string + +@description('Required. Resource ID of the resource that needs to be connected to the network.') +param serviceResourceId string + +@description('Optional. Application security groups in which the private endpoint IP configuration is included.') +param applicationSecurityGroupResourceIds array? + +@description('Optional. The custom name of the network interface attached to the private endpoint.') +param customNetworkInterfaceName string? + +@description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') +param ipConfigurations ipConfigurationsType? + +@description('Required. Subtype(s) of the connection to be created. The allowed values depend on the type serviceResourceId refers to.') +param groupIds array + +@description('Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided.') +param privateDnsZoneGroupName string? + +@description('Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones.') +param privateDnsZoneResourceIds array? + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. The lock settings of the service.') +param lock lockType + +@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 roleAssignmentType + +@description('Optional. Tags to be applied on all resources/resource groups in this deployment.') +param tags object? + +@description('Optional. Custom DNS configurations.') +param customDnsConfigs customDnsConfigType? + +@description('Optional. Manual PrivateLink Service Connections.') +param manualPrivateLinkServiceConnections array? + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableDefaultTelemetry bool = true + +var enableReferencedModulesTelemetry = false + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'DNS Resolver Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d') + 'DNS Zone Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314') + 'Domain Services Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2') + 'Domain Services Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb') + 'Network Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + 'Private DNS Zone Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') +} + +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 privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { + name: name + location: location + tags: tags + properties: { + applicationSecurityGroups: [for applicationSecurityGroupResourceId in (applicationSecurityGroupResourceIds ?? []): { + id: applicationSecurityGroupResourceId + }] + customDnsConfigs: customDnsConfigs + customNetworkInterfaceName: customNetworkInterfaceName ?? '' + ipConfigurations: ipConfigurations ?? [] + manualPrivateLinkServiceConnections: manualPrivateLinkServiceConnections ?? [] + privateLinkServiceConnections: [ + { + name: name + properties: { + privateLinkServiceId: serviceResourceId + groupIds: groupIds + } + } + ] + subnet: { + id: subnetResourceId + } + } +} + +module privateEndpoint_privateDnsZoneGroup './private-dns-zone-group/main.bicep' = if (!empty(privateDnsZoneResourceIds)) { + name: '${uniqueString(deployment().name)}-PrivateEndpoint-PrivateDnsZoneGroup' + params: { + name: privateDnsZoneGroupName ?? 'default' + privateDNSResourceIds: privateDnsZoneResourceIds ?? [] + privateEndpointName: privateEndpoint.name + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +resource privateEndpoint_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' + } + scope: privateEndpoint +} + +resource privateEndpoint_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(privateEndpoint.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: privateEndpoint +}] + +@description('The resource group the private endpoint was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The resource ID of the private endpoint.') +output resourceId string = privateEndpoint.id + +@description('The name of the private endpoint.') +output name string = privateEndpoint.name + +@description('The location the resource was deployed into.') +output location string = privateEndpoint.location + +// ================ // +// Definitions // +// ================ // + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type ipConfigurationsType = { + @description('Required. The name of the resource that is unique within a resource group.') + name: string + + @description('Required. Properties of private endpoint IP configurations.') + properties: { + @description('Required. The ID of a group obtained from the remote resource that this private endpoint should connect to.') + groupId: string + + @description('Required. The member name of a group obtained from the remote resource that this private endpoint should connect to.') + memberName: string + + @description('Required. A private ip address obtained from the private endpoint\'s subnet.') + privateIPAddress: string + } +}[]? + +type customDnsConfigType = { + @description('Required. Fqdn that resolves to private endpoint ip address.') + fqdn: string + + @description('Required. A list of private ip addresses of the private endpoint.') + ipAddresses: string[] +}[]? diff --git a/bicep-examples/dependencies/modules/webApp/appPlan.bicep b/bicep-examples/dependencies/modules/webApp/appPlan.bicep new file mode 100644 index 0000000..856f2cc --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/appPlan.bicep @@ -0,0 +1,238 @@ +metadata name = 'App Service Plans' +metadata description = 'This module deploys an App Service Plan.' +metadata owner = 'Azure/module-maintainers' + +// ================ // +// Parameters // +// ================ // +@description('Required. The name of the app service plan to deploy.') +@minLength(1) +@maxLength(40) +param name string + +@description('Required. Defines the name, tier, size, family and capacity of the App Service Plan.') +param sku object + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Kind of server OS.') +@allowed([ + 'App' + 'Elastic' + 'FunctionApp' + 'Windows' + 'Linux' +]) +param kind string = 'Windows' + +@description('Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true.') +param reserved bool = false + +@description('Optional. The Resource ID of the App Service Environment to use for the App Service Plan.') +param appServiceEnvironmentId string = '' + +@description('Optional. Target worker tier assigned to the App Service plan.') +param workerTierName string = '' + +@description('Optional. If true, apps assigned to this App Service plan can be scaled independently. If false, apps assigned to this App Service plan will scale to all instances of the plan.') +param perSiteScaling bool = false + +@description('Optional. Maximum number of total workers allowed for this ElasticScaleEnabled App Service Plan.') +param maximumElasticWorkerCount int = 1 + +@description('Optional. Scaling worker count.') +param targetWorkerCount int = 0 + +@description('Optional. The instance size of the hosting plan (small, medium, or large).') +@allowed([ + 0 + 1 + 2 +]) +param targetWorkerSize int = 0 + +@description('Optional. The lock settings of the service.') +param lock lockType + +@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 roleAssignmentType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. When true, this App Service Plan will perform availability zone balancing.') +param zoneRedundant bool = false + +// ============ // +// Dependencies // +// ============ // +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Web Plan Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b') + 'Website Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') +} + +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 appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = { + name: name + kind: kind + location: location + tags: tags + sku: sku + properties: { + workerTierName: workerTierName + hostingEnvironmentProfile: !empty(appServiceEnvironmentId) ? { + id: appServiceEnvironmentId + } : null + perSiteScaling: perSiteScaling + maximumElasticWorkerCount: maximumElasticWorkerCount + reserved: reserved + targetWorkerCount: targetWorkerCount + targetWorkerSizeId: targetWorkerSize + zoneRedundant: zoneRedundant + } +} + +resource appServicePlan_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: appServicePlan +}] + +resource appServicePlan_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' + } + scope: appServicePlan +} + +resource appServicePlan_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(appServicePlan.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: appServicePlan +}] + +// =========== // +// Outputs // +// =========== // +@description('The resource group the app service plan was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the app service plan.') +output name string = appServicePlan.name + +@description('The resource ID of the app service plan.') +output resourceId string = appServicePlan.id + +@description('The location the resource was deployed into.') +output location string = appServicePlan.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics' | null)? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: 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.') + eventHubAuthorizationRuleResourceId: 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. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/main.bicep b/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/main.bicep new file mode 100644 index 0000000..c30cc79 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/main.bicep @@ -0,0 +1,56 @@ +metadata name = 'Web Site Basic Publishing Credentials Policies' +metadata description = 'This module deploys a Web Site Basic Publishing Credentials Policy.' +metadata owner = 'Azure/module-maintainers' + +@sys.description('Required. The name of the resource.') +@allowed([ + 'scm' + 'ftp' +]) +param name string + +@sys.description('Conditional. The name of the parent web site. Required if the template is used in a standalone deployment.') +param webAppName string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +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 webApp 'Microsoft.Web/sites@2022-09-01' existing = { + name: webAppName +} + +resource basicPublishingCredentialsPolicy 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01' = { + name: name + location: location + parent: webApp + properties: { + allow: true + } +} + +@sys.description('The name of the basic publishing credential policy.') +output name string = basicPublishingCredentialsPolicy.name + +@sys.description('The resource ID of the basic publishing credential policy.') +output resourceId string = basicPublishingCredentialsPolicy.id + +@sys.description('The name of the resource group the basic publishing credential policy was deployed into.') +output resourceGroupName string = resourceGroup().name + +@sys.description('The location the resource was deployed into.') +output location string = basicPublishingCredentialsPolicy.location diff --git a/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/version.json b/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/basic-publishing-credentials-policy/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/config--appsettings/main.bicep b/bicep-examples/dependencies/modules/webApp/site/config--appsettings/main.bicep new file mode 100644 index 0000000..75f9a5d --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/config--appsettings/main.bicep @@ -0,0 +1,86 @@ +metadata name = 'Site App Settings' +metadata description = 'This module deploys a Site App Setting.' +metadata owner = 'Azure/module-maintainers' + +@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 site 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 storageAccountResourceId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightResourceId 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 a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +var azureWebJobsValues = !empty(storageAccountResourceId) ? 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(appInsightResourceId) ? { + APPINSIGHTS_INSTRUMENTATIONKEY: appInsight.properties.InstrumentationKey + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsight.properties.ConnectionString +} : {} + +var expandedAppSettings = union(appSettingsKeyValuePairs, azureWebJobsValues, appInsightsValues) + +resource app 'Microsoft.Web/sites@2022-09-01' existing = { + name: appName +} + +resource appInsight 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(appInsightResourceId)) { + name: last(split(appInsightResourceId, '/'))! + scope: resourceGroup(split(appInsightResourceId, '/')[2], split(appInsightResourceId, '/')[4]) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (!empty(storageAccountResourceId)) { + name: last(split(storageAccountResourceId, '/'))! + scope: resourceGroup(split(storageAccountResourceId, '/')[2], split(storageAccountResourceId, '/')[4]) +} + +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 appSettings 'Microsoft.Web/sites/config@2022-09-01' = { + name: 'appsettings' + kind: kind + parent: app + properties: expandedAppSettings +} + +@description('The name of the site config.') +output name string = appSettings.name + +@description('The resource ID of the site config.') +output resourceId string = appSettings.id + +@description('The resource group the site config was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/bicep-examples/dependencies/modules/webApp/site/config--appsettings/version.json b/bicep-examples/dependencies/modules/webApp/site/config--appsettings/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/config--appsettings/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/main.bicep b/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/main.bicep new file mode 100644 index 0000000..14f9589 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/main.bicep @@ -0,0 +1,54 @@ +metadata name = 'Site Auth Settings V2 Config' +metadata description = 'This module deploys a Site Auth Settings V2 Configuration.' +metadata owner = 'Azure/module-maintainers' + +@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 site 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 a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource app 'Microsoft.Web/sites@2022-09-01' existing = { + name: appName +} + +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 appSettings 'Microsoft.Web/sites/config@2022-09-01' = { + name: 'authsettingsV2' + kind: kind + parent: app + properties: authSettingV2Configuration +} + +@description('The name of the site config.') +output name string = appSettings.name + +@description('The resource ID of the site config.') +output resourceId string = appSettings.id + +@description('The resource group the site config was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/version.json b/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/config--authsettingsv2/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/main.bicep b/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/main.bicep new file mode 100644 index 0000000..f1972af --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/main.bicep @@ -0,0 +1,66 @@ +metadata name = 'Web/Function Apps Hybrid Connection Relay' +metadata description = 'This module deploys a Site Hybrid Connection Namespace Relay.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The resource ID of the relay namespace hybrid connection.') +param hybridConnectionResourceId string + +@description('Conditional. The name of the parent web site. Required if the template is used in a standalone deployment.') +param appName string + +@description('Optional. Name of the authorization rule send key to use.') +param sendKeyName string = 'defaultSender' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +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 namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[8] + scope: resourceGroup(split(hybridConnectionResourceId, '/')[2], split(hybridConnectionResourceId, '/')[4]) + + resource hybridConnection 'hybridConnections@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[10] + + resource authorizationRule 'authorizationRules@2021-11-01' existing = { + name: sendKeyName + } + } +} + +resource hybridConnectionRelay 'Microsoft.Web/sites/hybridConnectionNamespaces/relays@2022-09-01' = { + name: '${appName}/${namespace.name}/${namespace::hybridConnection.name}' + properties: { + serviceBusNamespace: namespace.name + serviceBusSuffix: split(substring(namespace.properties.serviceBusEndpoint, indexOf(namespace.properties.serviceBusEndpoint, '.servicebus')), ':')[0] + relayName: namespace::hybridConnection.name + relayArmUri: namespace::hybridConnection.id + hostname: split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[0] + port: int(split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[1]) + sendKeyName: namespace::hybridConnection::authorizationRule.name + sendKeyValue: namespace::hybridConnection::authorizationRule.listKeys().primaryKey + } +} + +@description('The name of the hybrid connection relay..') +output name string = hybridConnectionRelay.name + +@description('The resource ID of the hybrid connection relay.') +output resourceId string = hybridConnectionRelay.id + +@description('The name of the resource group the resource was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/version.json b/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/hybrid-connection-namespace/relay/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/main.bicep b/bicep-examples/dependencies/modules/webApp/site/main.bicep new file mode 100644 index 0000000..b2ac05d --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/main.bicep @@ -0,0 +1,559 @@ +metadata name = 'Web/Function Apps' +metadata description = 'This module deploys a Web or Function App.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the site.') +param name string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Required. Type of site 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 resource ID of the app service plan to use for the site.') +param serverFarmResourceId string + +@description('Optional. Configures a site 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 appServiceEnvironmentResourceId string = '' + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@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 = '' + +@description('Optional. To enable accessing content over virtual network.') +param vnetContentShareEnabled bool = false + +@description('Optional. To enable pulling image over Virtual Network.') +param vnetImagePullEnabled bool = false + +@description('Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied.') +param vnetRouteAllEnabled bool = false + +@description('Optional. Stop SCM (KUDU) site when the app is stopped.') +param scmSiteAlsoStopped bool = false + +@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 storageAccountResourceId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightResourceId 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 = {} + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointType + +@description('Optional. Configuration for deployment slots for an app.') +param slots array = [] + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@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 roleAssignmentType + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. To enable client certificate authentication (TLS mutual authentication).') +param clientCertEnabled bool = false + +@description('Optional. Client certificate authentication comma-separated exclusion paths.') +param clientCertExclusionPaths string = '' + +@description('Optional. This composes with ClientCertEnabled setting.

- ClientCertEnabled: false means ClientCert is ignored.

- ClientCertEnabled: true and ClientCertMode: Required means ClientCert is required.

- ClientCertEnabled: true and ClientCertMode: Optional means ClientCert is optional or accepted.') +@allowed([ + 'Optional' + 'OptionalInteractiveUser' + 'Required' +]) +param clientCertMode string = 'Optional' + +@description('Optional. If specified during app creation, the app is cloned from a source app.') +param cloningInfo object = {} + +@description('Optional. Size of the function container.') +param containerSize int = -1 + +@description('Optional. Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification.') +param customDomainVerificationId string = '' + +@description('Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only).') +param dailyMemoryTimeQuota int = -1 + +@description('Optional. Setting this value to false disables the app (takes the app offline).') +param enabled bool = true + +@description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.') +param hostNameSslStates array = [] + +@description('Optional. Hyper-V sandbox.') +param hyperV bool = false + +@description('Optional. Site redundancy mode.') +@allowed([ + 'ActiveActive' + 'Failover' + 'GeoRedundant' + 'Manual' + 'None' +]) +param redundancyMode string = 'None' + +@description('Optional. The site publishing credential policy names which are associated with the sites.') +param basicPublishingCredentialsPolicies array = [] + +@description('Optional. Names of hybrid connection relays to connect app with.') +param hybridConnectionRelays array = [] + +@description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set.') +@allowed([ + '' + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string = '' + +var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourcesIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } + +var identity = !empty(managedIdentities) ? { + type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null +} : null + +var enableReferencedModulesTelemetry = false + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Web Plan Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b') + 'Website Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') +} + +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 app 'Microsoft.Web/sites@2022-09-01' = { + name: name + location: location + kind: kind + tags: tags + identity: identity + properties: { + serverFarmId: serverFarmResourceId + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: httpsOnly + hostingEnvironmentProfile: !empty(appServiceEnvironmentResourceId) ? { + id: appServiceEnvironmentResourceId + } : null + storageAccountRequired: storageAccountRequired + keyVaultReferenceIdentity: !empty(keyVaultAccessIdentityResourceId) ? keyVaultAccessIdentityResourceId : null + virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : any(null) + siteConfig: siteConfig + clientCertEnabled: clientCertEnabled + clientCertExclusionPaths: !empty(clientCertExclusionPaths) ? clientCertExclusionPaths : null + clientCertMode: clientCertMode + cloningInfo: !empty(cloningInfo) ? cloningInfo : null + containerSize: containerSize != -1 ? containerSize : null + customDomainVerificationId: !empty(customDomainVerificationId) ? customDomainVerificationId : null + dailyMemoryTimeQuota: dailyMemoryTimeQuota != -1 ? dailyMemoryTimeQuota : null + enabled: enabled + hostNameSslStates: hostNameSslStates + hyperV: hyperV + redundancyMode: redundancyMode + publicNetworkAccess: !empty(publicNetworkAccess) ? any(publicNetworkAccess) : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled') + vnetContentShareEnabled: vnetContentShareEnabled + vnetImagePullEnabled: vnetImagePullEnabled + vnetRouteAllEnabled: vnetRouteAllEnabled + scmSiteAlsoStopped: scmSiteAlsoStopped + } +} + +module app_appsettings 'config--appsettings/main.bicep' = if (!empty(appSettingsKeyValuePairs)) { + name: '${uniqueString(deployment().name, location)}-Site-Config-AppSettings' + params: { + appName: app.name + kind: kind + storageAccountResourceId: storageAccountResourceId + appInsightResourceId: appInsightResourceId + setAzureWebJobsDashboard: setAzureWebJobsDashboard + appSettingsKeyValuePairs: appSettingsKeyValuePairs + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module app_authsettingsv2 'config--authsettingsv2/main.bicep' = if (!empty(authSettingV2Configuration)) { + name: '${uniqueString(deployment().name, location)}-Site-Config-AuthSettingsV2' + params: { + appName: app.name + kind: kind + authSettingV2Configuration: authSettingV2Configuration + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +@batchSize(1) +module app_slots 'slot/main.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 + appServiceEnvironmentResourceId: !empty(appServiceEnvironmentResourceId) ? appServiceEnvironmentResourceId : '' + clientAffinityEnabled: contains(slot, 'clientAffinityEnabled') ? slot.clientAffinityEnabled : clientAffinityEnabled + managedIdentities: contains(slot, 'managedIdentities') ? slot.managedIdentities : managedIdentities + 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 + storageAccountResourceId: contains(slot, 'storageAccountResourceId') ? slot.storageAccountResourceId : storageAccountResourceId + appInsightResourceId: contains(slot, 'appInsightResourceId') ? slot.appInsightResourceId : appInsightResourceId + setAzureWebJobsDashboard: contains(slot, 'setAzureWebJobsDashboard') ? slot.setAzureWebJobsDashboard : setAzureWebJobsDashboard + authSettingV2Configuration: contains(slot, 'authSettingV2Configuration') ? slot.authSettingV2Configuration : authSettingV2Configuration + enableDefaultTelemetry: enableReferencedModulesTelemetry + diagnosticSettings: slot.?diagnosticSettings + roleAssignments: contains(slot, 'roleAssignments') ? slot.roleAssignments : roleAssignments + appSettingsKeyValuePairs: contains(slot, 'appSettingsKeyValuePairs') ? slot.appSettingsKeyValuePairs : appSettingsKeyValuePairs + lock: slot.?lock ?? lock + privateEndpoints: contains(slot, 'privateEndpoints') ? slot.privateEndpoints : privateEndpoints + tags: slot.?tags ?? tags + clientCertEnabled: contains(slot, 'clientCertEnabled') ? slot.clientCertEnabled : false + clientCertExclusionPaths: contains(slot, 'clientCertExclusionPaths') ? slot.clientCertExclusionPaths : '' + clientCertMode: contains(slot, 'clientCertMode') ? slot.clientCertMode : 'Optional' + cloningInfo: contains(slot, 'cloningInfo') ? slot.cloningInfo : {} + containerSize: contains(slot, 'containerSize') ? slot.containerSize : -1 + customDomainVerificationId: contains(slot, 'customDomainVerificationId') ? slot.customDomainVerificationId : '' + dailyMemoryTimeQuota: contains(slot, 'dailyMemoryTimeQuota') ? slot.dailyMemoryTimeQuota : -1 + enabled: contains(slot, 'enabled') ? slot.enabled : true + hostNameSslStates: contains(slot, 'hostNameSslStates') ? slot.hostNameSslStates : [] + hyperV: contains(slot, 'hyperV') ? slot.hyperV : false + publicNetworkAccess: contains(slot, 'publicNetworkAccess') ? slot.publicNetworkAccess : '' + redundancyMode: contains(slot, 'redundancyMode') ? slot.redundancyMode : 'None' + vnetContentShareEnabled: contains(slot, 'vnetContentShareEnabled') ? slot.vnetContentShareEnabled : false + vnetImagePullEnabled: contains(slot, 'vnetImagePullEnabled') ? slot.vnetImagePullEnabled : false + vnetRouteAllEnabled: contains(slot, 'vnetRouteAllEnabled') ? slot.vnetRouteAllEnabled : false + hybridConnectionRelays: contains(slot, 'hybridConnectionRelays') ? slot.hybridConnectionRelays : [] + } +}] + +module app_basicPublishingCredentialsPolicies 'basic-publishing-credentials-policy/main.bicep' = [for (basicPublishingCredentialsPolicy, index) in basicPublishingCredentialsPolicies: { + name: '${uniqueString(deployment().name, location)}-Site-Publis-Cred-${index}' + params: { + webAppName: app.name + name: basicPublishingCredentialsPolicy.name + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +module app_hybridConnectionRelays 'hybrid-connection-namespace/relay/main.bicep' = [for (hybridConnectionRelay, index) in hybridConnectionRelays: { + name: '${uniqueString(deployment().name, location)}-HybridConnectionRelay-${index}' + params: { + hybridConnectionResourceId: hybridConnectionRelay.resourceId + appName: app.name + sendKeyName: contains(hybridConnectionRelay, 'sendKeyName') ? hybridConnectionRelay.sendKeyName : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource app_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' + } + scope: app +} + +resource app_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: app +}] + +resource app_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(app.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: app +}] + +module app_privateEndpoints '../../network/private-endpoint/main.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.?service ?? 'sites' + ] + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + serviceResourceId: app.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: privateEndpoint.?lock ?? lock + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } +}] + +@description('The name of the site.') +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 systemAssignedMIPrincipalId string = (managedIdentities.?systemAssigned ?? false) && 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.systemAssignedMIPrincipalId] + +@description('The location the resource was deployed into.') +output location string = app.location + +@description('Default hostname of the app.') +output defaultHostname string = app.properties.defaultHostName + +// =============== // +// Definitions // +// =============== // + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + userAssignedResourcesIds: string[]? +}? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + @description('Required. Fqdn that resolves to private endpoint ip address.') + fqdn: string? + + @description('Required. A list of private ip addresses of the private endpoint.') + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + @description('Required. The name of the resource that is unique within a resource group.') + name: string + + @description('Required. Properties of private endpoint IP configurations.') + properties: { + @description('Required. The ID of a group obtained from the remote resource that this private endpoint should connect to.') + groupId: string + + @description('Required. The member name of a group obtained from the remote resource that this private endpoint should connect to.') + memberName: string + + @description('Required. A private ip address obtained from the private endpoint\'s subnet.') + privateIPAddress: string + } + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics' | null)? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: 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.') + eventHubAuthorizationRuleResourceId: 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. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/main.bicep b/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/main.bicep new file mode 100644 index 0000000..5f3ea19 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/main.bicep @@ -0,0 +1,93 @@ +metadata name = 'Site Slot App Settings' +metadata description = 'This module deploys a Site Slot App Setting.' +metadata owner = 'Azure/module-maintainers' + +@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 storageAccountResourceId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightResourceId 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 + +var azureWebJobsValues = !empty(storageAccountResourceId) ? 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(appInsightResourceId) ? { + APPINSIGHTS_INSTRUMENTATIONKEY: appInsight.properties.InstrumentationKey + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsight.properties.ConnectionString +} : {} + +var expandedAppSettings = union(appSettingsKeyValuePairs, azureWebJobsValues, appInsightsValues) + +resource app 'Microsoft.Web/sites@2022-09-01' existing = { + name: appName + + resource slot 'slots' existing = { + name: slotName + } +} + +resource appInsight 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(appInsightResourceId)) { + name: last(split(appInsightResourceId, '/'))! + scope: resourceGroup(split(appInsightResourceId, '/')[2], split(appInsightResourceId, '/')[4]) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (!empty(storageAccountResourceId)) { + name: last(split(storageAccountResourceId, '/'))! + scope: resourceGroup(split(storageAccountResourceId, '/')[2], split(storageAccountResourceId, '/')[4]) +} + +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-09-01' = { + name: 'appsettings' + kind: kind + parent: app::slot + properties: expandedAppSettings +} + +@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/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/version.json b/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/config--appsettings/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/main.bicep b/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/main.bicep new file mode 100644 index 0000000..739d774 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/main.bicep @@ -0,0 +1,61 @@ +metadata name = 'Site Slot Auth Settings V2 Config' +metadata description = 'This module deploys a Site Auth Settings V2 Configuration.' +metadata owner = 'Azure/module-maintainers' + +@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 + +resource app 'Microsoft.Web/sites@2022-09-01' existing = { + name: appName + + resource slot 'slots' existing = { + name: slotName + } +} + +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-09-01' = { + name: 'authsettingsV2' + kind: kind + parent: app::slot + properties: authSettingV2Configuration +} + +@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/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/version.json b/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/config--authsettingsv2/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/main.bicep b/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/main.bicep new file mode 100644 index 0000000..fe51fdf --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/main.bicep @@ -0,0 +1,69 @@ +metadata name = 'Web/Function Apps Slot Hybrid Connection Relay' +metadata description = 'This module deploys a Site Slot Hybrid Connection Namespace Relay.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The resource ID of the relay namespace hybrid connection.') +param hybridConnectionResourceId string + +@description('Conditional. The name of the site slot. Required if the template is used in a standalone deployment.') +param slotName string + +@description('Conditional. The name of the parent web site. Required if the template is used in a standalone deployment.') +param appName string + +@description('Optional. Name of the authorization rule send key to use.') +param sendKeyName string = 'defaultSender' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +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 namespace 'Microsoft.Relay/namespaces@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[8] + scope: resourceGroup(split(hybridConnectionResourceId, '/')[2], split(hybridConnectionResourceId, '/')[4]) + + resource hybridConnection 'hybridConnections@2021-11-01' existing = { + name: split(hybridConnectionResourceId, '/')[10] + + resource authorizationRule 'authorizationRules@2021-11-01' existing = { + name: sendKeyName + } + } +} + +resource hybridConnectionRelay 'Microsoft.Web/sites/slots/hybridConnectionNamespaces/relays@2022-09-01' = { + name: '${appName}/${slotName}/${namespace.name}/${namespace::hybridConnection.name}' + properties: { + serviceBusNamespace: namespace.name + serviceBusSuffix: split(substring(namespace.properties.serviceBusEndpoint, indexOf(namespace.properties.serviceBusEndpoint, '.servicebus')), ':')[0] + relayName: namespace::hybridConnection.name + relayArmUri: namespace::hybridConnection.id + hostname: split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[0] + port: int(split(json(namespace::hybridConnection.properties.userMetadata)[0].value, ':')[1]) + sendKeyName: namespace::hybridConnection::authorizationRule.name + sendKeyValue: namespace::hybridConnection::authorizationRule.listKeys().primaryKey + } +} + +@description('The name of the hybrid connection relay..') +output name string = hybridConnectionRelay.name + +@description('The resource ID of the hybrid connection relay.') +output resourceId string = hybridConnectionRelay.id + +@description('The name of the resource group the resource was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/version.json b/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/hybrid-connection-namespace/relay/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/main.bicep b/bicep-examples/dependencies/modules/webApp/site/slot/main.bicep new file mode 100644 index 0000000..e38a90e --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/main.bicep @@ -0,0 +1,492 @@ +metadata name = 'Web/Function App Deployment Slots' +metadata description = 'This module deploys a Web or Function App Deployment Slot.' +metadata owner = 'Azure/module-maintainers' + +@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 appServiceEnvironmentResourceId string = '' + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@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 = '' + +@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 storageAccountResourceId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightResourceId 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 = {} + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Configuration details for private endpoints.') +param privateEndpoints privateEndpointType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via the Customer Usage Attribution ID (GUID).') +param enableDefaultTelemetry bool = true + +@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 roleAssignmentType + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. To enable client certificate authentication (TLS mutual authentication).') +param clientCertEnabled bool = false + +@description('Optional. Client certificate authentication comma-separated exclusion paths.') +param clientCertExclusionPaths string = '' + +@description('Optional. This composes with ClientCertEnabled setting.

- ClientCertEnabled: false means ClientCert is ignored.

- ClientCertEnabled: true and ClientCertMode: Required means ClientCert is required.

- ClientCertEnabled: true and ClientCertMode: Optional means ClientCert is optional or accepted.') +@allowed([ + 'Optional' + 'OptionalInteractiveUser' + 'Required' +]) +param clientCertMode string = 'Optional' + +@description('Optional. If specified during app creation, the app is cloned from a source app.') +param cloningInfo object = {} + +@description('Optional. Size of the function container.') +param containerSize int = -1 + +@description('Optional. Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification.') +param customDomainVerificationId string = '' + +@description('Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only).') +param dailyMemoryTimeQuota int = -1 + +@description('Optional. Setting this value to false disables the app (takes the app offline).') +param enabled bool = true + +@description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.') +param hostNameSslStates array = [] + +@description('Optional. Hyper-V sandbox.') +param hyperV bool = false + +@description('Optional. Allow or block all public traffic.') +@allowed([ + 'Enabled' + 'Disabled' + '' +]) +param publicNetworkAccess string = '' + +@description('Optional. Site redundancy mode.') +@allowed([ + 'ActiveActive' + 'Failover' + 'GeoRedundant' + 'Manual' + 'None' +]) +param redundancyMode string = 'None' + +@description('Optional. To enable accessing content over virtual network.') +param vnetContentShareEnabled bool = false + +@description('Optional. To enable pulling image over Virtual Network.') +param vnetImagePullEnabled bool = false + +@description('Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied.') +param vnetRouteAllEnabled bool = false + +@description('Optional. Names of hybrid connection relays to connect app with.') +param hybridConnectionRelays array = [] + +var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourcesIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } + +var identity = !empty(managedIdentities) ? { + type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null +} : null + +var enableReferencedModulesTelemetry = false + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Web Plan Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b') + 'Website Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') +} + +resource app 'Microsoft.Web/sites@2021-03-01' existing = { + name: appName +} + +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-09-01' = { + name: name + parent: app + location: location + kind: kind + tags: tags + identity: identity + properties: { + serverFarmId: serverFarmResourceId + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: httpsOnly + hostingEnvironmentProfile: !empty(appServiceEnvironmentResourceId) ? { + id: appServiceEnvironmentResourceId + } : null + storageAccountRequired: storageAccountRequired + keyVaultReferenceIdentity: !empty(keyVaultAccessIdentityResourceId) ? keyVaultAccessIdentityResourceId : any(null) + virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : any(null) + siteConfig: siteConfig + clientCertEnabled: clientCertEnabled + clientCertExclusionPaths: !empty(clientCertExclusionPaths) ? clientCertExclusionPaths : null + clientCertMode: clientCertMode + cloningInfo: !empty(cloningInfo) ? cloningInfo : null + containerSize: containerSize != -1 ? containerSize : null + customDomainVerificationId: !empty(customDomainVerificationId) ? customDomainVerificationId : null + dailyMemoryTimeQuota: dailyMemoryTimeQuota != -1 ? dailyMemoryTimeQuota : null + enabled: enabled + hostNameSslStates: hostNameSslStates + hyperV: hyperV + publicNetworkAccess: publicNetworkAccess + redundancyMode: redundancyMode + vnetContentShareEnabled: vnetContentShareEnabled + vnetImagePullEnabled: vnetImagePullEnabled + vnetRouteAllEnabled: vnetRouteAllEnabled + } +} + +module slot_appsettings 'config--appsettings/main.bicep' = if (!empty(appSettingsKeyValuePairs)) { + name: '${uniqueString(deployment().name, location)}-Slot-${name}-Config-AppSettings' + params: { + slotName: slot.name + appName: app.name + kind: kind + storageAccountResourceId: storageAccountResourceId + appInsightResourceId: appInsightResourceId + setAzureWebJobsDashboard: setAzureWebJobsDashboard + appSettingsKeyValuePairs: appSettingsKeyValuePairs + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module slot_authsettingsv2 'config--authsettingsv2/main.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 + } +} + +module slot_hybridConnectionRelays 'hybrid-connection-namespace/relay/main.bicep' = [for (hybridConnectionRelay, index) in hybridConnectionRelays: { + name: '${uniqueString(deployment().name, location)}-Slot-HybridConnectionRelay-${index}' + params: { + hybridConnectionResourceId: hybridConnectionRelay.resourceId + appName: app.name + slotName: slot.name + sendKeyName: contains(hybridConnectionRelay, 'sendKeyName') ? hybridConnectionRelay.sendKeyName : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource slot_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' + } + scope: slot +} + +resource slot_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: slot +}] + +resource slot_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(slot.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: slot +}] + +module slot_privateEndpoints '../../../privateEndpoint/privateEndpoint.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.?service ?? 'sites' + ] + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + serviceResourceId: app.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: privateEndpoint.?lock ?? lock + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } +}] + +@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 systemAssignedMIPrincipalId string = (managedIdentities.?systemAssigned ?? false) && contains(slot.identity, 'principalId') ? slot.identity.principalId : '' + +@description('The location the resource was deployed into.') +output location string = slot.location + +// =============== // +// Definitions // +// =============== // + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + userAssignedResourcesIds: string[]? +}? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + @description('Required. Fqdn that resolves to private endpoint ip address.') + fqdn: string? + + @description('Required. A list of private ip addresses of the private endpoint.') + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + @description('Required. The name of the resource that is unique within a resource group.') + name: string + + @description('Required. Properties of private endpoint IP configurations.') + properties: { + @description('Required. The ID of a group obtained from the remote resource that this private endpoint should connect to.') + groupId: string + + @description('Required. The member name of a group obtained from the remote resource that this private endpoint should connect to.') + memberName: string + + @description('Required. A private ip address obtained from the private endpoint\'s subnet.') + privateIPAddress: string + } + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics' | null)? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: 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.') + eventHubAuthorizationRuleResourceId: 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. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/bicep-examples/dependencies/modules/webApp/site/slot/version.json b/bicep-examples/dependencies/modules/webApp/site/slot/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/slot/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/site/version.json b/bicep-examples/dependencies/modules/webApp/site/version.json new file mode 100644 index 0000000..96236a6 --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/site/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/bicep-examples/dependencies/modules/webApp/webApp.bicep b/bicep-examples/dependencies/modules/webApp/webApp.bicep new file mode 100644 index 0000000..593c38e --- /dev/null +++ b/bicep-examples/dependencies/modules/webApp/webApp.bicep @@ -0,0 +1,560 @@ +metadata name = 'Web/Function Apps' +metadata description = 'This module deploys a Web or Function App.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the site.') +param name string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Required. Type of site 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 resource ID of the app service plan to use for the site.') +param serverFarmResourceId string + +@description('Optional. Configures a site 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 appServiceEnvironmentResourceId string = '' + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@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 = '' + +@description('Optional. To enable accessing content over virtual network.') +param vnetContentShareEnabled bool = false + +@description('Optional. To enable pulling image over Virtual Network.') +param vnetImagePullEnabled bool = false + +@description('Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied.') +param vnetRouteAllEnabled bool = false + +@description('Optional. Stop SCM (KUDU) site when the app is stopped.') +param scmSiteAlsoStopped bool = false + +@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 storageAccountResourceId string = '' + +@description('Optional. Resource ID of the app insight to leverage for this resource.') +param appInsightResourceId 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 = {} + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointType + +@description('Optional. Configuration for deployment slots for an app.') +param slots array = [] + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@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 roleAssignmentType + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. To enable client certificate authentication (TLS mutual authentication).') +param clientCertEnabled bool = false + +@description('Optional. Client certificate authentication comma-separated exclusion paths.') +param clientCertExclusionPaths string = '' + +@description('Optional. This composes with ClientCertEnabled setting.

- ClientCertEnabled: false means ClientCert is ignored.

- ClientCertEnabled: true and ClientCertMode: Required means ClientCert is required.

- ClientCertEnabled: true and ClientCertMode: Optional means ClientCert is optional or accepted.') +@allowed([ + 'Optional' + 'OptionalInteractiveUser' + 'Required' +]) +param clientCertMode string = 'Optional' + +@description('Optional. If specified during app creation, the app is cloned from a source app.') +param cloningInfo object = {} + +@description('Optional. Size of the function container.') +param containerSize int = -1 + +@description('Optional. Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification.') +param customDomainVerificationId string = '' + +@description('Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only).') +param dailyMemoryTimeQuota int = -1 + +@description('Optional. Setting this value to false disables the app (takes the app offline).') +param enabled bool = true + +@description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.') +param hostNameSslStates array = [] + +@description('Optional. Hyper-V sandbox.') +param hyperV bool = false + +@description('Optional. Site redundancy mode.') +@allowed([ + 'ActiveActive' + 'Failover' + 'GeoRedundant' + 'Manual' + 'None' +]) +param redundancyMode string = 'None' + +@description('Optional. The site publishing credential policy names which are associated with the sites.') +param basicPublishingCredentialsPolicies array = [] + +@description('Optional. Names of hybrid connection relays to connect app with.') +param hybridConnectionRelays array = [] + +@description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set.') +@allowed([ + '' + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string = '' + +var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourcesIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } + +var identity = !empty(managedIdentities) ? { + type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourcesIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null +} : null + +var enableReferencedModulesTelemetry = false + +var builtInRoleNames = { + 'App Compliance Automation Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2') + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') + 'Web Plan Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b') + 'Website Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') +} + +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 app 'Microsoft.Web/sites@2022-09-01' = { + name: name + location: location + kind: kind + tags: tags + identity: identity + properties: { + serverFarmId: serverFarmResourceId + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: httpsOnly + hostingEnvironmentProfile: !empty(appServiceEnvironmentResourceId) ? { + id: appServiceEnvironmentResourceId + } : null + storageAccountRequired: storageAccountRequired + keyVaultReferenceIdentity: !empty(keyVaultAccessIdentityResourceId) ? keyVaultAccessIdentityResourceId : null + virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : any(null) + siteConfig: siteConfig + clientCertEnabled: clientCertEnabled + clientCertExclusionPaths: !empty(clientCertExclusionPaths) ? clientCertExclusionPaths : null + clientCertMode: clientCertMode + cloningInfo: !empty(cloningInfo) ? cloningInfo : null + containerSize: containerSize != -1 ? containerSize : null + customDomainVerificationId: !empty(customDomainVerificationId) ? customDomainVerificationId : null + dailyMemoryTimeQuota: dailyMemoryTimeQuota != -1 ? dailyMemoryTimeQuota : null + enabled: enabled + hostNameSslStates: hostNameSslStates + hyperV: hyperV + redundancyMode: redundancyMode + publicNetworkAccess: !empty(publicNetworkAccess) ? any(publicNetworkAccess) : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled') + vnetContentShareEnabled: vnetContentShareEnabled + vnetImagePullEnabled: vnetImagePullEnabled + vnetRouteAllEnabled: vnetRouteAllEnabled + scmSiteAlsoStopped: scmSiteAlsoStopped + } +} + +module app_appsettings './site/config--appsettings/main.bicep' = if (!empty(appSettingsKeyValuePairs)) { + name: '${uniqueString(deployment().name, location)}-Site-Config-AppSettings' + params: { + appName: app.name + kind: kind + storageAccountResourceId: storageAccountResourceId + appInsightResourceId: appInsightResourceId + setAzureWebJobsDashboard: setAzureWebJobsDashboard + appSettingsKeyValuePairs: appSettingsKeyValuePairs + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module app_authsettingsv2 './site/config--authsettingsv2/main.bicep' = if (!empty(authSettingV2Configuration)) { + name: '${uniqueString(deployment().name, location)}-Site-Config-AuthSettingsV2' + params: { + appName: app.name + kind: kind + authSettingV2Configuration: authSettingV2Configuration + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +@batchSize(1) +module app_slots './site/slot/main.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 + appServiceEnvironmentResourceId: !empty(appServiceEnvironmentResourceId) ? appServiceEnvironmentResourceId : '' + clientAffinityEnabled: contains(slot, 'clientAffinityEnabled') ? slot.clientAffinityEnabled : clientAffinityEnabled + managedIdentities: contains(slot, 'managedIdentities') ? slot.managedIdentities : managedIdentities + 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 + storageAccountResourceId: contains(slot, 'storageAccountResourceId') ? slot.storageAccountResourceId : storageAccountResourceId + appInsightResourceId: contains(slot, 'appInsightResourceId') ? slot.appInsightResourceId : appInsightResourceId + setAzureWebJobsDashboard: contains(slot, 'setAzureWebJobsDashboard') ? slot.setAzureWebJobsDashboard : setAzureWebJobsDashboard + authSettingV2Configuration: contains(slot, 'authSettingV2Configuration') ? slot.authSettingV2Configuration : authSettingV2Configuration + enableDefaultTelemetry: enableReferencedModulesTelemetry + diagnosticSettings: slot.?diagnosticSettings + roleAssignments: contains(slot, 'roleAssignments') ? slot.roleAssignments : roleAssignments + appSettingsKeyValuePairs: contains(slot, 'appSettingsKeyValuePairs') ? slot.appSettingsKeyValuePairs : appSettingsKeyValuePairs + lock: slot.?lock ?? lock + privateEndpoints: contains(slot, 'privateEndpoints') ? slot.privateEndpoints : privateEndpoints + tags: slot.?tags ?? tags + clientCertEnabled: contains(slot, 'clientCertEnabled') ? slot.clientCertEnabled : false + clientCertExclusionPaths: contains(slot, 'clientCertExclusionPaths') ? slot.clientCertExclusionPaths : '' + clientCertMode: contains(slot, 'clientCertMode') ? slot.clientCertMode : 'Optional' + cloningInfo: contains(slot, 'cloningInfo') ? slot.cloningInfo : {} + containerSize: contains(slot, 'containerSize') ? slot.containerSize : -1 + customDomainVerificationId: contains(slot, 'customDomainVerificationId') ? slot.customDomainVerificationId : '' + dailyMemoryTimeQuota: contains(slot, 'dailyMemoryTimeQuota') ? slot.dailyMemoryTimeQuota : -1 + enabled: contains(slot, 'enabled') ? slot.enabled : true + hostNameSslStates: contains(slot, 'hostNameSslStates') ? slot.hostNameSslStates : [] + hyperV: contains(slot, 'hyperV') ? slot.hyperV : false + publicNetworkAccess: contains(slot, 'publicNetworkAccess') ? slot.publicNetworkAccess : '' + redundancyMode: contains(slot, 'redundancyMode') ? slot.redundancyMode : 'None' + vnetContentShareEnabled: contains(slot, 'vnetContentShareEnabled') ? slot.vnetContentShareEnabled : false + vnetImagePullEnabled: contains(slot, 'vnetImagePullEnabled') ? slot.vnetImagePullEnabled : false + vnetRouteAllEnabled: contains(slot, 'vnetRouteAllEnabled') ? slot.vnetRouteAllEnabled : false + hybridConnectionRelays: contains(slot, 'hybridConnectionRelays') ? slot.hybridConnectionRelays : [] + } +}] + +module app_basicPublishingCredentialsPolicies './site/basic-publishing-credentials-policy/main.bicep' = [for (basicPublishingCredentialsPolicy, index) in basicPublishingCredentialsPolicies: { + name: '${uniqueString(deployment().name, location)}-Site-Publis-Cred-${index}' + params: { + location: location + webAppName: app.name + name: basicPublishingCredentialsPolicy.name + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +module app_hybridConnectionRelays './site/hybrid-connection-namespace/relay/main.bicep' = [for (hybridConnectionRelay, index) in hybridConnectionRelays: { + name: '${uniqueString(deployment().name, location)}-HybridConnectionRelay-${index}' + params: { + hybridConnectionResourceId: hybridConnectionRelay.resourceId + appName: app.name + sendKeyName: contains(hybridConnectionRelay, 'sendKeyName') ? hybridConnectionRelay.sendKeyName : null + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +}] + +resource app_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.' + } + scope: app +} + +resource app_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: app +}] + +resource app_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(app.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: app +}] + +module app_privateEndpoints '../privateEndpoint/privateEndpoint.bicep' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.?service ?? 'sites' + ] + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + serviceResourceId: app.id + subnetResourceId: privateEndpoint.subnetResourceId + enableDefaultTelemetry: privateEndpoint.?enableDefaultTelemetry ?? enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: privateEndpoint.?lock ?? lock + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } +}] + +@description('The name of the site.') +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 systemAssignedMIPrincipalId string = (managedIdentities.?systemAssigned ?? false) && 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.systemAssignedMIPrincipalId] + +@description('The location the resource was deployed into.') +output location string = app.location + +@description('Default hostname of the app.') +output defaultHostname string = app.properties.defaultHostName + +// =============== // +// Definitions // +// =============== // + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + userAssignedResourcesIds: string[]? +}? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device' | null)? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + @description('Required. Fqdn that resolves to private endpoint ip address.') + fqdn: string? + + @description('Required. A list of private ip addresses of the private endpoint.') + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + @description('Required. The name of the resource that is unique within a resource group.') + name: string + + @description('Required. Properties of private endpoint IP configurations.') + properties: { + @description('Required. The ID of a group obtained from the remote resource that this private endpoint should connect to.') + groupId: string + + @description('Required. The member name of a group obtained from the remote resource that this private endpoint should connect to.') + memberName: string + + @description('Required. A private ip address obtained from the private endpoint\'s subnet.') + privateIPAddress: string + } + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @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\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics' | null)? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: 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.') + eventHubAuthorizationRuleResourceId: 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. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]?