diff --git a/.github/workflows/avm.res.web.static-site.yml b/.github/workflows/avm.res.web.static-site.yml new file mode 100644 index 0000000000..f44dc3a4eb --- /dev/null +++ b/.github/workflows/avm.res.web.static-site.yml @@ -0,0 +1,83 @@ +name: "avm.res.web.static-site" + +on: + schedule: + - cron: "0 12 1/15 * *" # Bi-Weekly Test (on 1st & 15th of month) + workflow_dispatch: + inputs: + staticValidation: + type: boolean + description: "Execute static validation" + required: false + default: true + deploymentValidation: + type: boolean + description: "Execute deployment validation" + required: false + default: true + removeDeployment: + type: boolean + description: "Remove deployed module" + required: false + default: true + + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.web.static-site.yml" + - "avm/res/web/serverfarm/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/web/static-site" + workflowPath: ".github/workflows/avm.res.web.static-site.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get module test file paths" + id: get-module-test-file-paths + uses: ./.github/actions/templates/avm-getModuleTestFiles + with: + modulePath: "${{ env.modulePath }}" + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Run" + needs: + - job_initialize_pipeline + uses: ./.github/workflows/avm.template.module.yml + with: + workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}" + moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}" + psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/web/static-site/README.md b/avm/res/web/static-site/README.md new file mode 100644 index 0000000000..c9cdbe5738 --- /dev/null +++ b/avm/res/web/static-site/README.md @@ -0,0 +1,1034 @@ +# Static Web Apps `[Microsoft.Web/staticSites]` + +This module deploys a Static Web App. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Web/staticSites` | [2021-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2021-03-01/staticSites) | +| `Microsoft.Web/staticSites/config` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/staticSites/config) | +| `Microsoft.Web/staticSites/customDomains` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/staticSites/customDomains) | +| `Microsoft.Web/staticSites/linkedBackends` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/staticSites/linkedBackends) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/res/web/static-site:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [WAF-aligned](#example-3-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +
+ +via Bicep module + +```bicep +module staticSite 'br/public:avm/res/web/static-site:' = { + name: '${uniqueString(deployment().name, location)}-test-wssmin' + params: { + // Required parameters + name: 'wssmin001' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "wssmin001" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using large parameter set_ + +This instance deploys the module with most of its features enabled. + + +

+ +via Bicep module + +```bicep +module staticSite 'br/public:avm/res/web/static-site:' = { + name: '${uniqueString(deployment().name, location)}-test-wssmax' + params: { + // Required parameters + name: 'wssmax001' + // Non-required parameters + allowConfigFileUpdates: true + appSettings: { + foo: 'bar' + setting: 1 + } + enterpriseGradeCdnStatus: 'Disabled' + functionAppSettings: { + foo: 'bar' + setting: 1 + } + linkedBackend: { + resourceId: '' + } + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + ] + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + ] + sku: 'Standard' + stagingEnvironmentPolicy: 'Enabled' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "wssmax001" + }, + // Non-required parameters + "allowConfigFileUpdates": { + "value": true + }, + "appSettings": { + "value": { + "foo": "bar", + "setting": 1 + } + }, + "enterpriseGradeCdnStatus": { + "value": "Disabled" + }, + "functionAppSettings": { + "value": { + "foo": "bar", + "setting": 1 + } + }, + "linkedBackend": { + "value": { + "resourceId": "" + } + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + ] + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + } + ] + }, + "sku": { + "value": "Standard" + }, + "stagingEnvironmentPolicy": { + "value": "Enabled" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ +### Example 3: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module staticSite 'br/public:avm/res/web/static-site:' = { + name: '${uniqueString(deployment().name, location)}-test-wsswaf' + params: { + // Required parameters + name: 'wsswaf001' + // Non-required parameters + allowConfigFileUpdates: true + appSettings: { + foo: 'bar' + setting: 1 + } + enterpriseGradeCdnStatus: 'Disabled' + functionAppSettings: { + foo: 'bar' + setting: 1 + } + linkedBackend: { + resourceId: '' + } + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + ] + sku: 'Standard' + stagingEnvironmentPolicy: 'Enabled' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "wsswaf001" + }, + // Non-required parameters + "allowConfigFileUpdates": { + "value": true + }, + "appSettings": { + "value": { + "foo": "bar", + "setting": 1 + } + }, + "enterpriseGradeCdnStatus": { + "value": "Disabled" + }, + "functionAppSettings": { + "value": { + "foo": "bar", + "setting": 1 + } + }, + "linkedBackend": { + "value": { + "resourceId": "" + } + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + ] + }, + "sku": { + "value": "Standard" + }, + "stagingEnvironmentPolicy": { + "value": "Enabled" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Name of the static site. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowConfigFileUpdates`](#parameter-allowconfigfileupdates) | bool | False if config file is locked for this static web app; otherwise, true. | +| [`appSettings`](#parameter-appsettings) | object | Static site app settings. | +| [`branch`](#parameter-branch) | string | The branch name of the GitHub repository. | +| [`buildProperties`](#parameter-buildproperties) | object | Build properties for the static site. | +| [`customDomains`](#parameter-customdomains) | array | The custom domains associated with this static site. The deployment will fail as long as the validation records are not present. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable telemetry via a Globally Unique Identifier (GUID). | +| [`enterpriseGradeCdnStatus`](#parameter-enterprisegradecdnstatus) | string | State indicating the status of the enterprise grade CDN serving traffic to the static web app. | +| [`functionAppSettings`](#parameter-functionappsettings) | object | Function app settings. | +| [`linkedBackend`](#parameter-linkedbackend) | object | Object with "resourceId" and "location" of the a user defined function app. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`managedIdentities`](#parameter-managedidentities) | object | The managed identity definition for this resource. | +| [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'sku' to be 'Standard'. | +| [`provider`](#parameter-provider) | string | The provider that submitted the last deployment to the primary environment of the static site. | +| [`repositoryToken`](#parameter-repositorytoken) | securestring | The Personal Access Token for accessing the GitHub repository. | +| [`repositoryUrl`](#parameter-repositoryurl) | string | The name of the GitHub repository. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`sku`](#parameter-sku) | string | Type of static site to deploy. | +| [`stagingEnvironmentPolicy`](#parameter-stagingenvironmentpolicy) | string | State indicating whether staging environments are allowed or not allowed for a static web app. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`templateProperties`](#parameter-templateproperties) | object | Template Options for the static site. | + +### Parameter: `name` + +Name of the static site. + +- Required: Yes +- Type: string + +### Parameter: `allowConfigFileUpdates` + +False if config file is locked for this static web app; otherwise, true. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `appSettings` + +Static site app settings. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `branch` + +The branch name of the GitHub repository. + +- Required: No +- Type: string + +### Parameter: `buildProperties` + +Build properties for the static site. + +- Required: No +- Type: object + +### Parameter: `customDomains` + +The custom domains associated with this static site. The deployment will fail as long as the validation records are not present. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `enableTelemetry` + +Enable telemetry via a Globally Unique Identifier (GUID). + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enterpriseGradeCdnStatus` + +State indicating the status of the enterprise grade CDN serving traffic to the static web app. + +- Required: No +- Type: string +- Default: `'Disabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Disabling' + 'Enabled' + 'Enabling' + ] + ``` + +### Parameter: `functionAppSettings` + +Function app settings. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `linkedBackend` + +Object with "resourceId" and "location" of the a user defined function app. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `location` + +Location for all resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `lock` + +The lock settings of the service. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | +| [`name`](#parameter-lockname) | string | Specify the name of lock. | + +### Parameter: `lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `managedIdentities` + +The managed identity definition for this resource. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`systemAssigned`](#parameter-managedidentitiessystemassigned) | bool | Enables system assigned managed identity on the resource. | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. | + +### Parameter: `managedIdentities.systemAssigned` + +Enables system assigned managed identity on the resource. + +- Required: No +- Type: bool + +### Parameter: `managedIdentities.userAssignedResourceIds` + +The resource ID(s) to assign to the resource. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints` + +Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'sku' to be 'Standard'. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | string | Resource ID of the subnet where the endpoint needs to be created. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | array | Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | array | Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | string | The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | array | A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | string | The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | object | Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | array | Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | string | The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | string | The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | array | The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | array | Array of role assignments to create. | +| [`service`](#parameter-privateendpointsservice) | string | The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`tags`](#parameter-privateendpointstags) | object | Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.subnetResourceId` + +Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Custom DNS configurations. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.location` + +The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Specify the type of lock. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-privateendpointslockkind) | string | Specify the type of lock. | +| [`name`](#parameter-privateendpointslockname) | string | Specify the name of lock. | + +### Parameter: `privateEndpoints.lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `privateEndpoints.lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-privateendpointsroleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-privateendpointsroleassignmentsroledefinitionidorname) | string | The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-privateendpointsroleassignmentscondition) | string | 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". | +| [`conditionVersion`](#parameter-privateendpointsroleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-privateendpointsroleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-privateendpointsroleassignmentsdescription) | string | The description of the role assignment. | +| [`principalType`](#parameter-privateendpointsroleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `privateEndpoints.roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.roleAssignments.condition` + +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". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `privateEndpoints.roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `privateEndpoints.service` + +The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.tags` + +Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object + +### Parameter: `provider` + +The provider that submitted the last deployment to the primary environment of the static site. + +- Required: No +- Type: string +- Default: `'None'` + +### Parameter: `repositoryToken` + +The Personal Access Token for accessing the GitHub repository. + +- Required: No +- Type: securestring + +### Parameter: `repositoryUrl` + +The name of the GitHub repository. + +- Required: No +- Type: string + +### Parameter: `roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-roleassignmentscondition) | string | 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". | +| [`conditionVersion`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.condition` + +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". + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `sku` + +Type of static site to deploy. + +- Required: No +- Type: string +- Default: `'Free'` +- Allowed: + ```Bicep + [ + 'Free' + 'Standard' + ] + ``` + +### Parameter: `stagingEnvironmentPolicy` + +State indicating whether staging environments are allowed or not allowed for a static web app. + +- Required: No +- Type: string +- Default: `'Enabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + +### Parameter: `templateProperties` + +Template Options for the static site. + +- Required: No +- Type: object + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `defaultHostname` | string | The default autogenerated hostname for the static site. | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the static site. | +| `resourceGroupName` | string | The resource group the static site was deployed into. | +| `resourceId` | string | The resource ID of the static site. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other CARML modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/network/private-endpoint:0.3.1` | Remote reference | diff --git a/avm/res/web/static-site/config/README.md b/avm/res/web/static-site/config/README.md new file mode 100644 index 0000000000..cd8319d99d --- /dev/null +++ b/avm/res/web/static-site/config/README.md @@ -0,0 +1,72 @@ +# Static Web App Site Config `[Microsoft.Web/staticSites/config]` + +This module deploys a Static Web App Site Config. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/staticSites/config` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/staticSites/config) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-kind) | string | Type of settings to apply. | +| [`properties`](#parameter-properties) | object | App settings. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`staticSiteName`](#parameter-staticsitename) | string | The name of the parent Static Web App. Required if the template is used in a standalone deployment. | + +### Parameter: `kind` + +Type of settings to apply. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'appsettings' + 'functionappsettings' + ] + ``` + +### Parameter: `properties` + +App settings. + +- Required: Yes +- Type: object + +### Parameter: `staticSiteName` + +The name of the parent Static Web App. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the config. | +| `resourceGroupName` | string | The name of the resource group the config was created in. | +| `resourceId` | string | The resource ID of the config. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/web/static-site/config/main.bicep b/avm/res/web/static-site/config/main.bicep new file mode 100644 index 0000000000..d2b1a3267c --- /dev/null +++ b/avm/res/web/static-site/config/main.bicep @@ -0,0 +1,36 @@ +metadata name = 'Static Web App Site Config' +metadata description = 'This module deploys a Static Web App Site Config.' +metadata owner = 'Azure/module-maintainers' + +@allowed([ + 'appsettings' + 'functionappsettings' +]) +@description('Required. Type of settings to apply.') +param kind string + +@description('Required. App settings.') +param properties object + +@description('Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment.') +param staticSiteName string + +resource staticSite 'Microsoft.Web/staticSites@2022-03-01' existing = { + name: staticSiteName +} + +resource config 'Microsoft.Web/staticSites/config@2022-03-01' = { + #disable-next-line BCP225 // Disables incorrect error that `name` cannot be determined at compile time. + name: kind + parent: staticSite + properties: properties +} + +@description('The name of the config.') +output name string = config.name + +@description('The resource ID of the config.') +output resourceId string = config.id + +@description('The name of the resource group the config was created in.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/web/static-site/config/main.json b/avm/res/web/static-site/config/main.json new file mode 100644 index 0000000000..94cece2fe0 --- /dev/null +++ b/avm/res/web/static-site/config/main.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "12360558347482884602" + }, + "name": "Static Web App Site Config", + "description": "This module deploys a Static Web App Site Config.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "kind": { + "type": "string", + "allowedValues": [ + "appsettings", + "functionappsettings" + ], + "metadata": { + "description": "Required. Type of settings to apply." + } + }, + "properties": { + "type": "object", + "metadata": { + "description": "Required. App settings." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('kind'))]", + "properties": "[parameters('properties')]" + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the config." + }, + "value": "[parameters('kind')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the config." + }, + "value": "[resourceId('Microsoft.Web/staticSites/config', parameters('staticSiteName'), parameters('kind'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the config was created in." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/web/static-site/custom-domain/README.md b/avm/res/web/static-site/custom-domain/README.md new file mode 100644 index 0000000000..1c5893aac6 --- /dev/null +++ b/avm/res/web/static-site/custom-domain/README.md @@ -0,0 +1,71 @@ +# Static Web App Site Custom Domains `[Microsoft.Web/staticSites/customDomains]` + +This module deploys a Static Web App Site Custom Domain. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/staticSites/customDomains` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/staticSites/customDomains) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The custom domain name. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`staticSiteName`](#parameter-staticsitename) | string | The name of the parent Static Web App. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`validationMethod`](#parameter-validationmethod) | string | Validation method for adding a custom domain. | + +### Parameter: `name` + +The custom domain name. + +- Required: Yes +- Type: string + +### Parameter: `staticSiteName` + +The name of the parent Static Web App. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `validationMethod` + +Validation method for adding a custom domain. + +- Required: No +- Type: string +- Default: `'cname-delegation'` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the static site custom domain. | +| `resourceGroupName` | string | The resource group the static site custom domain was deployed into. | +| `resourceId` | string | The resource ID of the static site custom domain. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/web/static-site/custom-domain/main.bicep b/avm/res/web/static-site/custom-domain/main.bicep new file mode 100644 index 0000000000..fb7c51def1 --- /dev/null +++ b/avm/res/web/static-site/custom-domain/main.bicep @@ -0,0 +1,33 @@ +metadata name = 'Static Web App Site Custom Domains' +metadata description = 'This module deploys a Static Web App Site Custom Domain.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The custom domain name.') +param name string + +@description('Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment.') +param staticSiteName string + +@description('Optional. Validation method for adding a custom domain.') +param validationMethod string = 'cname-delegation' + +resource staticSite 'Microsoft.Web/staticSites@2022-03-01' existing = { + name: staticSiteName +} + +resource customDomain 'Microsoft.Web/staticSites/customDomains@2022-03-01' = { + name: name + parent: staticSite + properties: { + validationMethod: validationMethod + } +} + +@description('The name of the static site custom domain.') +output name string = customDomain.name + +@description('The resource ID of the static site custom domain.') +output resourceId string = customDomain.id + +@description('The resource group the static site custom domain was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/web/static-site/custom-domain/main.json b/avm/res/web/static-site/custom-domain/main.json new file mode 100644 index 0000000000..29a6ad82d3 --- /dev/null +++ b/avm/res/web/static-site/custom-domain/main.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "15929667345294936427" + }, + "name": "Static Web App Site Custom Domains", + "description": "This module deploys a Static Web App Site Custom Domain.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The custom domain name." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + }, + "validationMethod": { + "type": "string", + "defaultValue": "cname-delegation", + "metadata": { + "description": "Optional. Validation method for adding a custom domain." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/customDomains", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('name'))]", + "properties": { + "validationMethod": "[parameters('validationMethod')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the static site custom domain." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the static site custom domain." + }, + "value": "[resourceId('Microsoft.Web/staticSites/customDomains', parameters('staticSiteName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the static site custom domain was deployed into." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/web/static-site/linked-backend/README.md b/avm/res/web/static-site/linked-backend/README.md new file mode 100644 index 0000000000..468acc399b --- /dev/null +++ b/avm/res/web/static-site/linked-backend/README.md @@ -0,0 +1,80 @@ +# Static Web App Site Linked Backends `[Microsoft.Web/staticSites/linkedBackends]` + +This module deploys a Custom Function App into a Static Web App Site using the Linked Backends property. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/staticSites/linkedBackends` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/staticSites/linkedBackends) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`backendResourceId`](#parameter-backendresourceid) | string | The resource ID of the backend linked to the static site. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`staticSiteName`](#parameter-staticsitename) | string | The name of the parent Static Web App. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Name of the backend to link to the static site. | +| [`region`](#parameter-region) | string | The region of the backend linked to the static site. | + +### Parameter: `backendResourceId` + +The resource ID of the backend linked to the static site. + +- Required: Yes +- Type: string + +### Parameter: `staticSiteName` + +The name of the parent Static Web App. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `name` + +Name of the backend to link to the static site. + +- Required: No +- Type: string +- Default: `[uniqueString(parameters('backendResourceId'))]` + +### Parameter: `region` + +The region of the backend linked to the static site. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the static site linked backend. | +| `resourceGroupName` | string | The resource group the static site linked backend was deployed into. | +| `resourceId` | string | The resource ID of the static site linked backend. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/web/static-site/linked-backend/main.bicep b/avm/res/web/static-site/linked-backend/main.bicep new file mode 100644 index 0000000000..3fc6a38be5 --- /dev/null +++ b/avm/res/web/static-site/linked-backend/main.bicep @@ -0,0 +1,37 @@ +metadata name = 'Static Web App Site Linked Backends' +metadata description = 'This module deploys a Custom Function App into a Static Web App Site using the Linked Backends property.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The resource ID of the backend linked to the static site.') +param backendResourceId string + +@description('Optional. The region of the backend linked to the static site.') +param region string = resourceGroup().location + +@description('Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment.') +param staticSiteName string + +@description('Optional. Name of the backend to link to the static site.') +param name string = uniqueString(backendResourceId) + +resource staticSite 'Microsoft.Web/staticSites@2022-03-01' existing = { + name: staticSiteName +} + +resource linkedBackend 'Microsoft.Web/staticSites/linkedBackends@2022-03-01' = { + name: name + parent: staticSite + properties: { + backendResourceId: backendResourceId + region: region + } +} + +@description('The name of the static site linked backend.') +output name string = linkedBackend.name + +@description('The resource ID of the static site linked backend.') +output resourceId string = linkedBackend.id + +@description('The resource group the static site linked backend was deployed into.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/web/static-site/linked-backend/main.json b/avm/res/web/static-site/linked-backend/main.json new file mode 100644 index 0000000000..73c8f32387 --- /dev/null +++ b/avm/res/web/static-site/linked-backend/main.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "7695569913720977108" + }, + "name": "Static Web App Site Linked Backends", + "description": "This module deploys a Custom Function App into a Static Web App Site using the Linked Backends property.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "backendResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the backend linked to the static site." + } + }, + "region": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The region of the backend linked to the static site." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[uniqueString(parameters('backendResourceId'))]", + "metadata": { + "description": "Optional. Name of the backend to link to the static site." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/linkedBackends", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('name'))]", + "properties": { + "backendResourceId": "[parameters('backendResourceId')]", + "region": "[parameters('region')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the static site linked backend." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the static site linked backend." + }, + "value": "[resourceId('Microsoft.Web/staticSites/linkedBackends', parameters('staticSiteName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the static site linked backend was deployed into." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/web/static-site/main.bicep b/avm/res/web/static-site/main.bicep new file mode 100644 index 0000000000..86d0308e35 --- /dev/null +++ b/avm/res/web/static-site/main.bicep @@ -0,0 +1,362 @@ +metadata name = 'Static Web Apps' +metadata description = 'This module deploys a Static Web App.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the static site.') +@minLength(1) +@maxLength(40) +param name string + +@allowed([ + 'Free' + 'Standard' +]) +@description('Optional. Type of static site to deploy.') +param sku string = 'Free' + +@description('Optional. False if config file is locked for this static web app; otherwise, true.') +param allowConfigFileUpdates bool = true + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@allowed([ + 'Enabled' + 'Disabled' +]) +@description('Optional. State indicating whether staging environments are allowed or not allowed for a static web app.') +param stagingEnvironmentPolicy string = 'Enabled' + +@allowed([ + 'Disabled' + 'Disabling' + 'Enabled' + 'Enabling' +]) +@description('Optional. State indicating the status of the enterprise grade CDN serving traffic to the static web app.') +param enterpriseGradeCdnStatus string = 'Disabled' + +@description('Optional. Build properties for the static site.') +param buildProperties object? + +@description('Optional. Template Options for the static site.') +param templateProperties object? + +@description('Optional. The provider that submitted the last deployment to the primary environment of the static site.') +param provider string = 'None' + +@secure() +@description('Optional. The Personal Access Token for accessing the GitHub repository.') +param repositoryToken string? + +@description('Optional. The name of the GitHub repository.') +param repositoryUrl string? + +@description('Optional. The branch name of the GitHub repository.') +param branch string? + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@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. Note, requires the \'sku\' to be \'Standard\'.') +param privateEndpoints privateEndpointType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Object with "resourceId" and "location" of the a user defined function app.') +param linkedBackend object = {} + +@description('Optional. Static site app settings.') +param appSettings object = {} + +@description('Optional. Function app settings.') +param functionAppSettings object = {} + +@description('Optional. The custom domains associated with this static site. The deployment will fail as long as the validation records are not present.') +param customDomains array = [] + +var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (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.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null +} : null + +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 avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.web-staticSite.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } + } + } + } +} + +resource staticSite 'Microsoft.Web/staticSites@2021-03-01' = { + name: name + location: location + tags: tags + identity: identity + sku: { + name: sku + tier: sku + } + properties: { + allowConfigFileUpdates: allowConfigFileUpdates + stagingEnvironmentPolicy: stagingEnvironmentPolicy + enterpriseGradeCdnStatus: enterpriseGradeCdnStatus + provider: !empty(provider) ? provider : 'None' + branch: branch + buildProperties: buildProperties + repositoryToken: repositoryToken + repositoryUrl: repositoryUrl + templateProperties: templateProperties + } +} + +module staticSite_linkedBackend 'linked-backend/main.bicep' = if (!empty(linkedBackend)) { + name: '${uniqueString(deployment().name, location)}-StaticSite-UserDefinedFunction' + params: { + staticSiteName: staticSite.name + backendResourceId: linkedBackend.resourceId + region: contains(linkedBackend, 'location') ? linkedBackend.location : location + } +} + +module staticSite_appSettings 'config/main.bicep' = if (!empty(appSettings)) { + name: '${uniqueString(deployment().name, location)}-StaticSite-appSettings' + params: { + kind: 'appsettings' + staticSiteName: staticSite.name + properties: appSettings + } +} + +module staticSite_functionAppSettings 'config/main.bicep' = if (!empty(functionAppSettings)) { + name: '${uniqueString(deployment().name, location)}-StaticSite-functionAppSettings' + params: { + kind: 'functionappsettings' + staticSiteName: staticSite.name + properties: functionAppSettings + } +} + +module staticSite_customDomains 'custom-domain/main.bicep' = [for (customDomain, index) in customDomains: { + name: '${uniqueString(deployment().name, location)}-StaticSite-customDomains-${index}' + params: { + name: customDomain + staticSiteName: staticSite.name + validationMethod: indexOf(customDomain, '.') == lastIndexOf(customDomain, '.') ? 'dns-txt-token' : 'cname-delegation' + } +}] + +resource staticSite_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: staticSite +} + +resource staticSite_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(staticSite.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 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: staticSite +}] + +module staticSite_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.3.1' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-staticSite-PrivateEndpoint-${index}' + params: { + privateLinkServiceConnections: [ + { + name: name + properties: { + privateLinkServiceId: staticSite.id + groupIds: [ + privateEndpoint.?service ?? 'staticSites' + ] + } + } + ] + name: privateEndpoint.?name ?? 'pep-${last(split(staticSite.id, '/'))}-${privateEndpoint.?service ?? 'staticSites'}-${index}' + subnetResourceId: privateEndpoint.subnetResourceId + enableTelemetry: enableTelemetry + 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 static site.') +output name string = staticSite.name + +@description('The resource ID of the static site.') +output resourceId string = staticSite.id + +@description('The resource group the static 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(staticSite.identity, 'principalId') ? staticSite.identity.principalId : '' + +@description('The location the resource was deployed into.') +output location string = staticSite.location + +@description('The default autogenerated hostname for the static site.') +output defaultHostname string = staticSite.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.') + userAssignedResourceIds: 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 role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + 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')? + + @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 assignments to create.') + 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? +}[]? diff --git a/avm/res/web/static-site/main.json b/avm/res/web/static-site/main.json new file mode 100644 index 0000000000..414d493f8f --- /dev/null +++ b/avm/res/web/static-site/main.json @@ -0,0 +1,1664 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "1837229798638976594" + }, + "name": "Static Web Apps", + "description": "This module deploys a Static Web App.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "metadata": { + "description": "Required. Name of the static site." + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Standard" + ], + "metadata": { + "description": "Optional. Type of static site to deploy." + } + }, + "allowConfigFileUpdates": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. False if config file is locked for this static web app; otherwise, true." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "stagingEnvironmentPolicy": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. State indicating whether staging environments are allowed or not allowed for a static web app." + } + }, + "enterpriseGradeCdnStatus": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Disabling", + "Enabled", + "Enabling" + ], + "metadata": { + "description": "Optional. State indicating the status of the enterprise grade CDN serving traffic to the static web app." + } + }, + "buildProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Build properties for the static site." + } + }, + "templateProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Template Options for the static site." + } + }, + "provider": { + "type": "string", + "defaultValue": "None", + "metadata": { + "description": "Optional. The provider that submitted the last deployment to the primary environment of the static site." + } + }, + "repositoryToken": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The Personal Access Token for accessing the GitHub repository." + } + }, + "repositoryUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the GitHub repository." + } + }, + "branch": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The branch name of the GitHub repository." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. Note, requires the 'sku' to be 'Standard'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "linkedBackend": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Object with \"resourceId\" and \"location\" of the a user defined function app." + } + }, + "appSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Static site app settings." + } + }, + "functionAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Function app settings." + } + }, + "customDomains": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The custom domains associated with this static site. The deployment will fail as long as the validation records are not present." + } + } + }, + "variables": { + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.web-staticSite.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "staticSite": { + "type": "Microsoft.Web/staticSites", + "apiVersion": "2021-03-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "sku": { + "name": "[parameters('sku')]", + "tier": "[parameters('sku')]" + }, + "properties": { + "allowConfigFileUpdates": "[parameters('allowConfigFileUpdates')]", + "stagingEnvironmentPolicy": "[parameters('stagingEnvironmentPolicy')]", + "enterpriseGradeCdnStatus": "[parameters('enterpriseGradeCdnStatus')]", + "provider": "[if(not(empty(parameters('provider'))), parameters('provider'), 'None')]", + "branch": "[parameters('branch')]", + "buildProperties": "[parameters('buildProperties')]", + "repositoryToken": "[parameters('repositoryToken')]", + "repositoryUrl": "[parameters('repositoryUrl')]", + "templateProperties": "[parameters('templateProperties')]" + } + }, + "staticSite_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Web/staticSites/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_roleAssignments": { + "copy": { + "name": "staticSite_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Web/staticSites/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Web/staticSites', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_linkedBackend": { + "condition": "[not(empty(parameters('linkedBackend')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StaticSite-UserDefinedFunction', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "staticSiteName": { + "value": "[parameters('name')]" + }, + "backendResourceId": { + "value": "[parameters('linkedBackend').resourceId]" + }, + "region": "[if(contains(parameters('linkedBackend'), 'location'), createObject('value', parameters('linkedBackend').location), createObject('value', parameters('location')))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "7695569913720977108" + }, + "name": "Static Web App Site Linked Backends", + "description": "This module deploys a Custom Function App into a Static Web App Site using the Linked Backends property.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "backendResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the backend linked to the static site." + } + }, + "region": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The region of the backend linked to the static site." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[uniqueString(parameters('backendResourceId'))]", + "metadata": { + "description": "Optional. Name of the backend to link to the static site." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/linkedBackends", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('name'))]", + "properties": { + "backendResourceId": "[parameters('backendResourceId')]", + "region": "[parameters('region')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the static site linked backend." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the static site linked backend." + }, + "value": "[resourceId('Microsoft.Web/staticSites/linkedBackends', parameters('staticSiteName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the static site linked backend was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_appSettings": { + "condition": "[not(empty(parameters('appSettings')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StaticSite-appSettings', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "kind": { + "value": "appsettings" + }, + "staticSiteName": { + "value": "[parameters('name')]" + }, + "properties": { + "value": "[parameters('appSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "12360558347482884602" + }, + "name": "Static Web App Site Config", + "description": "This module deploys a Static Web App Site Config.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "kind": { + "type": "string", + "allowedValues": [ + "appsettings", + "functionappsettings" + ], + "metadata": { + "description": "Required. Type of settings to apply." + } + }, + "properties": { + "type": "object", + "metadata": { + "description": "Required. App settings." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('kind'))]", + "properties": "[parameters('properties')]" + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the config." + }, + "value": "[parameters('kind')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the config." + }, + "value": "[resourceId('Microsoft.Web/staticSites/config', parameters('staticSiteName'), parameters('kind'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the config was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_functionAppSettings": { + "condition": "[not(empty(parameters('functionAppSettings')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StaticSite-functionAppSettings', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "kind": { + "value": "functionappsettings" + }, + "staticSiteName": { + "value": "[parameters('name')]" + }, + "properties": { + "value": "[parameters('functionAppSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "12360558347482884602" + }, + "name": "Static Web App Site Config", + "description": "This module deploys a Static Web App Site Config.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "kind": { + "type": "string", + "allowedValues": [ + "appsettings", + "functionappsettings" + ], + "metadata": { + "description": "Required. Type of settings to apply." + } + }, + "properties": { + "type": "object", + "metadata": { + "description": "Required. App settings." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('kind'))]", + "properties": "[parameters('properties')]" + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the config." + }, + "value": "[parameters('kind')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the config." + }, + "value": "[resourceId('Microsoft.Web/staticSites/config', parameters('staticSiteName'), parameters('kind'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the config was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_customDomains": { + "copy": { + "name": "staticSite_customDomains", + "count": "[length(parameters('customDomains'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StaticSite-customDomains-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('customDomains')[copyIndex()]]" + }, + "staticSiteName": { + "value": "[parameters('name')]" + }, + "validationMethod": "[if(equals(indexOf(parameters('customDomains')[copyIndex()], '.'), lastIndexOf(parameters('customDomains')[copyIndex()], '.')), createObject('value', 'dns-txt-token'), createObject('value', 'cname-delegation'))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "15929667345294936427" + }, + "name": "Static Web App Site Custom Domains", + "description": "This module deploys a Static Web App Site Custom Domain.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The custom domain name." + } + }, + "staticSiteName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Static Web App. Required if the template is used in a standalone deployment." + } + }, + "validationMethod": { + "type": "string", + "defaultValue": "cname-delegation", + "metadata": { + "description": "Optional. Validation method for adding a custom domain." + } + } + }, + "resources": [ + { + "type": "Microsoft.Web/staticSites/customDomains", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', parameters('staticSiteName'), parameters('name'))]", + "properties": { + "validationMethod": "[parameters('validationMethod')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the static site custom domain." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the static site custom domain." + }, + "value": "[resourceId('Microsoft.Web/staticSites/customDomains', parameters('staticSiteName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the static site custom domain was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "staticSite" + ] + }, + "staticSite_privateEndpoints": { + "copy": { + "name": "staticSite_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-staticSite-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateLinkServiceConnections": { + "value": [ + { + "name": "[parameters('name')]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Web/staticSites', parameters('name'))]", + "groupIds": [ + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'staticSites')]" + ] + } + } + ] + }, + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/staticSites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'staticSites'), copyIndex()))]" + }, + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "2821141217598568122" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "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\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18168683629401652671" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "staticSite" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the static site." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the static site." + }, + "value": "[resourceId('Microsoft.Web/staticSites', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the static site was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[if(and(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), contains(reference('staticSite', '2021-03-01', 'full').identity, 'principalId')), reference('staticSite', '2021-03-01', 'full').identity.principalId, '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('staticSite', '2021-03-01', 'full').location]" + }, + "defaultHostname": { + "type": "string", + "metadata": { + "description": "The default autogenerated hostname for the static site." + }, + "value": "[reference('staticSite').defaultHostname]" + } + } +} \ No newline at end of file diff --git a/avm/res/web/static-site/tests/e2e/defaults/main.test.bicep b/avm/res/web/static-site/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..91f09b1142 --- /dev/null +++ b/avm/res/web/static-site/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,46 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-web.staticsites-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'wssmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + } +}] diff --git a/avm/res/web/static-site/tests/e2e/max/dependencies.bicep b/avm/res/web/static-site/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..7939cfd2d2 --- /dev/null +++ b/avm/res/web/static-site/tests/e2e/max/dependencies.bicep @@ -0,0 +1,94 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Function App to create.') +param siteName string + +@description('Required. The name of the Server Farm to create.') +param serverFarmName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.azurestaticapps.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource serverFarm 'Microsoft.Web/serverfarms@2022-03-01' = { + name: serverFarmName + location: location + sku: { + name: 'S1' + tier: 'Standard' + size: 'S1' + family: 'S' + capacity: 1 + } + properties: {} +} + +resource functionApp 'Microsoft.Web/sites@2022-03-01' = { + name: siteName + location: location + kind: 'functionapp' + properties: { + serverFarmId: serverFarm.id + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created Private DNS zone.') +output privateDNSZoneResourceId string = privateDNSZone.id + +@description('The resource ID of the created Function App.') +output siteResourceId string = functionApp.id diff --git a/avm/res/web/static-site/tests/e2e/max/main.test.bicep b/avm/res/web/static-site/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..6cfea6e8e3 --- /dev/null +++ b/avm/res/web/static-site/tests/e2e/max/main.test.bicep @@ -0,0 +1,118 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-web.staticsites-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'wssmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + siteName: 'dep-${namePrefix}-fa-${serviceShort}' + serverFarmName: 'dep-${namePrefix}-sf-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + allowConfigFileUpdates: true + enterpriseGradeCdnStatus: 'Disabled' + location: location + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + privateEndpoints: [ + { + subnetResourceId: nestedDependencies.outputs.subnetResourceId + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + roleAssignments: [ + { + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + sku: 'Standard' + stagingEnvironmentPolicy: 'Enabled' + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + appSettings: { + foo: 'bar' + setting: 1 + } + functionAppSettings: { + foo: 'bar' + setting: 1 + } + linkedBackend: { + resourceId: nestedDependencies.outputs.siteResourceId + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +}] diff --git a/avm/res/web/static-site/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/web/static-site/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..8a6f1f158a --- /dev/null +++ b/avm/res/web/static-site/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,80 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Function App to create.') +param siteName string + +@description('Required. The name of the Server Farm to create.') +param serverFarmName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.azurestaticapps.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource serverFarm 'Microsoft.Web/serverfarms@2022-03-01' = { + name: serverFarmName + location: location + sku: { + name: 'S1' + tier: 'Standard' + size: 'S1' + family: 'S' + capacity: 1 + } + properties: {} +} + +resource functionApp 'Microsoft.Web/sites@2022-03-01' = { + name: siteName + location: location + kind: 'functionapp' + properties: { + serverFarmId: serverFarm.id + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Private DNS zone.') +output privateDNSZoneResourceId string = privateDNSZone.id + +@description('The resource ID of the created Function App.') +output siteResourceId string = functionApp.id diff --git a/avm/res/web/static-site/tests/e2e/waf-aligned/main.test.bicep b/avm/res/web/static-site/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..6abca487cc --- /dev/null +++ b/avm/res/web/static-site/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,94 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-web.staticsites-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'wsswaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + siteName: 'dep-${namePrefix}-fa-${serviceShort}' + serverFarmName: 'dep-${namePrefix}-sf-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + allowConfigFileUpdates: true + enterpriseGradeCdnStatus: 'Disabled' + location: location + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + privateEndpoints: [ + { + subnetResourceId: nestedDependencies.outputs.subnetResourceId + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + sku: 'Standard' + stagingEnvironmentPolicy: 'Enabled' + appSettings: { + foo: 'bar' + setting: 1 + } + functionAppSettings: { + foo: 'bar' + setting: 1 + } + linkedBackend: { + resourceId: nestedDependencies.outputs.siteResourceId + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +}] diff --git a/avm/res/web/static-site/version.json b/avm/res/web/static-site/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/res/web/static-site/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +}