From 18604b4b6d03de203d23b7c1df834fbeae585c15 Mon Sep 17 00:00:00 2001 From: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:13:14 +0200 Subject: [PATCH 01/32] feat: if module list is not in sync with CSV file an issue is created (#1401) This code includes a pipeline, that runs daily and checks, if the module list drop down (for module issues) is up-to-date. If not, an issue is created. --- .../avm.platform.sync-avm-modules-list.yml | 36 +++++++++ .../platform/Sync-AvmModulesList.ps1 | 78 +++++++++++++++++++ .../platform/helper/Get-AvmCsvData.ps1 | 1 + 3 files changed, 115 insertions(+) create mode 100644 .github/workflows/avm.platform.sync-avm-modules-list.yml create mode 100644 avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 diff --git a/.github/workflows/avm.platform.sync-avm-modules-list.yml b/.github/workflows/avm.platform.sync-avm-modules-list.yml new file mode 100644 index 0000000000..7557309692 --- /dev/null +++ b/.github/workflows/avm.platform.sync-avm-modules-list.yml @@ -0,0 +1,36 @@ +name: .Platform - Sync AVM module list + +on: + schedule: + - cron: "30 4 * * *" # Every day at 4:30 am + workflow_dispatch: + +jobs: + sync-avm-modules-list: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: tibdex/github-app-token@v2 + id: generate-token + with: + app_id: ${{ secrets.TEAM_LINTER_APP_ID }} + private_key: ${{ secrets.TEAM_LINTER_PRIVATE_KEY }} + - name: sync avm modules list + shell: pwsh + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + run: | + # Load used functions + . (Join-Path $env:GITHUB_WORKSPACE 'avm' 'utilities' 'pipelines' 'platform' 'Sync-AvmModulesList.ps1') + + $functionInput = @{ + Repo = "${{ github.repository_owner }}/${{ github.event.repository.name }}" + RepoRoot = $env:GITHUB_WORKSPACE + } + + Sync-AvmModulesList @functionInput -Verbose diff --git a/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 new file mode 100644 index 0000000000..7cdfa21e8d --- /dev/null +++ b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 @@ -0,0 +1,78 @@ +<# +.SYNOPSIS +If module list is not in sync with CSV file, an issue is created + +.DESCRIPTION +CSV data for moules and pattern is loaded and compared with the list in the issue template. If they are not in sync, an issue with the necessary changes is created + +.PARAMETER Repo +Mandatory. The name of the respository to scan. Needs to have the structure "/", like 'Azure/bicep-registry-modules/' + +.PARAMETER RepoRoot +Optional. Path to the root of the repository. + +.EXAMPLE +Sync-AvmModulesList -Repo 'Azure/bicep-registry-modules' + +.NOTES +Will be triggered by the workflow avm.platform.sync-avm-modules-list.yml +#> +function Sync-AvmModulesList { + param ( + [Parameter(Mandatory = $true)] + [string] $Repo, + + [Parameter(Mandatory = $false)] + [string] $RepoRoot = (Get-Item -Path $PSScriptRoot).parent.parent.parent.parent.FullName + ) + + # Loading helper functions + . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Get-AvmCsvData.ps1') + . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Add-GithubIssueToProject.ps1') + + $workflowFilePath = Join-Path $RepoRoot '.github' 'ISSUE_TEMPLATE' 'avm_module_issue.yml' + + # get CSV data + $modules = Get-AvmCsvData -ModuleIndex 'Bicep-Resource' | Select-Object -Property 'ModuleName' + $patterns = Get-AvmCsvData -ModuleIndex 'Bicep-Pattern' | Select-Object -Property 'ModuleName' + + # build new strings + $prefix = ' - "' + $postfix = '"' + $newModuleLines = $modules | ForEach-Object { $prefix + $_.ModuleName + $postfix } + $newPatternLines = $patterns | ForEach-Object { $prefix + $_.ModuleName + $postfix } + + # parse workflow file + $workflowFileLines = Get-Content $workflowFilePath + $startIndex = 0 + $endIndex = 0 + + for ($lineNumber = 0; $lineNumber -lt $workflowFileLines.Count; $lineNumber++) { + if ($startIndex -gt 0 -and (-not ($workflowFileLines[$lineNumber]).Trim().StartsWith('- "avm/'))) { + $endIndex = $lineNumber + break + } + + if (($workflowFileLines[$lineNumber]).Trim() -eq '- "Other, as defined below..."') { + $startIndex = $lineNumber + } + } + + $oldLines = $workflowFileLines[($startIndex + 1)..($endIndex - 1)] + $newLines = $newModuleLines + $newPatternLines + $body = $newLines -join ([Environment]::NewLine) + + if ($oldLines -ne $newLines) { + $title = '[AVM core] Module(s) missing from AVM Module Issue template' + $label = 'Type: AVM :a: :v: :m:,Type: Hygiene :broom:,Needs: Triage :mag:' + $issues = gh issue list --state open --limit 500 --label $label --json 'title' --repo $Repo | ConvertFrom-Json -Depth 100 + + if ($issues.title -notcontains $title) { + # create issue + $issueUrl = gh issue create --title $title --body $body --label $label --repo $Repo + # add issue to project + $ProjectNumber = 538 # AVM - Issue Triage + Add-GithubIssueToProject -Repo $Repo -ProjectNumber $ProjectNumber -IssueUrl $issueUrl + } + } +} diff --git a/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 b/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 index 821f476bb7..17f6e077b9 100644 --- a/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 +++ b/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 @@ -30,6 +30,7 @@ Function Get-AvmCsvData { 'Bicep-Resource' { try { $unfilteredCSV = Invoke-WebRequest -Uri $BicepResourceUrl + } catch { throw 'Unable to retrieve CSV file - Check network connection.' } From 33db14045f75747b208f626e1735ba3adf7689f1 Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:13:28 +0200 Subject: [PATCH 02/32] feat: New Module `avm/res/load-test-service/load-test` (#1613) ## Description New module : Load-test-service.load-test ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.load-test-service.load-test](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml/badge.svg?branch=avm-load-test-service)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Alexander Sehr --- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 2 +- .../avm.res.load-test-service.load-test.yml | 86 +++ avm/res/load-test-service/load-test/README.md | 597 ++++++++++++++++++ .../load-test-service/load-test/main.bicep | 242 +++++++ avm/res/load-test-service/load-test/main.json | 378 +++++++++++ .../tests/e2e/defaults/main.test.bicep | 45 ++ .../tests/e2e/max/dependencies.bicep | 16 + .../load-test/tests/e2e/max/main.test.bicep | 73 +++ .../dependencies.bicep | 64 ++ .../main.test.bicep | 69 ++ .../tests/e2e/waf-aligned/main.test.bicep | 55 ++ .../load-test-service/load-test/version.json | 7 + 13 files changed, 1634 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/avm.res.load-test-service.load-test.yml create mode 100644 avm/res/load-test-service/load-test/README.md create mode 100644 avm/res/load-test-service/load-test/main.bicep create mode 100644 avm/res/load-test-service/load-test/main.json create mode 100644 avm/res/load-test-service/load-test/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/load-test-service/load-test/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/load-test-service/load-test/tests/e2e/max/main.test.bicep create mode 100644 avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/dependencies.bicep create mode 100644 avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/main.test.bicep create mode 100644 avm/res/load-test-service/load-test/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/load-test-service/load-test/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dd7d4654fc..bf9aa06b23 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -68,7 +68,7 @@ /avm/res/key-vault/vault/ @Azure/avm-res-keyvault-vault-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/kubernetes-configuration/extension/ @Azure/avm-res-kubernetesconfiguration-extension-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/kubernetes-configuration/flux-configuration/ @Azure/avm-res-kubernetesconfiguration-fluxconfiguration-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/load-test-service/load-test/ @Azure/avm-res-loadtestservice-loadtest-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/load-test-service/load-test/ @Azure/avm-res-loadtestservice-loadtest-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/logic/workflow/ @Azure/avm-res-logic-workflow-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/machine-learning-services/workspace/ @Azure/avm-res-machinelearningservices-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/maintenance/maintenance-configuration/ @Azure/avm-res-maintenance-maintenanceconfiguration-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 9fea2a0a2e..77d58e66d1 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -101,7 +101,7 @@ body: - "avm/res/key-vault/vault" - "avm/res/kubernetes-configuration/extension" - "avm/res/kubernetes-configuration/flux-configuration" - # - "avm/res/load-test-service/load-test" + - "avm/res/load-test-service/load-test" - "avm/res/logic/workflow" - "avm/res/machine-learning-services/workspace" - "avm/res/maintenance/maintenance-configuration" diff --git a/.github/workflows/avm.res.load-test-service.load-test.yml b/.github/workflows/avm.res.load-test-service.load-test.yml new file mode 100644 index 0000000000..255a2abdca --- /dev/null +++ b/.github/workflows/avm.res.load-test-service.load-test.yml @@ -0,0 +1,86 @@ +name: "avm.res.load-test-service.load-test" + +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.load-test-service.load-test.yml" + - "avm/res/load-test-service/load-test/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/load-test-service/load-test" + workflowPath: ".github/workflows/avm.res.load-test-service.load-test.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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/load-test-service/load-test/README.md b/avm/res/load-test-service/load-test/README.md new file mode 100644 index 0000000000..54ee6da52b --- /dev/null +++ b/avm/res/load-test-service/load-test/README.md @@ -0,0 +1,597 @@ +# Load Testing Service `[Microsoft.LoadTestService/loadTests]` + +This module deploys a Load test. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## 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.LoadTestService/loadTests` | [2022-12-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.LoadTestService/2022-12-01/loadTests) | + +## 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/load-test-service/load-test:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [Using Customer-Managed-Keys with User-Assigned identity](#example-3-using-customer-managed-keys-with-user-assigned-identity) +- [WAF-aligned](#example-4-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +
+ +via Bicep module + +```bicep +module loadTest 'br/public:avm/res/load-test-service/load-test:' = { + name: 'loadTestDeployment' + params: { + // Required parameters + name: 'ltmin001' + // 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": "ltmin001" + }, + // 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 loadTest 'br/public:avm/res/load-test-service/load-test:' = { + name: 'loadTestDeployment' + params: { + // Required parameters + name: 'ltmax001' + // Non-required parameters + loadTestDescription: 'This is a test load test to validate the module.' + location: '' + lock: { + kind: 'None' + } + managedIdentities: { + systemAssigned: true + } + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + 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": "ltmax001" + }, + // Non-required parameters + "loadTestDescription": { + "value": "This is a test load test to validate the module." + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "None" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ +### Example 3: _Using Customer-Managed-Keys with User-Assigned identity_ + +This instance deploys the module using Customer-Managed-Keys using a User-Assigned Identity to access the Customer-Managed-Key secret. + + +

+ +via Bicep module + +```bicep +module loadTest 'br/public:avm/res/load-test-service/load-test:' = { + name: 'loadTestDeployment' + params: { + // Required parameters + name: 'ltucmk001' + // Non-required parameters + customerManagedKey: { + keyName: '' + keyVaultResourceId: '' + userAssignedIdentityResourceId: '' + } + location: '' + managedIdentities: { + userAssignedResourceIds: [ + '' + ] + } + } +} +``` + +
+

+ +

+ +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": "ltucmk001" + }, + // Non-required parameters + "customerManagedKey": { + "value": { + "keyName": "", + "keyVaultResourceId": "", + "userAssignedIdentityResourceId": "" + } + }, + "location": { + "value": "" + }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "" + ] + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module loadTest 'br/public:avm/res/load-test-service/load-test:' = { + name: 'loadTestDeployment' + params: { + // Required parameters + name: 'ltwaf001' + // Non-required parameters + enableTelemetry: '' + loadTestDescription: 'This is a sample load test.' + location: '' + 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": "ltwaf001" + }, + // Non-required parameters + "enableTelemetry": { + "value": "" + }, + "loadTestDescription": { + "value": "This is a sample load test." + }, + "location": { + "value": "" + }, + "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 Load test. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`customerManagedKey`](#parameter-customermanagedkey) | object | The customer managed key definition. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`loadTestDescription`](#parameter-loadtestdescription) | string | The description of the load test. | +| [`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. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`tags`](#parameter-tags) | object | Resource tags. | + +### Parameter: `name` + +Name of the Load test. + +- Required: Yes +- Type: string + +### Parameter: `customerManagedKey` + +The customer managed key definition. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`keyName`](#parameter-customermanagedkeykeyname) | string | The name of the customer managed key to use for encryption. | +| [`keyVaultResourceId`](#parameter-customermanagedkeykeyvaultresourceid) | string | The resource ID of a key vault to reference a customer managed key for encryption from. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`keyVersion`](#parameter-customermanagedkeykeyversion) | string | The version of the customer managed key to reference for encryption. If not provided, using 'latest'. | +| [`userAssignedIdentityResourceId`](#parameter-customermanagedkeyuserassignedidentityresourceid) | string | User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use. | + +### Parameter: `customerManagedKey.keyName` + +The name of the customer managed key to use for encryption. + +- Required: No +- Type: string + +### Parameter: `customerManagedKey.keyVaultResourceId` + +The resource ID of a key vault to reference a customer managed key for encryption from. + +- Required: No +- Type: string + +### Parameter: `customerManagedKey.keyVersion` + +The version of the customer managed key to reference for encryption. If not provided, using 'latest'. + +- Required: No +- Type: string + +### Parameter: `customerManagedKey.userAssignedIdentityResourceId` + +User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use. + +- Required: No +- Type: string + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `loadTestDescription` + +The description of the load test. + +- Required: No +- Type: string + +### 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. Required if a user assigned identity is used for encryption. | + +### 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 if a user assigned identity is used for encryption. + +- Required: No +- Type: array + +### 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: `tags` + +Resource tags. + +- Required: No +- Type: object + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the load test. | +| `resourceGroupName` | string | The resource group the load test was deployed into. | +| `resourceId` | string | The resource ID of the load test. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/load-test-service/load-test/main.bicep b/avm/res/load-test-service/load-test/main.bicep new file mode 100644 index 0000000000..cb5a1dd4c8 --- /dev/null +++ b/avm/res/load-test-service/load-test/main.bicep @@ -0,0 +1,242 @@ +metadata name = 'Load Testing Service' +metadata description = 'This module deploys a Load test.' +metadata owner = 'Azure/module-maintainers' + +// ================ // +// Parameters // +// ================ // + +@description('Required. Name of the Load test.') +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Resource tags.') +param tags object? + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. The description of the load test.') +param loadTestDescription string? + +@description('Optional. The customer managed key definition.') +param customerManagedKey customerManagedKeyType? + +// =========== // +// Variables // +// =========== // + +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' : 'None') + 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' + ) +} + +// ================ // +// Resources // +// ================ // + +resource loadTest_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: loadTest + } + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.res.loadtestservice-loadtest.${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 cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = + if (!empty(customerManagedKey.?keyVaultResourceId)) { + name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/')) + scope: resourceGroup( + split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2], + split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4] + ) + + resource cMKKey 'keys@2023-02-01' existing = + if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { + name: customerManagedKey.?keyName ?? 'dummyKey' + } + } + +resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = + if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { + name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/')) + scope: resourceGroup( + split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2], + split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4] + ) + } + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + identity: identity + tags: tags + properties: { + description: loadTestDescription + encryption: !empty(customerManagedKey) + ? { + identity: { + type: 'UserAssigned' + resourceId: !empty(customerManagedKey.?userAssignedIdentityResourceId) ? cMKUserAssignedIdentity.id : null + } + keyUrl: !empty(customerManagedKey.?keyVersion ?? '') + ? '${cMKKeyVault::cMKKey.properties.keyUri}/${customerManagedKey!.keyVersion}' + : cMKKeyVault::cMKKey.properties.keyUriWithVersion + } + : null + } +} + +resource loadTest_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(loadTest.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: loadTest + } +] + +// ================ // +// Outputs // +// ================ // + +@description('The name of the load test.') +output name string = loadTest.name + +@description('The resource ID of the load test.') +output resourceId string = loadTest.id + +@description('The resource group the load test was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = loadTest.location + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string = loadTest.?identity.?principalId ?? '' + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The 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 customerManagedKeyType = { + @description('Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use.') + userAssignedIdentityResourceId: string? + + @description('Required. The resource ID of a key vault to reference a customer managed key for encryption from.') + keyVaultResourceId: string? + + @description('Required. The name of the customer managed key to use for encryption.') + keyName: string? + + @description('Optional. The version of the customer managed key to reference for encryption. If not provided, using \'latest\'.') + keyVersion: string? +}? + +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. Required if a user assigned identity is used for encryption.') + userAssignedResourceIds: string[]? +}? diff --git a/avm/res/load-test-service/load-test/main.json b/avm/res/load-test-service/load-test/main.json new file mode 100644 index 0000000000..db96fce11b --- /dev/null +++ b/avm/res/load-test-service/load-test/main.json @@ -0,0 +1,378 @@ +{ + "$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.26.54.24096", + "templateHash": "7118073794250229597" + }, + "name": "Load Testing Service", + "description": "This module deploys a Load test.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "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 + }, + "customerManagedKeyType": { + "type": "object", + "properties": { + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + }, + "keyVaultResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using 'latest'." + } + } + }, + "nullable": true + }, + "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. Required if a user assigned identity is used for encryption." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Load test." + } + }, + "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. Resource tags." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "loadTestDescription": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the load test." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + } + }, + "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', 'None')), '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')]" + } + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/')), coalesce(tryGet(parameters('customerManagedKey'), 'keyName'), 'dummyKey'))]", + "dependsOn": [ + "cMKKeyVault" + ] + }, + "loadTest_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.LoadTestService/loadTests/{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": [ + "loadTest" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.loadtestservice-loadtest.{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" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))]" + }, + "loadTest": { + "type": "Microsoft.LoadTestService/loadTests", + "apiVersion": "2022-12-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": { + "description": "[parameters('loadTestDescription')]", + "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('identity', createObject('type', 'UserAssigned', 'resourceId', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))), null())), 'keyUrl', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), format('{0}/{1}', reference('cMKKeyVault::cMKKey').keyUri, parameters('customerManagedKey').keyVersion), reference('cMKKeyVault::cMKKey').keyUriWithVersion)), null())]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKUserAssignedIdentity" + ] + }, + "loadTest_roleAssignments": { + "copy": { + "name": "loadTest_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.LoadTestService/loadTests/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.LoadTestService/loadTests', 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": [ + "loadTest" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the load test." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the load test." + }, + "value": "[resourceId('Microsoft.LoadTestService/loadTests', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the load test was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('loadTest', '2022-12-01', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[coalesce(tryGet(tryGet(reference('loadTest', '2022-12-01', 'full'), 'identity'), 'principalId'), '')]" + } + } +} \ No newline at end of file diff --git a/avm/res/load-test-service/load-test/tests/e2e/defaults/main.test.bicep b/avm/res/load-test-service/load-test/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..9b031cc77d --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,45 @@ +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}-loadTestService.loadTest-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'ltmin' + +@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: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + } +} diff --git a/avm/res/load-test-service/load-test/tests/e2e/max/dependencies.bicep b/avm/res/load-test-service/load-test/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..0b7f226c1a --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/max/dependencies.bicep @@ -0,0 +1,16 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@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 diff --git a/avm/res/load-test-service/load-test/tests/e2e/max/main.test.bicep b/avm/res/load-test-service/load-test/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..e2a7ec1328 --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/max/main.test.bicep @@ -0,0 +1,73 @@ +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}-loadTestService.loadTest-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'ltmax' + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + lock: { + kind: 'None' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + managedIdentities: { + systemAssigned: true + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + loadTestDescription: 'This is a test load test to validate the module.' + } +} diff --git a/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/dependencies.bicep b/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/dependencies.bicep new file mode 100644 index 0000000000..8ebced9503 --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/dependencies.bicep @@ -0,0 +1,64 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: true + softDeleteRetentionInDays: 7 + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + + resource key 'keys@2022-07-01' = { + name: 'keyEncryptionKey' + properties: { + kty: 'RSA' + } + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault::key.id}-${location}-${managedIdentity.id}-Key-Key-Vault-Crypto-User-RoleAssignment') + scope: keyVault::key + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '12338af0-0e69-4776-bea7-57ae8d297424' + ) // Key Vault Crypto User + principalType: 'ServicePrincipal' + } +} + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id + +@description('The name of the created Key Vault encryption key.') +output keyVaultKeyName string = keyVault::key.name + +@description('The URL of the created Key Vault.') +output keyVaultKeyUrl string = keyVault::key.properties.keyUriWithVersion + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id diff --git a/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/main.test.bicep b/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/main.test.bicep new file mode 100644 index 0000000000..e50e444333 --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/user-assigned-cmk-encryption/main.test.bicep @@ -0,0 +1,69 @@ +targetScope = 'subscription' + +metadata name = 'Using Customer-Managed-Keys with User-Assigned identity' +metadata description = 'This instance deploys the module using Customer-Managed-Keys using a User-Assigned Identity to access the Customer-Managed-Key secret.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-loadTestService.loadTest-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'ltucmk' + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + // Adding base time to make the name unique as purge protection must be enabled (but may not be longer than 24 characters total) + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' + location: resourceLocation + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + managedIdentities: { + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + customerManagedKey: { + keyName: nestedDependencies.outputs.keyVaultKeyName + keyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId + userAssignedIdentityResourceId: nestedDependencies.outputs.managedIdentityResourceId + } + } +} diff --git a/avm/res/load-test-service/load-test/tests/e2e/waf-aligned/main.test.bicep b/avm/res/load-test-service/load-test/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..1134a5b323 --- /dev/null +++ b/avm/res/load-test-service/load-test/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,55 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-loadTestService.loadTest-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'ltwaf' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + +@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: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + enableTelemetry: enableTelemetry + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + loadTestDescription: 'This is a sample load test.' + } +} diff --git a/avm/res/load-test-service/load-test/version.json b/avm/res/load-test-service/load-test/version.json new file mode 100644 index 0000000000..7fa401bdf7 --- /dev/null +++ b/avm/res/load-test-service/load-test/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 66cafb374e63f7d63730bcc02c31256c199f6b1a Mon Sep 17 00:00:00 2001 From: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:49:24 +0100 Subject: [PATCH 03/32] fix: Dnsrule bug for virtual Network link name (#1538) ## Description update to virualtNetworkLinkName Closes #1463 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.network.dns-forwarding-ruleset](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml/badge.svg?branch=dnsrule-bug-1463)](https://github.com/ChrisSidebotham/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../network/dns-forwarding-ruleset/README.md | 44 ++++++++++++++++--- .../network/dns-forwarding-ruleset/main.bicep | 15 +++++-- .../network/dns-forwarding-ruleset/main.json | 36 ++++++++++++--- .../tests/e2e/max/main.test.bicep | 7 ++- 4 files changed, 85 insertions(+), 17 deletions(-) diff --git a/avm/res/network/dns-forwarding-ruleset/README.md b/avm/res/network/dns-forwarding-ruleset/README.md index 0f68d737bc..74096309a7 100644 --- a/avm/res/network/dns-forwarding-ruleset/README.md +++ b/avm/res/network/dns-forwarding-ruleset/README.md @@ -148,8 +148,11 @@ module dnsForwardingRuleset 'br/public:avm/res/network/dns-forwarding-ruleset:' + virtualNetworkLinks: [ + { + name: 'mytestvnetlink1' + virtualNetworkResourceId: '' + } ] } } @@ -227,9 +230,12 @@ module dnsForwardingRuleset 'br/public:avm/res/network/dns-forwarding-ruleset:" + { + "name": "mytestvnetlink1", + "virtualNetworkResourceId": "" + } ] } } @@ -337,7 +343,7 @@ module dnsForwardingRuleset 'br/public:avm/res/network/dns-forwarding-ruleset: Date: Mon, 15 Apr 2024 16:54:00 +0200 Subject: [PATCH 04/32] feat: Convert publishModuleIndex to PWSH (#1533) ## Description [AB#33918](https://dev.azure.com/CSUSolEng/Azure%20Verified%20Modules/_workitems/edit/33918) This pull request includes changes to the bicep module index JSON file generation process. The primary changes include the introduction of a new GitHub workflow and PowerShell script to generate the module index and the removal of the previous workflow and JavaScript script. The new workflow is scheduled to run daily at 3:45 AM PST and uses Azure login and PowerShell to generate and upload the module index to Azure blob storage. The new PowerShell script generates the module index based on the modules in the repository and merges it with the previous version of the index. > Diffs have been compared and apart from a move of `avm/res/app-configuration/configuration-store` in the JSON file as its being sorted differently, there are no regressions. You can check for yourself in VScode by downloading, saving and comparing the 2 files from: [current workflow](https://biceplivedatasaprod.blob.core.windows.net/bicep-cdn-live-data-container/module-index) VS [new workflow](https://stguksjtbrmindextest.blob.core.windows.net/bicep-cdn-live-data-container/module-index) Changes to GitHub workflows: * [`.github/workflows/avm.platform.publish-module-index-json.yml`](diffhunk://#diff-a32f4d20d0b1b49f6307c794b2b75c9515a36131eef33ccab3012a2c3c431ae3R1-R77): Added a new GitHub workflow that is scheduled to run daily at 3:45 AM PST. This workflow checks out the repository, logs in to Azure, installs Azure PowerShell Modules, generates the module index, uploads the artifacts, and uploads the module index to Azure blob storage. * [`.github/workflows/publish-module-index.yml`](diffhunk://#diff-0db9403318bc32b133eb4c1f5af094a003d067d3cce1e5839b773910d8536cb0L1-L118): Removed the previous GitHub workflow that was used to publish the module index. Changes to scripts: * [`avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1`](diffhunk://#diff-d93730e5546a51dc8ca6236af545822fff02a6b4b551eb6d0a34ac4e457446a1R1-R169): Added a new PowerShell script that generates the module index based on the modules in the repository and merges it with the previous version of the index. This script is used in the new GitHub workflow. * [`scripts/github-actions/generate-module-index-data.js`](diffhunk://#diff-2c75227ba0b424f38faa9fe12c0fe64333ce433bbca33986e000139d8445c399L1-L211): Removed the previous JavaScript script that was used to generate the module index. ## Pipeline Reference | Pipeline | | -------- | | [![avm.platform.publish-module-index-json](https://github.com/jtracey93/bicep-registry-modules/actions/workflows/avm.platform.publish-module-index-json.yml/badge.svg)](https://github.com/jtracey93/bicep-registry-modules/actions/workflows/avm.platform.publish-module-index-json.yml) | > Had to merge in main to resolve a conflict which wiped the workflow from my fork. However, last run can be seen here: https://github.com/jtracey93/bicep-registry-modules/actions/runs/8572556792/job/23495383080 and comparison of files above in storage accounts is sufficient evidence ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Alexander Sehr --- ...avm.platform.publish-module-index-json.yml | 95 ++++++++ .github/workflows/publish-module-index.yml | 118 ---------- .gitignore | 2 + .../Invoke-AvmJsonModuleIndexGeneration.ps1 | 222 ++++++++++++++++++ .../generate-module-index-data.js | 211 ----------------- .../generate-module-index-md.js | 179 -------------- .../runlocally/run-generate-module-index.bat | 4 - .../runlocally/run-generate-module-index.js | 50 ---- .../runlocally/run-generate-module-index.sh | 9 - 9 files changed, 319 insertions(+), 571 deletions(-) create mode 100644 .github/workflows/avm.platform.publish-module-index-json.yml delete mode 100644 .github/workflows/publish-module-index.yml create mode 100644 avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 delete mode 100644 scripts/github-actions/generate-module-index-data.js delete mode 100644 scripts/github-actions/generate-module-index-md.js delete mode 100644 scripts/github-actions/runlocally/run-generate-module-index.bat delete mode 100644 scripts/github-actions/runlocally/run-generate-module-index.js delete mode 100644 scripts/github-actions/runlocally/run-generate-module-index.sh diff --git a/.github/workflows/avm.platform.publish-module-index-json.yml b/.github/workflows/avm.platform.publish-module-index-json.yml new file mode 100644 index 0000000000..d82f31317e --- /dev/null +++ b/.github/workflows/avm.platform.publish-module-index-json.yml @@ -0,0 +1,95 @@ +# This publishes the list of all public bicep modules to an index file that the Bicep vscode extension can read for intellisense using pwsh +name: .Platform - Publish [moduleIndex.json] +on: + schedule: + - cron: 45 11 * * * # Run daily at 3:45 AM PST + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + upload-index-data: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 // Needed to fetch all history and tags + + - name: Log in to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.PUBLISH_CLIENT_ID }} + tenant-id: ${{ secrets.PUBLISH_TENANT_ID }} + subscription-id: ${{ secrets.PUBLISH_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Install Azure Powershell Modules + shell: pwsh + run: | + if(-not (Get-Module 'Az.Storage' -ListAvailable)) { + Install-Module -Name 'Az.Storage' -Force + } + + - name: Generate moduleIndex.json + shell: pwsh + run: | + # Load used functions + . (Join-Path $env:GITHUB_WORKSPACE 'avm' 'utilities' 'pipelines' 'platform' 'Invoke-AvmJsonModuleIndexGeneration.ps1') + + $functionInput = @{ + storageAccountName = 'biceplivedatasaprod' + storageAccountContainer = 'bicep-cdn-live-data-container' + storageBlobName = 'module-index' + moduleIndexJsonFilePath = 'moduleIndex.json' + prefixForLastModuleIndexJsonFile = 'last-' + prefixForCurrentGeneratedModuleIndexJsonFile = 'generated-' + } + + Write-Verbose "Invoke task with" -Verbose + Write-Verbose ($functionInput | ConvertTo-Json | Out-String) -Verbose + + if(-not (Invoke-AvmJsonModuleIndexGeneration @functionInput -Force)) { + Write-Output ('{0}={1}' -f 'anyErrorsOccurred', $true) >> $env:GITHUB_ENV + } + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: publish-module-index-json-artifacts + path: | + moduleIndex.json + last-moduleIndex.json + generated-moduleIndex.json + + - name: Upload to blob storage + shell: pwsh + run: | + $storageAccountInfo = @{ + storageAccountName = 'biceplivedatasaprod' + storageAccountContainer = 'bicep-cdn-live-data-container' + storageBlobName = 'module-index' + storageBlobContentType = @{'ContentType' = 'application/json'} + } + Write-Verbose ('Uploading [moduleIndex.json] to blob storage account [{0}] in container [{1}] as blob [{2}]' -f $storageAccountInfo.storageAccountName, $storageAccountInfo.storageAccountContainer, $storageAccountInfo.storageBlobName) -Verbose + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccountInfo.storageAccountName -UseConnectedAccount + + $functionInput = @{ + Context = $storageContext + Container = $storageAccountInfo.storageAccountContainer + Blob = $storageAccountInfo.storageBlobName + File = 'moduleIndex.json' + Properties = $storageAccountInfo.storageBlobContentType + } + Set-AzStorageBlobContent @functionInput -Force + + Write-Verbose ('Upload of [{0}] complete.' -f $storageAccountInfo.storageBlobName) -Verbose + + - name: Check if any errors occurred during 'Generate moduleIndex.json' + if: ${{ env.anyErrorsOccurred == 'true' }} + shell: pwsh + run: | + throw "Errors occurred during 'Generate moduleIndex.json' step. Please check the logs of that step in the workflow." diff --git a/.github/workflows/publish-module-index.yml b/.github/workflows/publish-module-index.yml deleted file mode 100644 index bbf1b68164..0000000000 --- a/.github/workflows/publish-module-index.yml +++ /dev/null @@ -1,118 +0,0 @@ -# This publishes the list of all public bicep modules to an index file that the Bicep vscode extension can read for intellisense -# and also a human-readable HTML version. -name: Publish module index -on: - schedule: - - cron: 45 11 * * * # Run daily at 3:45 AM PST - workflow_dispatch: - -permissions: - id-token: write - pages: write - contents: read - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - upload-index-data: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 // Needed to fetch all history and tags - - - name: Install packages - run: npm ci - - - name: Generate moduleIndex.json - uses: actions/github-script@v7 - with: - script: | - const script = require("./scripts/github-actions/generate-module-index-data.js") - await script({ require, github, context, core }) - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: moduleIndex.json - path: moduleIndex.json - - - name: Log in to Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.PUBLISH_CLIENT_ID }} - tenant-id: ${{ secrets.PUBLISH_TENANT_ID }} - subscription-id: ${{ secrets.PUBLISH_SUBSCRIPTION_ID }} - - - name: Upload to blob storage - uses: azure/CLI@v2 - with: - inlineScript: | - az storage blob upload --account-name biceplivedatasaprod --container-name bicep-cdn-live-data-container --name module-index --file moduleIndex.json --auth-mode login --overwrite - - build-index-page: - runs-on: ubuntu-latest - needs: upload-index-data - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install packages - run: npm ci - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: moduleIndex.json - - - name: Generate index.md - uses: actions/github-script@v7 - with: - script: | - const script = require("./scripts/github-actions/generate-module-index-md.js") - await script({ require, github, context, core }) - - - name: Upload Markdown artifact - uses: actions/upload-artifact@v4 - with: - path: ./docs/jekyll/index.md - name: index.md - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.1" - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - cache-version: 0 # Increment this number if you need to re-download cached gems - working-directory: ./docs/jekyll - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - - - name: Build with Jekyll - working-directory: ./docs/jekyll - run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" # Outputs to the './_site' directory by default - env: - JEKYLL_ENV: production - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./docs/jekyll/_site - - deploy-index-page: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build-index-page - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 813cba8c38..f73a7ea35c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules .netrc moduleIndex.json +last-moduleIndex.json +generated-moduleIndex.json .DS_Store diff --git a/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 new file mode 100644 index 0000000000..49a58895c5 --- /dev/null +++ b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 @@ -0,0 +1,222 @@ +<# +.SYNOPSIS +Creates the moduleIndex.json file for the AVM modules that is used by Visual Studio Code and other IDEs to provide the intellisense list of modules from the Bicep public registry. + +.PARAMETER storageAccountName +The name of the Azure Storage Account where the moduleIndex.json file is stored. Default is 'biceplivedatasaprod'. + +.PARAMETER storageAccountContainer +The name of the Azure Storage Account Blob Container where the moduleIndex.json file is stored. Default is 'bicep-cdn-live-data-container'. + +.PARAMETER storageBlobName +The name of the Azure Storage Account Blob where the moduleIndex.json file is stored. Default is 'module-index'. + +.PARAMETER moduleIndexJsonFilePath +The file path to save the moduleIndex.json file to. Default is 'moduleIndex.json'. + +.PARAMETER prefixForLastModuleIndexJsonFile +The prefix to add to the last version of the moduleIndex.json file that is downloaded from the storage account. Default is 'last-'. + +.PARAMETER prefixForCurrentGeneratedModuleIndexJsonFile +The prefix to add to the current generated moduleIndex.json file. Default is 'generated-'. + +.PARAMETER doNotMergeWithLastModuleIndexJsonFileVersion +If specified, the last version of the moduleIndex.json file that is downloaded from the storage account will not be merged with the current generated moduleIndex.json file. + +.DESCRIPTION +Creates the moduleIndex.json file for the AVM modules that is used by Visual Studio Code and other IDEs to provide the intellisense list of modules from the Bicep public registry. + +Also has error handling to cope with a module not being published fully but will not prevent the script from completeing each time. + +The script uses a merging strategy with the previous version of moduleIndex.json to ensure that the file is always up to date with the latest modules but previous versions are not removed, this can be changed by specifying the $doNotMergeWithLastModuleIndexJsonFileVersion parameter. + +.EXAMPLE +Invoke-AvmJsonModuleIndexGeneration -storageAccountName '' -storageAccountContainer '' -storageBlobName '' -moduleIndexJsonFilePath 'moduleIndex.json' -prefixForLastModuleIndexJsonFile 'last-' -prefixForCurrentGeneratedModuleIndexJsonFile 'generated-' + +This example will generate the moduleIndex.json file for the AVM modules and save it to the current directory and merge it with the last version of the moduleIndex.json file that was downloaded from the storage account. + +.NOTES +The function requires Azure PowerShell Storage Module (Az.Storage) to be installed and the user to be logged in to Azure. +#> + +function Invoke-AvmJsonModuleIndexGeneration { + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $false)] + [string] $storageAccountName = 'biceplivedatasaprod', + + [Parameter(Mandatory = $false)] + [string] $storageAccountContainer = 'bicep-cdn-live-data-container', + + [Parameter(Mandatory = $false)] + [string] $storageBlobName = 'module-index', + + [Parameter(Mandatory = $false)] + [string] $moduleIndexJsonFilePath = 'moduleIndex.json', + + [Parameter(Mandatory = $false)] + [string] $prefixForLastModuleIndexJsonFile = 'last-', + + [Parameter(Mandatory = $false)] + [string] $prefixForCurrentGeneratedModuleIndexJsonFile = 'generated-', + + [Parameter(Mandatory = $false)] + [switch] $doNotMergeWithLastModuleIndexJsonFileVersion + ) + + ## Download the current published moduleIndex.json from the storage account if the $doNotMergeWithLastModuleIndexJsonFileVersion is set to $false + if (-not $doNotMergeWithLastModuleIndexJsonFileVersion) { + try { + $lastModuleIndexJsonFilePath = $prefixForLastModuleIndexJsonFile + $moduleIndexJsonFilePath + + Write-Verbose "Attempting to get last version of the moduleIndex.json from the Storage Account: $storageAccountName, Container: $storageAccountContainer, Blob: $storageBlobName and save to file: $lastModuleIndexJsonFilePath ..." -Verbose + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount + + Get-AzStorageBlobContent -Blob $storageBlobName -Container $storageAccountContainer -Context $storageContext -Destination $lastModuleIndexJsonFilePath -Force | Out-Null + } catch { + Write-Error "Unable to retrieve moduleIndex.json file from the Storage Account: $storageAccountName, Container: $storageAccountContainer, Blob: $storageBlobName. Error: $($_.Exception.Message)" -ErrorAction Stop + } + + ## Check if the last version of the moduleIndex.json (last-moduleIndex.json) file exists and is not empty + + if (Test-Path $lastModuleIndexJsonFilePath) { + $lastModuleIndexJsonFileContent = Get-Content $lastModuleIndexJsonFilePath + if ($null -eq $lastModuleIndexJsonFileContent) { + Write-Error "The last version of the moduleIndex.json file (last-moduleIndex.json) exists but is empty. File: $lastModuleIndexJsonFilePath" -ErrorAction Stop + } + Write-Verbose 'The last version of the moduleIndex.json file (last-moduleIndex.json) exists and is not empty. Proceeding...' -Verbose + } + } + + ## Generate the new moduleIndex.json file based off the modules in the repository + + $currentGeneratedModuleIndexJsonFilePath = $prefixForCurrentGeneratedModuleIndexJsonFile + $moduleIndexJsonFilePath + + Write-Verbose "Generating the current generated moduleIndex.json file and saving to: $currentGeneratedModuleIndexJsonFilePath ..." -Verbose + + $anyErrorsOccurred = $false + $moduleIndexData = @() + + foreach ($avmModuleRoot in @('avm/res', 'avm/ptn')) { + $avmModuleGroups = (Get-ChildItem -Path $avmModuleRoot -Directory).Name + + foreach ($moduleGroup in $avmModuleGroups) { + $moduleGroupPath = "$avmModuleRoot/$moduleGroup" + $moduleNames = (Get-ChildItem -Path $moduleGroupPath -Directory).Name + + foreach ($moduleName in $moduleNames) { + $modulePath = "$moduleGroupPath/$moduleName" + $mainJsonPath = "$modulePath/main.json" + $tagListUrl = "https://mcr.microsoft.com/v2/bicep/$modulePath/tags/list" + + try { + Write-Verbose "Processing AVM Module '$modulePath'..." -Verbose + Write-Verbose " Getting available tags at '$tagListUrl'..." -Verbose + + try { + $tagListResponse = Invoke-RestMethod -Uri $tagListUrl + } catch { + $anyErrorsOccurred = $true + Write-Error "Error occurred while accessing URL: $tagListUrl" + Write-Error "Error message: $($_.Exception.Message)" + continue + } + $tags = $tagListResponse.tags | Sort-Object + + $properties = [ordered]@{} + foreach ($tag in $tags) { + $gitTag = "$modulePath/$tag" + $documentationUri = "https://github.com/Azure/bicep-registry-modules/tree/$gitTag/$modulePath/README.md" + + try { + $moduleMainJsonUri = "https://raw.githubusercontent.com/Azure/bicep-registry-modules/$gitTag/$mainJsonPath" + Write-Verbose " Getting available description for tag $tag via '$moduleMainJsonUri'..." -Verbose + $moduleMainJsonUriResponse = Invoke-RestMethod -Uri $moduleMainJsonUri + $description = $moduleMainJsonUriResponse.metadata.description + } catch { + $anyErrorsOccurred = $true + Write-Error "Error occurred while accessing description for tag $tag via '$moduleMainJsonUri'" + Write-Error "Error message: $($_.Exception.Message)" + continue + } + + $properties[$tag] = [ordered]@{ + description = $description + documentationUri = $documentationUri + } + } + + $moduleIndexData += [ordered]@{ + moduleName = $modulePath + tags = @($tags) + properties = $properties + } + } catch { + $anyErrorsOccurred = $true + Write-Error "Error message: $($_.Exception.Message)" + } + } + + $numberOfModuleGroupsProcessed++ + } + } + + Write-Verbose "Processed $numberOfModuleGroupsProcessed modules groups." -Verbose + Write-Verbose "Processed $($moduleIndexData.Count) total modules." -Verbose + + Write-Verbose "Convert moduleIndexData variable to JSON and save as 'generated-moduleIndex.json'" -Verbose + $moduleIndexData | ConvertTo-Json -Depth 10 | Out-File -FilePath $currentGeneratedModuleIndexJsonFilePath + + ## Merge the new moduleIndex.json file with the previous version if the $doNotMergeWithLastModuleIndexJsonFileVersion is not specified + + if (-not $doNotMergeWithLastModuleIndexJsonFileVersion) { + Write-Verbose "Merging 'generated-moduleIndex.json' (new) file with 'last-moduleIndex.json' (previous) file..." -Verbose + + $lastModuleIndexJsonFileContent = Get-Content $lastModuleIndexJsonFilePath + $currentGeneratedModuleIndexJsonFileContent = Get-Content $currentGeneratedModuleIndexJsonFilePath + + $lastModuleIndexData = $lastModuleIndexJsonFileContent | ConvertFrom-Json -Depth 10 + $currentGeneratedModuleIndexData = $currentGeneratedModuleIndexJsonFileContent | ConvertFrom-Json -Depth 10 + + $initialMergeOfJsonFilesData = @{} + + foreach ($module in $currentGeneratedModuleIndexData) { + $initialMergeOfJsonFilesData[$module.moduleName] = $module + } + + # Add modules from lastModuleIndexData to the initialMergeOfJsonFilesData hashtable, merging tags and properties if they exist in both files + foreach ($module in $lastModuleIndexData) { + if (-not $initialMergeOfJsonFilesData.ContainsKey($module.moduleName)) { + $initialMergeOfJsonFilesData[$module.moduleName] = $module + } else { + # If the module exists, merge the tags and properties + $mergedModule = $initialMergeOfJsonFilesData[$module.moduleName] + $mergedModule.tags = @(($mergedModule.tags + $module.tags) | Sort-Object -Unique) + + # Merge properties + foreach ($property in $module.properties.PSObject.Properties) { + if (-not $mergedModule.properties.PSObject.Properties.Name.Contains($property.Name)) { + $mergedModule.properties | Add-Member -NotePropertyName $property.Name -NotePropertyValue $property.Value + } + } + } + } + + # Convert the mergedModuleIndexData hashtable to an array of values (i.e., the modules) + $mergedModuleIndexData = $initialMergeOfJsonFilesData.Values + + # Sort the modules by their names + $sortedMergedModuleIndexData = $mergedModuleIndexData | Sort-Object moduleName + + Write-Verbose "Convert mergedModuleIndexData variable to JSON and save as 'moduleIndex.json'" -Verbose + $sortedMergedModuleIndexData | ConvertTo-Json -Depth 10 | Out-File -FilePath $moduleIndexJsonFilePath + } + if ($doNotMergeWithLastModuleIndexJsonFileVersion -eq $true) { + Write-Verbose "Convert currentGeneratedModuleIndexData variable to JSON and save as 'moduleIndex.json to overwrite it as `doNotMergeWithLastModuleIndexJsonFileVersion` was specified'" -Verbose + $moduleIndexData | ConvertTo-Json -Depth 10 | Out-File -FilePath $moduleIndexJsonFilePath -Force + } + + return ($anyErrorsOccurred ? $false : $true) + +} diff --git a/scripts/github-actions/generate-module-index-data.js b/scripts/github-actions/generate-module-index-data.js deleted file mode 100644 index 0489b525b1..0000000000 --- a/scripts/github-actions/generate-module-index-data.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * @param {typeof import("fs").promises} fs - * @param {string} dir - */ -async function getSubdirNames(fs, dir) { - var files = await fs.readdir(dir, { withFileTypes: true }); - return files.filter((x) => x.isDirectory()).map((x) => x.name); -} - -async function getModuleDescription( - github, - core, - mainJsonPath, - gitTag, - context -) { - const gitTagRef = `tags/${gitTag}`; - - core.info(` Retrieving main.json at Git tag ref ${gitTagRef}`); - - // Get the SHA of the commit - const { - data: { - object: { sha: commitSha }, - }, - } = await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: gitTagRef, - }); - - // Get the tree data - const { - data: { tree }, - } = await github.rest.git.getTree({ - owner: context.repo.owner, - repo: context.repo.repo, - tree_sha: commitSha, - recursive: true, - }); - - // Find the file in the tree - const file = tree.find((f) => f.path === mainJsonPath); - if (!file) { - throw new Error(`File ${mainJsonPath} not found in repository`); - } - - // Get the blob data - const { - data: { content }, - } = await github.rest.git.getBlob({ - owner: context.repo.owner, - repo: context.repo.repo, - file_sha: file.sha, - }); - - // content is base64 encoded, so decode it - const fileContent = Buffer.from(content, "base64").toString("utf8"); - - // Parse the main.json file - if (fileContent !== "") { - const json = JSON.parse(fileContent); - return json.metadata.description; - } else { - throw new Error( - "The specified path does not represent a file or it is empty." - ); - } -} - -/** - * @typedef Params - * @property {typeof require} require - * @property {ReturnType} github - * @property {typeof import("@actions/github").context} context - * @property {typeof import("@actions/core")} core - * - * @param {Params} params - */ -async function generateModuleIndexData({ require, github, context, core }) { - const fs = require("fs").promises; - const { existsSync } = require("fs"); - const axios = require("axios").default; - const moduleIndexData = []; - - let numberOfModuleGroupsProcessed = 0; - - // BRM Modules - for (const moduleGroup of await getSubdirNames(fs, "modules")) { - const moduleGroupPath = `modules/${moduleGroup}`; - const moduleNames = await getSubdirNames(fs, moduleGroupPath); - - for (const moduleName of moduleNames) { - const modulePath = `${moduleGroupPath}/${moduleName}`; - const archivedFilePath = `${modulePath}/ARCHIVED.md`; - - if (existsSync(archivedFilePath)) { - continue; - } - - const mainJsonPath = `${modulePath}/main.json`; - // BRM module git tags do not include the modules/ prefix. - const mcrModulePath = modulePath.slice(8); - const tagListUrl = `https://mcr.microsoft.com/v2/bicep/${mcrModulePath}/tags/list`; - - try { - core.info(`Processing BRM Module "${modulePath}"...`); - core.info(` Getting available tags at "${tagListUrl}"...`); - - const tagListResponse = await axios.get(tagListUrl); - const tags = tagListResponse.data.tags.sort(); - - const properties = {}; - for (const tag of tags) { - // Using mcrModulePath because BRM module git tags do not include the modules/ prefix - const gitTag = `${mcrModulePath}/${tag}`; - const documentationUri = `https://github.com/Azure/bicep-registry-modules/tree/${gitTag}/${modulePath}/README.md`; - const description = await getModuleDescription( - github, - core, - mainJsonPath, - gitTag, - context - ); - - properties[tag] = { description, documentationUri }; - } - - moduleIndexData.push({ - moduleName: mcrModulePath, - tags, - properties, - }); - } catch (error) { - core.setFailed(error); - } - } - - numberOfModuleGroupsProcessed++; - } - - for (const avmModuleRoot of ["avm/res", "avm/ptn"]) { - // Resource module path pattern: `avm/res/${moduleGroup}/${moduleName}` - // Pattern module path pattern: `avm/ptn/${moduleGroup}/${moduleName}` - const avmModuleGroups = await getSubdirNames(fs, avmModuleRoot); - - for (const moduleGroup of avmModuleGroups) { - const moduleGroupPath = `${avmModuleRoot}/${moduleGroup}`; - const moduleNames = await getSubdirNames(fs, moduleGroupPath); - - for (const moduleName of moduleNames) { - const modulePath = `${moduleGroupPath}/${moduleName}`; - const mainJsonPath = `${modulePath}/main.json`; - const tagListUrl = `https://mcr.microsoft.com/v2/bicep/${modulePath}/tags/list`; - - try { - core.info(`Processing AVM Module "${modulePath}"...`); - core.info(` Getting available tags at "${tagListUrl}"...`); - - const tagListResponse = await axios.get(tagListUrl); - const tags = tagListResponse.data.tags.sort(); - - const properties = {}; - for (const tag of tags) { - const gitTag = `${modulePath}/${tag}`; - const documentationUri = `https://github.com/Azure/bicep-registry-modules/tree/${gitTag}/${modulePath}/README.md`; - const description = await getModuleDescription( - github, - core, - mainJsonPath, - gitTag, - context - ); - - properties[tag] = { description, documentationUri }; - } - - moduleIndexData.push({ - moduleName: modulePath, - tags, - properties, - }); - } catch (error) { - core.setFailed(error); - } - } - - numberOfModuleGroupsProcessed++; - } - } - - core.info(`Writing moduleIndex.json`); - await fs.writeFile( - "moduleIndex.json", - JSON.stringify(moduleIndexData, null, 2) - ); - - core.info(`Processed ${numberOfModuleGroupsProcessed} modules groups.`); - core.info(`Processed ${moduleIndexData.length} total modules.`); - core.info( - `${ - moduleIndexData.filter((m) => - Object.keys(m.properties).some( - (key) => "description" in m.properties[key] - ) - ).length - } modules have a description` - ); -} - -module.exports = generateModuleIndexData; diff --git a/scripts/github-actions/generate-module-index-md.js b/scripts/github-actions/generate-module-index-md.js deleted file mode 100644 index 5c7bb1360d..0000000000 --- a/scripts/github-actions/generate-module-index-md.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * @param {object[]} items - * @param {(item: any) => string} keyGetter - * @returns - */ -function groupBy(items, keyGetter) { - const map = new Map(); - - for (const item of items) { - const key = keyGetter(item); - const collection = map.get(key); - if (!collection) { - map.set(key, [item]); - } else { - collection.push(item); - } - } - - return map; -} - -/** - * @param {ReturnType} github - * @param {typeof import("@actions/github").context} context - * @param {string} tag - */ -async function getPublishDate(github, context, tag) { - const { owner, repo } = context.repo; - - const reference = await github.rest.git.getRef({ - owner, - repo, - ref: `tags/${tag}`, - }); - if (!reference) { - throw `Could not find tag ${tag}`; - } - - const commit = await github.rest.git.getCommit({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: reference.data.object.sha, - }); - if (!commit) { - throw `Could not find commit for tag ${tag}`; - } - - return commit.data.committer.date.split("T")[0]; -} - -/** - * @param {ReturnType} github - * @param {typeof import("@actions/github").context} context - * @param {Array<{ moduleName: string, tags: string[] }>} modules - * @param {typeof import("prettier")} prettier - * @returns - */ -async function generateModuleGroupTable(github, context, modules, prettier) { - const moduleGroupTableData = [ - [ - "Module", - "Latest version", - "Published on", - "Source code", - "Readme", - "Description", - ], - ]; - - for (const module of modules) { - const modulePath = `\`${module.moduleName}\``; - - // module.tags is an sorted array. - const latestVersion = module.tags.slice(-1)[0]; - const versionListUrl = `https://mcr.microsoft.com/v2/bicep/${module.moduleName}/tags/list`; - const versionBadgeUrl = `https://img.shields.io/badge/mcr-${latestVersion}-blue`; - const versionBadge = ``; - - const tag = `${module.moduleName}/${latestVersion}`; - const publishDate = await getPublishDate(github, context, tag); - - const description = - module.properties && - module.properties[latestVersion]?.description?.replace(/\n|\r/g, " "); - - const moduleRootUrl = `https://github.com/Azure/bicep-registry-modules/tree/main/modules/${module.moduleName}`; - const sourceCodeButton = `[🦾 Source code](${moduleRootUrl}/main.bicep){: .btn}`; - const readmeButton = `[📃 Readme](${moduleRootUrl}/README.md){: .btn .btn-purple}`; - - moduleGroupTableData.push([ - modulePath, - versionBadge, - publishDate, - sourceCodeButton, - readmeButton, - description, - ]); - } - - const { markdownTable } = await import("markdown-table"); - const table = markdownTable(moduleGroupTableData, { - align: ["l", "r", "r", "r", "r", "l"], - }); - - return prettier.format(table, { parser: "markdown" }); -} - -/** - * @typedef Params - * @property {typeof require} require - * @property {ReturnType} github - * @property {typeof import("@actions/github").context} context - * @property {typeof import("@actions/core")} core - * - * @param {Params} params - */ -async function generateModuleIndexMarkdown({ require, github, context, core }) { - const fs = require("fs").promises; - const prettier = require("prettier"); - - var moduleIndexMarkdown = `--- -layout: default -title: Module Index -nav_order: 1 -permalink: / ---- - -# Module Index -{: .fs-9} - ---- - -{: .note-title } -> Azure Verified Modules (AVM) - Module Indexes -> -> The [Azure Verified Modules (AVM)](https://aka.ms/avm) module indexes are located over on the AVM website. You can find them here for: -> -> - [Resource Modules](https://aka.ms/avm/index/bicep/res) -> - [Pattern Modules](https://aka.ms/avm/index/bicep/ptn) - ---- - -`; - - const moduleIndexDataContent = await fs.readFile("moduleIndex.json", { - encoding: "utf-8", - }); - if (!moduleIndexDataContent) { - throw "Could not read moduleIndex.json"; - } - - const moduleIndexData = JSON.parse(moduleIndexDataContent); - const moduleGroups = groupBy( - moduleIndexData, - (x) => x.moduleName.split("/")[0] - ); - - for (const [moduleGroup, modules] of moduleGroups) { - if (moduleGroup.includes("avm")) { - continue; - } - core.debug(`Generating ${moduleGroup}...`); - - const moduleGroupTable = await generateModuleGroupTable( - github, - context, - modules, - prettier - ); - - moduleIndexMarkdown += `## ${moduleGroup}\n\n`; - moduleIndexMarkdown += moduleGroupTable; - moduleIndexMarkdown += "\n\n"; - } - - await fs.writeFile("docs/jekyll/index.md", moduleIndexMarkdown); -} - -module.exports = generateModuleIndexMarkdown; diff --git a/scripts/github-actions/runlocally/run-generate-module-index.bat b/scripts/github-actions/runlocally/run-generate-module-index.bat deleted file mode 100644 index 76c5608120..0000000000 --- a/scripts/github-actions/runlocally/run-generate-module-index.bat +++ /dev/null @@ -1,4 +0,0 @@ -rem See run-generate-module-index.js for instructions -rem Must run from the root of the repo - -node scripts\github-actions\runlocally\run-generate-module-index.js diff --git a/scripts/github-actions/runlocally/run-generate-module-index.js b/scripts/github-actions/runlocally/run-generate-module-index.js deleted file mode 100644 index e81e20630e..0000000000 --- a/scripts/github-actions/runlocally/run-generate-module-index.js +++ /dev/null @@ -1,50 +0,0 @@ -// This runs locally the scripts that power the "Publish Module Index" action on github -// (see bicep-registry-modules/.github/workflows/publish-module-index.yml) -// to make it easier to debug locally. -// -// Run via run-generate-module-index{.bat,.sh} from the root of the repo -// after having set up these environment variables: -// -// GITHUB_PAT: your github PAT -// GITHUB_OWNER: -// "Azure" for Azure/bicep-registry-modules -// sor your github username for a fork of bicep-registry-modules - -const path = require("path"); -const core = require("@actions/core"); - -const process = require("process"); -if (!process.env.GITHUB_PAT) { - console.error("Need to set GITHUB_PAT"); - return; -} -if (!process.env.GITHUB_OWNER) { - console.error( - 'Need to set GITHUB_OWNER (e.g. "Azure" for "Azure/bicep-registry-modules"' - ); - return; -} - -const github = require("@actions/github").getOctokit(process.env.GITHUB_PAT); - -const context = { - repo: { owner: process.env.GITHUB_OWNER, repo: "bicep-registry-modules" }, -}; - -const scriptGenerateModuleIndexData = require(path.join( - process.cwd(), - "scripts/github-actions/generate-module-index-data.js" -)); - -scriptGenerateModuleIndexData({ require, github, context, core }).then(() => { - const scriptGenerateModuleIndexMarkdown = require(path.join( - process.cwd(), - "scripts/github-actions/generate-module-index-md.js" - )); - - scriptGenerateModuleIndexMarkdown({ require, github, context, core }).then( - () => { - console.info("Done."); - } - ); -}); diff --git a/scripts/github-actions/runlocally/run-generate-module-index.sh b/scripts/github-actions/runlocally/run-generate-module-index.sh deleted file mode 100644 index 45210b4093..0000000000 --- a/scripts/github-actions/runlocally/run-generate-module-index.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -# See run-generate-module-index.js for instructions -# Must run from the root of the repo - -node scripts/github-actions/runlocally/run-generate-module-index.js -pushd docs/jekyll -bundle exec jekyll build --baseurl $PWD/_site -popd From edf3d5db6565488a943912b4963c3d3ec7fa948d Mon Sep 17 00:00:00 2001 From: John Date: Mon, 15 Apr 2024 17:00:06 +0200 Subject: [PATCH 05/32] feat: Added UDTs for disks and ability to give a custom name to a disk - `avm/res/compute/virtual-machine` (#1517) ## Description This pull request adds UDTs for OS disk and data disk. Also, included the option to give a custom name to either disk. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.compute.virtual-machine](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml/badge.svg?branch=johnlokerse%2Fissue1436)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Rainer Halanek <61878316+rahalan@users.noreply.github.com> --- avm/res/compute/virtual-machine/README.md | 355 +++++++++++++++--- .../virtual-machine/extension/main.json | 4 +- avm/res/compute/virtual-machine/main.bicep | 110 ++++-- avm/res/compute/virtual-machine/main.json | 274 +++++++++++--- .../tests/e2e/atmg/main.test.bicep | 14 +- .../tests/e2e/linux.defaults/main.test.bicep | 2 +- .../tests/e2e/linux.max/dependencies.bicep | 8 +- .../tests/e2e/linux.max/main.test.bicep | 11 +- .../tests/e2e/waf-aligned/dependencies.bicep | 8 +- .../tests/e2e/waf-aligned/main.test.bicep | 8 +- .../e2e/windows.defaults/main.test.bicep | 2 +- .../main.test.bicep | 2 +- .../e2e/windows.hostpool/main.test.bicep | 12 +- .../tests/e2e/windows.max/dependencies.bicep | 8 +- .../tests/e2e/windows.max/main.test.bicep | 13 +- .../tests/e2e/windows.nvidia/main.test.bicep | 8 +- .../tests/e2e/windows.ssecmk/main.test.bicep | 4 +- 17 files changed, 686 insertions(+), 157 deletions(-) diff --git a/avm/res/compute/virtual-machine/README.md b/avm/res/compute/virtual-machine/README.md index cdd5b09b49..9907ba0feb 100644 --- a/avm/res/compute/virtual-machine/README.md +++ b/avm/res/compute/virtual-machine/README.md @@ -89,7 +89,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -163,7 +163,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { }, "osDisk": { "value": { - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -239,7 +239,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -308,7 +308,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -439,12 +439,13 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadOnly' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'osdisk01' } osType: 'Linux' vmSize: 'Standard_DS2_v2' @@ -458,19 +459,21 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { caching: 'ReadWrite' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'datadisk01' } { caching: 'ReadWrite' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'datadisk02' } ] disablePasswordAuthentication: true @@ -703,12 +706,13 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadOnly", - "createOption": "fromImage", + "createOption": "FromImage", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "osdisk01" } }, "osType": { @@ -736,19 +740,21 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "caching": "ReadWrite", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "datadisk01" }, { "caching": "ReadWrite", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "datadisk02" } ] }, @@ -1011,9 +1017,9 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1031,7 +1037,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { caching: 'ReadOnly' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1040,7 +1046,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { caching: 'ReadOnly' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1292,9 +1298,9 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "createOption": "fromImage", + "createOption": "FromImage", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -1328,7 +1334,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "caching": "ReadOnly", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -1337,7 +1343,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "caching": "ReadOnly", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -1558,7 +1564,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1618,7 +1624,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -1680,7 +1686,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1769,7 +1775,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -1866,7 +1872,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -1949,7 +1955,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -2101,12 +2107,13 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'osdisk01' } osType: 'Windows' vmSize: 'Standard_DS2_v2' @@ -2121,19 +2128,23 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { caching: 'None' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 + lun: 0 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'datadisk01' } { caching: 'None' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 + lun: 1 managedDisk: { storageAccountType: 'Premium_LRS' } + name: 'datadisk02' } ] enableAutomaticUpdates: true @@ -2383,12 +2394,13 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "createOption": "fromImage", + "createOption": "FromImage", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "osdisk01" } }, "osType": { @@ -2419,19 +2431,23 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "caching": "None", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, + "lun": 0, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "datadisk01" }, { "caching": "None", "createOption": "Empty", "deleteOption": "Delete", - "diskSizeGB": "128", + "diskSizeGB": 128, + "lun": 1, "managedDisk": { "storageAccountType": "Premium_LRS" - } + }, + "name": "datadisk02" } ] }, @@ -2650,7 +2666,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { ] osDisk: { caching: 'ReadWrite' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -2713,7 +2729,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "osDisk": { "value": { "caching": "ReadWrite", - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "storageAccountType": "Premium_LRS" } @@ -2779,7 +2795,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { diskEncryptionSet: { id: '' @@ -2793,7 +2809,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { adminPassword: '' dataDisks: [ { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { diskEncryptionSet: { id: '' @@ -2852,7 +2868,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { }, "osDisk": { "value": { - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "diskEncryptionSet": { "id": "" @@ -2874,7 +2890,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:' = { "dataDisks": { "value": [ { - "diskSizeGB": "128", + "diskSizeGB": 128, "managedDisk": { "diskEncryptionSet": { "id": "" @@ -3046,6 +3062,125 @@ Specifies the OS disk. For security reasons, it is recommended to specify DiskEn - Required: Yes - Type: object +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diskSizeGB`](#parameter-osdiskdisksizegb) | int | Specifies the size of an empty data disk in gigabytes. | +| [`managedDisk`](#parameter-osdiskmanageddisk) | object | The managed disk parameters. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`caching`](#parameter-osdiskcaching) | string | Specifies the caching requirements. | +| [`createOption`](#parameter-osdiskcreateoption) | string | Specifies how the virtual machine should be created. | +| [`deleteOption`](#parameter-osdiskdeleteoption) | string | Specifies whether data disk should be deleted or detached upon VM deletion. | +| [`name`](#parameter-osdiskname) | string | The disk name. | + +### Parameter: `osDisk.diskSizeGB` + +Specifies the size of an empty data disk in gigabytes. + +- Required: Yes +- Type: int + +### Parameter: `osDisk.managedDisk` + +The managed disk parameters. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`storageAccountType`](#parameter-osdiskmanageddiskstorageaccounttype) | string | Specifies the storage account type for the managed disk. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diskEncryptionSetResourceId`](#parameter-osdiskmanageddiskdiskencryptionsetresourceid) | string | Specifies the customer managed disk encryption set resource id for the managed disk. | + +### Parameter: `osDisk.managedDisk.storageAccountType` + +Specifies the storage account type for the managed disk. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Premium_LRS' + 'Premium_ZRS' + 'PremiumV2_LRS' + 'Standard_LRS' + 'StandardSSD_LRS' + 'StandardSSD_ZRS' + 'UltraSSD_LRS' + ] + ``` + +### Parameter: `osDisk.managedDisk.diskEncryptionSetResourceId` + +Specifies the customer managed disk encryption set resource id for the managed disk. + +- Required: No +- Type: string + +### Parameter: `osDisk.caching` + +Specifies the caching requirements. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'None' + 'ReadOnly' + 'ReadWrite' + ] + ``` + +### Parameter: `osDisk.createOption` + +Specifies how the virtual machine should be created. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Attach' + 'Empty' + 'FromImage' + ] + ``` + +### Parameter: `osDisk.deleteOption` + +Specifies whether data disk should be deleted or detached upon VM deletion. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Delete' + 'Detach' + ] + ``` + +### Parameter: `osDisk.name` + +The disk name. + +- Required: No +- Type: string + ### Parameter: `osType` The chosen OS type. @@ -3177,7 +3312,133 @@ Specifies the data disks. For security reasons, it is recommended to specify Dis - Required: No - Type: array -- Default: `[]` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diskSizeGB`](#parameter-datadisksdisksizegb) | int | Specifies the size of an empty data disk in gigabytes. | +| [`managedDisk`](#parameter-datadisksmanageddisk) | object | The managed disk parameters. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`caching`](#parameter-datadiskscaching) | string | Specifies the caching requirements. | +| [`createOption`](#parameter-datadiskscreateoption) | string | Specifies how the virtual machine should be created. | +| [`deleteOption`](#parameter-datadisksdeleteoption) | string | Specifies whether data disk should be deleted or detached upon VM deletion. | +| [`lun`](#parameter-datadiskslun) | int | Specifies the logical unit number of the data disk. | +| [`name`](#parameter-datadisksname) | string | The disk name. | + +### Parameter: `dataDisks.diskSizeGB` + +Specifies the size of an empty data disk in gigabytes. + +- Required: Yes +- Type: int + +### Parameter: `dataDisks.managedDisk` + +The managed disk parameters. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`storageAccountType`](#parameter-datadisksmanageddiskstorageaccounttype) | string | Specifies the storage account type for the managed disk. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diskEncryptionSetResourceId`](#parameter-datadisksmanageddiskdiskencryptionsetresourceid) | string | Specifies the customer managed disk encryption set resource id for the managed disk. | + +### Parameter: `dataDisks.managedDisk.storageAccountType` + +Specifies the storage account type for the managed disk. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Premium_LRS' + 'Premium_ZRS' + 'PremiumV2_LRS' + 'Standard_LRS' + 'StandardSSD_LRS' + 'StandardSSD_ZRS' + 'UltraSSD_LRS' + ] + ``` + +### Parameter: `dataDisks.managedDisk.diskEncryptionSetResourceId` + +Specifies the customer managed disk encryption set resource id for the managed disk. + +- Required: No +- Type: string + +### Parameter: `dataDisks.caching` + +Specifies the caching requirements. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'None' + 'ReadOnly' + 'ReadWrite' + ] + ``` + +### Parameter: `dataDisks.createOption` + +Specifies how the virtual machine should be created. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Attach' + 'Empty' + 'FromImage' + ] + ``` + +### Parameter: `dataDisks.deleteOption` + +Specifies whether data disk should be deleted or detached upon VM deletion. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Delete' + 'Detach' + ] + ``` + +### Parameter: `dataDisks.lun` + +Specifies the logical unit number of the data disk. + +- Required: No +- Type: int + +### Parameter: `dataDisks.name` + +The disk name. + +- Required: No +- Type: string ### Parameter: `dedicatedHostId` diff --git a/avm/res/compute/virtual-machine/extension/main.json b/avm/res/compute/virtual-machine/extension/main.json index 73e9c680a6..230becd81b 100644 --- a/avm/res/compute/virtual-machine/extension/main.json +++ b/avm/res/compute/virtual-machine/extension/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.25.53.49325", - "templateHash": "5949079428725139834" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", diff --git a/avm/res/compute/virtual-machine/main.bicep b/avm/res/compute/virtual-machine/main.bicep index 1fe7b22def..4d5e48280a 100644 --- a/avm/res/compute/virtual-machine/main.bicep +++ b/avm/res/compute/virtual-machine/main.bicep @@ -30,10 +30,10 @@ param imageReference object param plan object = {} @description('Required. Specifies the OS disk. For security reasons, it is recommended to specify DiskEncryptionSet into the osDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs.') -param osDisk object +param osDisk osDiskType @description('Optional. Specifies the data disks. For security reasons, it is recommended to specify DiskEncryptionSet into the dataDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs.') -param dataDisks array = [] +param dataDisks dataDisksType @description('Optional. The flag that enables or disables a capability to have one or more managed data disks with UltraSSD_LRS storage account type on the VM or VMSS. Managed disks with storage account type UltraSSD_LRS can be added to a virtual machine or virtual machine scale set only if this property is enabled.') param ultraSSDEnabled bool = false @@ -474,35 +474,29 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-11-01' = { storageProfile: { imageReference: imageReference osDisk: { - name: '${name}-disk-os-01' - createOption: contains(osDisk, 'createOption') ? osDisk.createOption : 'FromImage' - deleteOption: contains(osDisk, 'deleteOption') ? osDisk.deleteOption : 'Delete' + name: osDisk.?name ?? '${name}-disk-os-01' + createOption: osDisk.?createOption ?? 'FromImage' + deleteOption: osDisk.?deleteOption ?? 'Delete' diskSizeGB: osDisk.diskSizeGB - caching: contains(osDisk, 'caching') ? osDisk.caching : 'ReadOnly' + caching: osDisk.?caching ?? 'ReadOnly' managedDisk: { storageAccountType: osDisk.managedDisk.storageAccountType - diskEncryptionSet: contains(osDisk.managedDisk, 'diskEncryptionSet') - ? { - id: osDisk.managedDisk.diskEncryptionSet.id - } - : null + diskEncryptionSet: { + id: osDisk.managedDisk.?diskEncryptionSetResourceId + } } } dataDisks: [ - for (dataDisk, index) in dataDisks: { - lun: index - name: '${name}-disk-data-${padLeft((index + 1), 2, '0')}' + for (dataDisk, index) in dataDisks ?? []: { + lun: dataDisk.?lun ?? index + name: dataDisk.?name ?? '${name}-disk-data-${padLeft((index + 1), 2, '0')}' diskSizeGB: dataDisk.diskSizeGB - createOption: contains(dataDisk, 'createOption') ? dataDisk.createOption : 'Empty' - deleteOption: contains(dataDisk, 'deleteOption') ? dataDisk.deleteOption : 'Delete' - caching: contains(dataDisk, 'caching') ? dataDisk.caching : 'ReadOnly' + createOption: dataDisk.?createoption ?? 'Empty' + deleteOption: dataDisk.?deleteOption ?? 'Delete' + caching: dataDisk.?caching ?? 'ReadOnly' managedDisk: { storageAccountType: dataDisk.managedDisk.storageAccountType - diskEncryptionSet: contains(dataDisk.managedDisk, 'diskEncryptionSet') - ? { - id: dataDisk.managedDisk.diskEncryptionSet.id - } - : null + diskEncryptionSet: dataDisk.?managedDisk.?diskEncryptionSet ?? null } } ] @@ -552,6 +546,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2022-11-01' = { : null priority: priority evictionPolicy: enableEvictionPolicy ? 'Deallocate' : null + #disable-next-line BCP036 billingProfile: !empty(priority) && !empty(maxPriceForLowPriorityVm) ? { maxPrice: json(maxPriceForLowPriorityVm) @@ -1060,3 +1055,74 @@ type roleAssignmentType = { @description('Optional. The Resource Id of the delegated managed identity resource.') delegatedManagedIdentityResourceId: string? }[]? + +type osDiskType = { + @description('Optional. The disk name.') + name: string? + + @description('Required. Specifies the size of an empty data disk in gigabytes.') + @maxValue(1023) + diskSizeGB: int + + @description('Optional. Specifies how the virtual machine should be created.') + createOption: 'Attach' | 'Empty' | 'FromImage'? + + @description('Optional. Specifies whether data disk should be deleted or detached upon VM deletion.') + deleteOption: 'Delete' | 'Detach'? + + @description('Optional. Specifies the caching requirements.') + caching: 'None' | 'ReadOnly' | 'ReadWrite'? + + @description('Required. The managed disk parameters.') + managedDisk: { + @description('Required. Specifies the storage account type for the managed disk.') + storageAccountType: + | 'PremiumV2_LRS' + | 'Premium_LRS' + | 'Premium_ZRS' + | 'StandardSSD_LRS' + | 'StandardSSD_ZRS' + | 'Standard_LRS' + | 'UltraSSD_LRS' + + @description('Optional. Specifies the customer managed disk encryption set resource id for the managed disk.') + diskEncryptionSetResourceId: string? + } +} + +type dataDisksType = { + @description('Optional. The disk name.') + name: string? + + @description('Optional. Specifies the logical unit number of the data disk.') + lun: int? + + @description('Required. Specifies the size of an empty data disk in gigabytes.') + @maxValue(1023) + diskSizeGB: int + + @description('Optional. Specifies how the virtual machine should be created.') + createOption: 'Attach' | 'Empty' | 'FromImage'? + + @description('Optional. Specifies whether data disk should be deleted or detached upon VM deletion.') + deleteOption: 'Delete' | 'Detach'? + + @description('Optional. Specifies the caching requirements.') + caching: 'None' | 'ReadOnly' | 'ReadWrite'? + + @description('Required. The managed disk parameters.') + managedDisk: { + @description('Required. Specifies the storage account type for the managed disk.') + storageAccountType: + | 'PremiumV2_LRS' + | 'Premium_LRS' + | 'Premium_ZRS' + | 'StandardSSD_LRS' + | 'StandardSSD_ZRS' + | 'Standard_LRS' + | 'UltraSSD_LRS' + + @description('Optional. Specifies the customer managed disk encryption set resource id for the managed disk.') + diskEncryptionSetResourceId: string? + } +}[]? diff --git a/avm/res/compute/virtual-machine/main.json b/avm/res/compute/virtual-machine/main.json index 5d9c60bd68..0d19a77932 100644 --- a/avm/res/compute/virtual-machine/main.json +++ b/avm/res/compute/virtual-machine/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "5410471245439722622" + "version": "0.26.170.59819", + "templateHash": "15754009504409627537" }, "name": "Virtual Machines", "description": "This module deploys a Virtual Machine with one or multiple NICs and optionally one or multiple public IPs.", @@ -126,6 +126,185 @@ } }, "nullable": true + }, + "osDiskType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name." + } + }, + "diskSizeGB": { + "type": "int", + "maxValue": 1023, + "metadata": { + "description": "Required. Specifies the size of an empty data disk in gigabytes." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "metadata": { + "description": "Required. Specifies the storage account type for the managed disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + } + } + }, + "dataDisksType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name." + } + }, + "lun": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the logical unit number of the data disk." + } + }, + "diskSizeGB": { + "type": "int", + "maxValue": 1023, + "metadata": { + "description": "Required. Specifies the size of an empty data disk in gigabytes." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "metadata": { + "description": "Required. Specifies the storage account type for the managed disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -190,14 +369,13 @@ } }, "osDisk": { - "type": "object", + "$ref": "#/definitions/osDiskType", "metadata": { "description": "Required. Specifies the OS disk. For security reasons, it is recommended to specify DiskEncryptionSet into the osDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." } }, "dataDisks": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/dataDisksType", "metadata": { "description": "Optional. Specifies the data disks. For security reasons, it is recommended to specify DiskEncryptionSet into the dataDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." } @@ -749,31 +927,33 @@ "copy": [ { "name": "dataDisks", - "count": "[length(parameters('dataDisks'))]", + "count": "[length(coalesce(parameters('dataDisks'), createArray()))]", "input": { - "lun": "[copyIndex('dataDisks')]", - "name": "[format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0'))]", - "diskSizeGB": "[parameters('dataDisks')[copyIndex('dataDisks')].diskSizeGB]", - "createOption": "[if(contains(parameters('dataDisks')[copyIndex('dataDisks')], 'createOption'), parameters('dataDisks')[copyIndex('dataDisks')].createOption, 'Empty')]", - "deleteOption": "[if(contains(parameters('dataDisks')[copyIndex('dataDisks')], 'deleteOption'), parameters('dataDisks')[copyIndex('dataDisks')].deleteOption, 'Delete')]", - "caching": "[if(contains(parameters('dataDisks')[copyIndex('dataDisks')], 'caching'), parameters('dataDisks')[copyIndex('dataDisks')].caching, 'ReadOnly')]", + "lun": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'lun'), copyIndex('dataDisks'))]", + "name": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))]", + "diskSizeGB": "[coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].diskSizeGB]", + "createOption": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'createoption'), 'Empty')]", + "deleteOption": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'deleteOption'), 'Delete')]", + "caching": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'caching'), 'ReadOnly')]", "managedDisk": { - "storageAccountType": "[parameters('dataDisks')[copyIndex('dataDisks')].managedDisk.storageAccountType]", - "diskEncryptionSet": "[if(contains(parameters('dataDisks')[copyIndex('dataDisks')].managedDisk, 'diskEncryptionSet'), createObject('id', parameters('dataDisks')[copyIndex('dataDisks')].managedDisk.diskEncryptionSet.id), null())]" + "storageAccountType": "[coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.storageAccountType]", + "diskEncryptionSet": "[coalesce(tryGet(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'managedDisk'), 'diskEncryptionSet'), null())]" } } } ], "imageReference": "[parameters('imageReference')]", "osDisk": { - "name": "[format('{0}-disk-os-01', parameters('name'))]", - "createOption": "[if(contains(parameters('osDisk'), 'createOption'), parameters('osDisk').createOption, 'FromImage')]", - "deleteOption": "[if(contains(parameters('osDisk'), 'deleteOption'), parameters('osDisk').deleteOption, 'Delete')]", + "name": "[coalesce(tryGet(parameters('osDisk'), 'name'), format('{0}-disk-os-01', parameters('name')))]", + "createOption": "[coalesce(tryGet(parameters('osDisk'), 'createOption'), 'FromImage')]", + "deleteOption": "[coalesce(tryGet(parameters('osDisk'), 'deleteOption'), 'Delete')]", "diskSizeGB": "[parameters('osDisk').diskSizeGB]", - "caching": "[if(contains(parameters('osDisk'), 'caching'), parameters('osDisk').caching, 'ReadOnly')]", + "caching": "[coalesce(tryGet(parameters('osDisk'), 'caching'), 'ReadOnly')]", "managedDisk": { "storageAccountType": "[parameters('osDisk').managedDisk.storageAccountType]", - "diskEncryptionSet": "[if(contains(parameters('osDisk').managedDisk, 'diskEncryptionSet'), createObject('id', parameters('osDisk').managedDisk.diskEncryptionSet.id), null())]" + "diskEncryptionSet": { + "id": "[tryGet(parameters('osDisk').managedDisk, 'diskEncryptionSetResourceId')]" + } } } }, @@ -949,8 +1129,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13415879962041783892" + "version": "0.26.170.59819", + "templateHash": "7272408028164257685" } }, "definitions": { @@ -2419,8 +2599,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -2625,8 +2805,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -2827,8 +3007,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -3041,8 +3221,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -3239,8 +3419,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -3436,8 +3616,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -3637,8 +3817,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -3846,8 +4026,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -4047,8 +4227,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -4246,8 +4426,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -4456,8 +4636,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -4656,8 +4836,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16251967456691023698" + "version": "0.26.170.59819", + "templateHash": "8063436714999902780" }, "name": "Virtual Machine Extensions", "description": "This module deploys a Virtual Machine Extension.", @@ -4855,8 +5035,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "5385249890312845255" + "version": "0.26.170.59819", + "templateHash": "2393851439891433510" }, "name": "Recovery Service Vaults Protection Container Protected Item", "description": "This module deploys a Recovery Services Vault Protection Container Protected Item.", diff --git a/avm/res/compute/virtual-machine/tests/e2e/atmg/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/atmg/main.test.bicep index 419c52c46d..47e5049f6d 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/atmg/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/atmg/main.test.bicep @@ -20,6 +20,10 @@ param serviceShort string = 'cvmlinatmg' @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' +// Set to fixed location as the RP function returns unsupported locations (configurationProfileAssignments) +// Right now (2024/04) the following locations are supported: centralus, eastus, eastus2, southcentralus, westus, westus2, westcentralus, northeurope, westeurope, canadacentral, japaneast, uksouth, australiasoutheast, australiaeast, southeastasia, westus3 +param enforcedLocation string = 'westeurope' + // ============ // // Dependencies // // ============ // @@ -33,9 +37,9 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { - location: resourceLocation + location: enforcedLocation virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' sshDeploymentScriptName: 'dep-${namePrefix}-ds-${serviceShort}' sshKeyName: 'dep-${namePrefix}-ssh-${serviceShort}' @@ -56,9 +60,9 @@ module nestedDependencies 'dependencies.bicep' = { module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { - location: resourceLocation + location: enforcedLocation name: '${namePrefix}${serviceShort}' adminUsername: 'localAdminUser' imageReference: { @@ -88,7 +92,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } diff --git a/avm/res/compute/virtual-machine/tests/e2e/linux.defaults/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/linux.defaults/main.test.bicep index f1547f9b26..6f646b2f4b 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/linux.defaults/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/linux.defaults/main.test.bicep @@ -83,7 +83,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 caching: 'ReadWrite' managedDisk: { storageAccountType: 'Premium_LRS' diff --git a/avm/res/compute/virtual-machine/tests/e2e/linux.max/dependencies.bicep b/avm/res/compute/virtual-machine/tests/e2e/linux.max/dependencies.bicep index b45e84c615..594807efaf 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/linux.max/dependencies.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/linux.max/dependencies.bicep @@ -88,7 +88,9 @@ resource loadBalancer 'Microsoft.Network/loadBalancers@2023-04-01' = { { name: 'privateIPConfig1' properties: { - subnet: virtualNetwork.properties.subnets[0] + subnet: { + id: virtualNetwork.properties.subnets[0].id + } } } ] @@ -218,10 +220,10 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } resource backupServiceKeyVaultPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid('${keyVault.name}-${location}-268f6a53-9f68-4a38-ae47-166f730d86af-KeyVault-KeyVaultAdministrator-RoleAssignment') + name: guid('${keyVault.name}-${location}-94d1f9d9-9caf-4e06-809e-29fc95f69e65-KeyVault-KeyVaultAdministrator-RoleAssignment') scope: keyVault properties: { - principalId: '268f6a53-9f68-4a38-ae47-166f730d86af' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) + principalId: '94d1f9d9-9caf-4e06-809e-29fc95f69e65' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483' diff --git a/avm/res/compute/virtual-machine/tests/e2e/linux.max/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/linux.max/main.test.bicep index 5bc3497300..4fa2f77428 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/linux.max/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/linux.max/main.test.bicep @@ -154,10 +154,11 @@ module testDeployment '../../../main.bicep' = { } ] osDisk: { + name: 'osdisk01' caching: 'ReadOnly' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -170,19 +171,21 @@ module testDeployment '../../../main.bicep' = { backupVaultResourceGroup: nestedDependencies.outputs.recoveryServicesVaultResourceGroupName dataDisks: [ { + name: 'datadisk01' caching: 'ReadWrite' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } } { + name: 'datadisk02' caching: 'ReadWrite' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } diff --git a/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/dependencies.bicep index 8e1705682c..f9979cbc71 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/dependencies.bicep @@ -85,7 +85,9 @@ resource loadBalancer 'Microsoft.Network/loadBalancers@2023-04-01' = { { name: 'privateIPConfig1' properties: { - subnet: virtualNetwork.properties.subnets[0] + subnet: { + id: virtualNetwork.properties.subnets[0].id + } } } ] @@ -215,10 +217,10 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } resource backupServiceKeyVaultPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid('${keyVault.name}-${location}-268f6a53-9f68-4a38-ae47-166f730d86af-KeyVault-KeyVaultAdministrator-RoleAssignment') + name: guid('${keyVault.name}-${location}-94d1f9d9-9caf-4e06-809e-29fc95f69e65-KeyVault-KeyVaultAdministrator-RoleAssignment') scope: keyVault properties: { - principalId: '268f6a53-9f68-4a38-ae47-166f730d86af' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) + principalId: '94d1f9d9-9caf-4e06-809e-29fc95f69e65' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483' diff --git a/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/main.test.bicep index 76cd0f2393..4090a9830a 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/waf-aligned/main.test.bicep @@ -160,9 +160,9 @@ module testDeployment '../../../main.bicep' = [ ] osDisk: { caching: 'ReadWrite' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -179,7 +179,7 @@ module testDeployment '../../../main.bicep' = [ caching: 'ReadOnly' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -188,7 +188,7 @@ module testDeployment '../../../main.bicep' = [ caching: 'ReadOnly' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.defaults/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.defaults/main.test.bicep index 0322bf0a51..5e6f6365dd 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.defaults/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.defaults/main.test.bicep @@ -75,7 +75,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 caching: 'ReadWrite' managedDisk: { storageAccountType: 'Premium_LRS' diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.guestconfiguration/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.guestconfiguration/main.test.bicep index 36db7d031d..c676412329 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.guestconfiguration/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.guestconfiguration/main.test.bicep @@ -78,7 +78,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 caching: 'ReadWrite' managedDisk: { storageAccountType: 'Premium_LRS' diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.hostpool/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.hostpool/main.test.bicep index 5a55be165f..1c198d0c69 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.hostpool/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.hostpool/main.test.bicep @@ -24,6 +24,10 @@ param password string = newGuid() @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' +// Set to fixed location as the RP function returns unsupported locations +// Right now (2024/04) the following locations are supported: centralindia,uksouth,ukwest,japaneast,australiaeast,canadaeast,canadacentral,northeurope,westeurope,eastus,eastus2,westus,westus2,westus3,northcentralus,southcentralus,westcentralus,centralus +param enforcedLocation string = 'westeurope' + // ============ // // Dependencies // // ============ // @@ -37,9 +41,9 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { - location: resourceLocation + location: enforcedLocation virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' hostPoolName: 'dep${namePrefix}-hp-${serviceShort}01' @@ -56,7 +60,7 @@ module testDeployment '../../../main.bicep' = [ scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { - location: resourceLocation + location: enforcedLocation name: '${namePrefix}${serviceShort}' adminUsername: 'localAdminUser' managedIdentities: { @@ -81,7 +85,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 caching: 'ReadWrite' managedDisk: { storageAccountType: 'Premium_LRS' diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.max/dependencies.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.max/dependencies.bicep index 8e1705682c..f9979cbc71 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.max/dependencies.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.max/dependencies.bicep @@ -85,7 +85,9 @@ resource loadBalancer 'Microsoft.Network/loadBalancers@2023-04-01' = { { name: 'privateIPConfig1' properties: { - subnet: virtualNetwork.properties.subnets[0] + subnet: { + id: virtualNetwork.properties.subnets[0].id + } } } ] @@ -215,10 +217,10 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } resource backupServiceKeyVaultPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid('${keyVault.name}-${location}-268f6a53-9f68-4a38-ae47-166f730d86af-KeyVault-KeyVaultAdministrator-RoleAssignment') + name: guid('${keyVault.name}-${location}-94d1f9d9-9caf-4e06-809e-29fc95f69e65-KeyVault-KeyVaultAdministrator-RoleAssignment') scope: keyVault properties: { - principalId: '268f6a53-9f68-4a38-ae47-166f730d86af' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) + principalId: '94d1f9d9-9caf-4e06-809e-29fc95f69e65' // Backup Management Service Enterprise Application Object Id (Note: this is tenant specific) roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483' diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.max/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.max/main.test.bicep index 01c03b31dd..633d6116f2 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.max/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.max/main.test.bicep @@ -159,10 +159,11 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { + name: 'osdisk01' caching: 'ReadWrite' - createOption: 'fromImage' + createOption: 'FromImage' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } @@ -176,19 +177,23 @@ module testDeployment '../../../main.bicep' = [ backupVaultResourceGroup: nestedDependencies.outputs.recoveryServicesVaultResourceGroupName dataDisks: [ { + name: 'datadisk01' + lun: 0 caching: 'None' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } } { + name: 'datadisk02' + lun: 1 caching: 'None' createOption: 'Empty' deleteOption: 'Delete' - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' } diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.nvidia/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.nvidia/main.test.bicep index 40778baf54..603cf8c4fc 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.nvidia/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.nvidia/main.test.bicep @@ -12,7 +12,7 @@ metadata description = 'This instance deploys the module for a VM with dedicated param resourceGroupName string = 'dep-${namePrefix}-compute.virtualMachines-${serviceShort}-rg' @description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location +param resourceLocation string = 'eastus' @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 = 'cvmwinnvidia' @@ -24,7 +24,7 @@ param password string = newGuid() @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' -#disable-next-line no-hardcoded-location // Due to quotas and capacity challenges, this region must be ued in the AVM testing subscription +#disable-next-line no-hardcoded-location // Due to quotas and capacity challenges, this region must be used in the AVM testing subscription var tempLocation = 'eastus' // ============ // @@ -35,7 +35,7 @@ var tempLocation = 'eastus' // ================= resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: tempLocation + location: resourceLocation } module nestedDependencies 'dependencies.bicep' = { @@ -78,7 +78,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 caching: 'ReadWrite' managedDisk: { storageAccountType: 'Premium_LRS' diff --git a/avm/res/compute/virtual-machine/tests/e2e/windows.ssecmk/main.test.bicep b/avm/res/compute/virtual-machine/tests/e2e/windows.ssecmk/main.test.bicep index e0d0b19cbc..cffd564ce1 100644 --- a/avm/res/compute/virtual-machine/tests/e2e/windows.ssecmk/main.test.bicep +++ b/avm/res/compute/virtual-machine/tests/e2e/windows.ssecmk/main.test.bicep @@ -81,7 +81,7 @@ module testDeployment '../../../main.bicep' = [ } ] osDisk: { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' diskEncryptionSet: { @@ -94,7 +94,7 @@ module testDeployment '../../../main.bicep' = [ adminPassword: password dataDisks: [ { - diskSizeGB: '128' + diskSizeGB: 128 managedDisk: { storageAccountType: 'Premium_LRS' diskEncryptionSet: { From 46d965bc80e86e13c5c39aef9a204a308ce62e44 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:37:20 +0200 Subject: [PATCH 06/32] fix: pwsh publish module index (#1683) ## Description Removing an unnecessary `-Force` and unrequired `SupportsShouldProcess` from the `Invoke-AvmJsonModuleIndexGeneration.ps1` ## Type of Change - [x] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- .github/workflows/avm.platform.publish-module-index-json.yml | 2 +- .../pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/avm.platform.publish-module-index-json.yml b/.github/workflows/avm.platform.publish-module-index-json.yml index d82f31317e..a8f2076487 100644 --- a/.github/workflows/avm.platform.publish-module-index-json.yml +++ b/.github/workflows/avm.platform.publish-module-index-json.yml @@ -51,7 +51,7 @@ jobs: Write-Verbose "Invoke task with" -Verbose Write-Verbose ($functionInput | ConvertTo-Json | Out-String) -Verbose - if(-not (Invoke-AvmJsonModuleIndexGeneration @functionInput -Force)) { + if(-not (Invoke-AvmJsonModuleIndexGeneration @functionInput)) { Write-Output ('{0}={1}' -f 'anyErrorsOccurred', $true) >> $env:GITHUB_ENV } diff --git a/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 index 49a58895c5..c96eb49f15 100644 --- a/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 +++ b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 @@ -40,7 +40,6 @@ The function requires Azure PowerShell Storage Module (Az.Storage) to be install #> function Invoke-AvmJsonModuleIndexGeneration { - [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $false)] [string] $storageAccountName = 'biceplivedatasaprod', From 025b862962f5b1d49519aaf34f85f1f0a087b052 Mon Sep 17 00:00:00 2001 From: Kris Baranek Date: Wed, 17 Apr 2024 09:20:59 +0200 Subject: [PATCH 07/32] fix: WAF reliability and static validation in `avm/res/web/site` (#1427) ## Description - added PSRule exceptions for `defaults` tests - removed optional parameters from `defaults` tests - re-generated readme due to static validation errors fixes #1378 fixes #1510 fixes #1592 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.web.site](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=users%2Fkrbar%2FwebSiteFix)](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/web/site/README.md | 24 +++++-------------- avm/res/web/site/main.bicep | 4 +++- avm/res/web/site/main.json | 12 ++++++---- avm/res/web/site/slot/README.md | 6 +++++ avm/res/web/site/slot/main.bicep | 4 +++- avm/res/web/site/slot/main.json | 6 +++-- .../e2e/functionApp.defaults/main.test.bicep | 3 --- .../tests/e2e/webApp.defaults/main.test.bicep | 4 ---- .../psrule/.ps-rule/min-suppress.Rule.yaml | 3 +++ 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/avm/res/web/site/README.md b/avm/res/web/site/README.md index a1d528472c..fd8403c4f3 100644 --- a/avm/res/web/site/README.md +++ b/avm/res/web/site/README.md @@ -65,9 +65,6 @@ module site 'br/public:avm/res/web/site:' = { serverFarmResourceId: '' // Non-required parameters location: '' - siteConfig: { - alwaysOn: true - } } } ``` @@ -97,11 +94,6 @@ module site 'br/public:avm/res/web/site:' = { // Non-required parameters "location": { "value": "" - }, - "siteConfig": { - "value": { - "alwaysOn": true - } } } } @@ -659,10 +651,6 @@ module site 'br/public:avm/res/web/site:' = { serverFarmResourceId: '' // Non-required parameters location: '' - siteConfig: { - alwaysOn: true - healthCheckPath: '/healthz' - } } } ``` @@ -692,12 +680,6 @@ module site 'br/public:avm/res/web/site:' = { // Non-required parameters "location": { "value": "" - }, - "siteConfig": { - "value": { - "alwaysOn": true, - "healthCheckPath": "/healthz" - } } } } @@ -2576,6 +2558,12 @@ The site config object. - Required: No - Type: object +- Default: + ```Bicep + { + alwaysOn: true + } + ``` ### Parameter: `slots` diff --git a/avm/res/web/site/main.bicep b/avm/res/web/site/main.bicep index c0571f9765..19fa05ed2d 100644 --- a/avm/res/web/site/main.bicep +++ b/avm/res/web/site/main.bicep @@ -56,7 +56,9 @@ param vnetRouteAllEnabled bool = false param scmSiteAlsoStopped bool = false @description('Optional. The site config object.') -param siteConfig object? +param siteConfig object = { + alwaysOn: true +} @description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') param storageAccountResourceId string? diff --git a/avm/res/web/site/main.json b/avm/res/web/site/main.json index c536503a67..3b31a486aa 100644 --- a/avm/res/web/site/main.json +++ b/avm/res/web/site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "6312828584864471342" + "templateHash": "14301920664802681394" }, "name": "Web/Function Apps", "description": "This module deploys a Web or Function App.", @@ -543,7 +543,9 @@ }, "siteConfig": { "type": "object", - "nullable": true, + "defaultValue": { + "alwaysOn": true + }, "metadata": { "description": "Optional. The site config object." } @@ -1299,7 +1301,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "12157473410186645235" + "templateHash": "7436278786647572492" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -1815,7 +1817,9 @@ }, "siteConfig": { "type": "object", - "nullable": true, + "defaultValue": { + "alwaysOn": true + }, "metadata": { "description": "Optional. The site config object." } diff --git a/avm/res/web/site/slot/README.md b/avm/res/web/site/slot/README.md index 0ee42e70e8..d6bd88486f 100644 --- a/avm/res/web/site/slot/README.md +++ b/avm/res/web/site/slot/README.md @@ -949,6 +949,12 @@ The site config object. - Required: No - Type: object +- Default: + ```Bicep + { + alwaysOn: true + } + ``` ### Parameter: `storageAccountRequired` diff --git a/avm/res/web/site/slot/main.bicep b/avm/res/web/site/slot/main.bicep index 209954c101..f5aa6cf5f1 100644 --- a/avm/res/web/site/slot/main.bicep +++ b/avm/res/web/site/slot/main.bicep @@ -47,7 +47,9 @@ param storageAccountRequired bool = false param virtualNetworkSubnetId string? @description('Optional. The site config object.') -param siteConfig object? +param siteConfig object = { + alwaysOn: true +} @description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') param storageAccountResourceId string? diff --git a/avm/res/web/site/slot/main.json b/avm/res/web/site/slot/main.json index 959db7cbac..ca03c4ee64 100644 --- a/avm/res/web/site/slot/main.json +++ b/avm/res/web/site/slot/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "12157473410186645235" + "templateHash": "7436278786647572492" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -522,7 +522,9 @@ }, "siteConfig": { "type": "object", - "nullable": true, + "defaultValue": { + "alwaysOn": true + }, "metadata": { "description": "Optional. The site config object." } diff --git a/avm/res/web/site/tests/e2e/functionApp.defaults/main.test.bicep b/avm/res/web/site/tests/e2e/functionApp.defaults/main.test.bicep index 49f241e3f0..9925860e2e 100644 --- a/avm/res/web/site/tests/e2e/functionApp.defaults/main.test.bicep +++ b/avm/res/web/site/tests/e2e/functionApp.defaults/main.test.bicep @@ -54,9 +54,6 @@ module testDeployment '../../../main.bicep' = [ location: resourceLocation kind: 'functionapp' serverFarmResourceId: nestedDependencies.outputs.serverFarmResourceId - siteConfig: { - alwaysOn: true - } } dependsOn: [ nestedDependencies diff --git a/avm/res/web/site/tests/e2e/webApp.defaults/main.test.bicep b/avm/res/web/site/tests/e2e/webApp.defaults/main.test.bicep index c4b0bce825..63bac17835 100644 --- a/avm/res/web/site/tests/e2e/webApp.defaults/main.test.bicep +++ b/avm/res/web/site/tests/e2e/webApp.defaults/main.test.bicep @@ -54,10 +54,6 @@ module testDeployment '../../../main.bicep' = [ location: resourceLocation kind: 'app' serverFarmResourceId: nestedDependencies.outputs.serverFarmResourceId - siteConfig: { - healthCheckPath: '/healthz' - alwaysOn: true - } } dependsOn: [ diff --git a/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/min-suppress.Rule.yaml b/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/min-suppress.Rule.yaml index f8bd41fc01..dbea639dbf 100644 --- a/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/min-suppress.Rule.yaml +++ b/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/min-suppress.Rule.yaml @@ -28,6 +28,9 @@ spec: # Azure Virtual Machine - Azure.VM.AMA - Azure.VM.Standalone + # Azure App Service + - Azure.AppService.WebProbe # Supressed as the probe path is specific to the app + - Azure.AppService.WebProbePath # Supressed as the probe path is specific to the app if: name: "." contains: From 58deb215ed5508b5e87b6196fcdd1469c16ee871 Mon Sep 17 00:00:00 2001 From: Joans Sandbox <44612095+Kathedra@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:25:44 +0200 Subject: [PATCH 08/32] fix: prevent updating the workspace repository configuration whenever no value is provided, so an existing configuration is not overwritten. (#1680) prevent updating the workspace repository configuration whenever no value is provided, so an existing configuration is not overwritten. ## Description ## Pipeline Reference | Pipeline | | -------- | | | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Alexander Sehr Co-authored-by: jzandijk@ilionx.com --- avm/res/synapse/workspace/README.md | 1 - avm/res/synapse/workspace/main.bicep | 2 +- avm/res/synapse/workspace/main.json | 18 +++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/avm/res/synapse/workspace/README.md b/avm/res/synapse/workspace/README.md index 62fbe376dd..80e5572b87 100644 --- a/avm/res/synapse/workspace/README.md +++ b/avm/res/synapse/workspace/README.md @@ -1590,7 +1590,6 @@ Git integration settings. - Required: No - Type: object -- Default: `{}` ## Outputs diff --git a/avm/res/synapse/workspace/main.bicep b/avm/res/synapse/workspace/main.bicep index 1e9c17624b..7546fecdd7 100644 --- a/avm/res/synapse/workspace/main.bicep +++ b/avm/res/synapse/workspace/main.bicep @@ -74,7 +74,7 @@ param sqlAdministratorLogin string param sqlAdministratorLoginPassword string = '' @description('Optional. Git integration settings.') -param workspaceRepositoryConfiguration object = {} +param workspaceRepositoryConfiguration object? @description('Optional. The managed identity definition for this resource.') param managedIdentities managedIdentitiesType diff --git a/avm/res/synapse/workspace/main.json b/avm/res/synapse/workspace/main.json index 89049892d8..27f50b2a73 100644 --- a/avm/res/synapse/workspace/main.json +++ b/avm/res/synapse/workspace/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "456170449467900103" + "version": "0.25.3.34343", + "templateHash": "14051939023718187274" }, "name": "Synapse Workspaces", "description": "This module deploys a Synapse Workspace.", @@ -587,7 +587,7 @@ }, "workspaceRepositoryConfiguration": { "type": "object", - "defaultValue": {}, + "nullable": true, "metadata": { "description": "Optional. Git integration settings." } @@ -811,8 +811,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3692817517911366371" + "version": "0.25.3.34343", + "templateHash": "10907909212229865317" }, "name": "Synapse Workspace Integration Runtimes", "description": "This module deploys a Synapse Workspace Integration Runtime.", @@ -909,8 +909,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "11661011043333744116" + "version": "0.25.3.34343", + "templateHash": "6689684157194442067" } }, "parameters": { @@ -997,8 +997,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "4754954307876642999" + "version": "0.25.3.34343", + "templateHash": "13839822979779705807" }, "name": "Synapse Workspaces Keys", "description": "This module deploys a Synapse Workspaces Key.", From a616f649dc9db0124488076dc1d0de7ecbe36a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenjie=20Yu=EF=BC=88MSFT=EF=BC=89?= <81678720+zedy-wj@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:19:17 +0800 Subject: [PATCH 09/32] feat: Add the creation of 'Microsoft.CognitiveServices/accounts/deployments' in Cognitive Service Module - `avm/res/cognitive-services/account` (#1466) ## Description Fixes #1131 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.cognitive-services.account](https://github.com/zedy-wj/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=fix%2F1131)](https://github.com/zedy-wj/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings @jongio - for notification. --------- Co-authored-by: zedy Co-authored-by: Alexander Sehr --- avm/res/cognitive-services/account/README.md | 217 +++++++++++++++++- avm/res/cognitive-services/account/main.bicep | 47 ++++ avm/res/cognitive-services/account/main.json | 98 +++++++- .../e2e/ai-model-deployment/main.test.bicep | 63 +++++ 4 files changed, 412 insertions(+), 13 deletions(-) create mode 100644 avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep diff --git a/avm/res/cognitive-services/account/README.md b/avm/res/cognitive-services/account/README.md index 587b599762..820802cdb8 100644 --- a/avm/res/cognitive-services/account/README.md +++ b/avm/res/cognitive-services/account/README.md @@ -18,6 +18,7 @@ This module deploys a Cognitive Service. | `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.CognitiveServices/accounts` | [2023-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.CognitiveServices/2023-05-01/accounts) | +| `Microsoft.CognitiveServices/accounts/deployments` | [2023-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.CognitiveServices/2023-05-01/accounts/deployments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | | `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) | @@ -30,14 +31,101 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/cognitive-services/account:`. -- [Using only defaults](#example-1-using-only-defaults) -- [Using large parameter set](#example-2-using-large-parameter-set) -- [As Speech Service](#example-3-as-speech-service) -- [Using Customer-Managed-Keys with System-Assigned identity](#example-4-using-customer-managed-keys-with-system-assigned-identity) -- [Using Customer-Managed-Keys with User-Assigned identity](#example-5-using-customer-managed-keys-with-user-assigned-identity) -- [WAF-aligned](#example-6-waf-aligned) +- [Using `deployments` in parameter set](#example-1-using-deployments-in-parameter-set) +- [Using only defaults](#example-2-using-only-defaults) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [As Speech Service](#example-4-as-speech-service) +- [Using Customer-Managed-Keys with System-Assigned identity](#example-5-using-customer-managed-keys-with-system-assigned-identity) +- [Using Customer-Managed-Keys with User-Assigned identity](#example-6-using-customer-managed-keys-with-user-assigned-identity) +- [WAF-aligned](#example-7-waf-aligned) -### Example 1: _Using only defaults_ +### Example 1: _Using `deployments` in parameter set_ + +This instance deploys the module with the AI model deployment feature. + + +

+ +via Bicep module + +```bicep +module account 'br/public:avm/res/cognitive-services/account:' = { + name: 'accountDeployment' + params: { + // Required parameters + kind: 'AIServices' + name: 'csad002' + // Non-required parameters + customSubDomainName: 'xcsadai' + deployments: [ + { + model: { + format: 'OpenAI' + name: 'gpt-35-turbo' + version: '0301' + } + name: 'gpt-35-turbo' + sku: { + capacity: 10 + name: 'Standard' + } + } + ] + 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 + "kind": { + "value": "AIServices" + }, + "name": { + "value": "csad002" + }, + // Non-required parameters + "customSubDomainName": { + "value": "xcsadai" + }, + "deployments": { + "value": [ + { + "model": { + "format": "OpenAI", + "name": "gpt-35-turbo", + "version": "0301" + }, + "name": "gpt-35-turbo", + "sku": { + "capacity": 10, + "name": "Standard" + } + } + ] + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -89,7 +177,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 2: _Using large parameter set_ +### Example 3: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -377,7 +465,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 3: _As Speech Service_ +### Example 4: _As Speech Service_ This instance deploys the module as a Speech Service. @@ -491,7 +579,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 4: _Using Customer-Managed-Keys with System-Assigned identity_ +### Example 5: _Using Customer-Managed-Keys with System-Assigned identity_ This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret. @@ -573,7 +661,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 5: _Using Customer-Managed-Keys with User-Assigned identity_ +### Example 6: _Using Customer-Managed-Keys with User-Assigned identity_ This instance deploys the module using Customer-Managed-Keys using a User-Assigned Identity to access the Customer-Managed-Key secret. @@ -661,7 +749,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 6: _WAF-aligned_ +### Example 7: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -820,6 +908,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = { | [`allowedFqdnList`](#parameter-allowedfqdnlist) | array | List of allowed FQDN. | | [`apiProperties`](#parameter-apiproperties) | object | The API properties for special APIs. | | [`customerManagedKey`](#parameter-customermanagedkey) | object | The customer managed key definition. | +| [`deployments`](#parameter-deployments) | array | Array of deployments about cognitive service accounts to create. | | [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | | [`disableLocalAuth`](#parameter-disablelocalauth) | bool | Allow only Azure AD authentication. Should be enabled for security reasons. | | [`dynamicThrottlingEnabled`](#parameter-dynamicthrottlingenabled) | bool | The flag to enable dynamic throttling. | @@ -956,6 +1045,110 @@ User assigned identity to use when fetching the customer managed key. Required i - Required: No - Type: string +### Parameter: `deployments` + +Array of deployments about cognitive service accounts to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`model`](#parameter-deploymentsmodel) | object | Properties of Cognitive Services account deployment model. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-deploymentsname) | string | Specify the name of cognitive service account deployment. | +| [`raiPolicyName`](#parameter-deploymentsraipolicyname) | string | The name of RAI policy. | +| [`sku`](#parameter-deploymentssku) | object | The resource model definition representing SKU. | + +### Parameter: `deployments.model` + +Properties of Cognitive Services account deployment model. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`format`](#parameter-deploymentsmodelformat) | string | The format of Cognitive Services account deployment model. | +| [`name`](#parameter-deploymentsmodelname) | string | The name of Cognitive Services account deployment model. | +| [`version`](#parameter-deploymentsmodelversion) | string | The version of Cognitive Services account deployment model. | + +### Parameter: `deployments.model.format` + +The format of Cognitive Services account deployment model. + +- Required: Yes +- Type: string + +### Parameter: `deployments.model.name` + +The name of Cognitive Services account deployment model. + +- Required: Yes +- Type: string + +### Parameter: `deployments.model.version` + +The version of Cognitive Services account deployment model. + +- Required: Yes +- Type: string + +### Parameter: `deployments.name` + +Specify the name of cognitive service account deployment. + +- Required: No +- Type: string + +### Parameter: `deployments.raiPolicyName` + +The name of RAI policy. + +- Required: No +- Type: string + +### Parameter: `deployments.sku` + +The resource model definition representing SKU. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-deploymentsskuname) | string | The name of the resource model definition representing SKU. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`capacity`](#parameter-deploymentsskucapacity) | int | The capacity of the resource model definition representing SKU. | + +### Parameter: `deployments.sku.name` + +The name of the resource model definition representing SKU. + +- Required: Yes +- Type: string + +### Parameter: `deployments.sku.capacity` + +The capacity of the resource model definition representing SKU. + +- Required: No +- Type: int + ### Parameter: `diagnosticSettings` The diagnostic settings of the service. diff --git a/avm/res/cognitive-services/account/main.bicep b/avm/res/cognitive-services/account/main.bicep index d88001d7f3..293821f9b8 100644 --- a/avm/res/cognitive-services/account/main.bicep +++ b/avm/res/cognitive-services/account/main.bicep @@ -126,6 +126,9 @@ param managedIdentities managedIdentitiesType @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true +@description('Optional. Array of deployments about cognitive service accounts to create.') +param deployments deploymentsType + var formattedUserAssignedIdentities = reduce( map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, @@ -339,6 +342,21 @@ resource cognitiveService 'Microsoft.CognitiveServices/accounts@2023-05-01' = { } } +@batchSize(1) +resource cognitiveService_deployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [ + for (deployment, index) in (deployments ?? []): { + parent: cognitiveService + name: deployment.?name ?? '${name}-deployments' + properties: { + model: deployment.model + raiPolicyName: deployment.?raiPolicyName + } + sku: deployment.?sku ?? { + name: sku + } + } +] + resource cognitiveService_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { name: lock.?name ?? 'lock-${name}' @@ -649,3 +667,32 @@ type lockType = { @description('Optional. Specify the type of lock.') kind: ('CanNotDelete' | 'ReadOnly' | 'None')? }? + +type deploymentsType = { + @description('Optional. Specify the name of cognitive service account deployment.') + name: string? + + @description('Required. Properties of Cognitive Services account deployment model.') + model: { + @description('Required. The name of Cognitive Services account deployment model.') + name: string + + @description('Required. The format of Cognitive Services account deployment model.') + format: string + + @description('Required. The version of Cognitive Services account deployment model.') + version: string + } + + @description('Optional. The resource model definition representing SKU.') + sku: { + @description('Required. The name of the resource model definition representing SKU.') + name: string + + @description('Optional. The capacity of the resource model definition representing SKU.') + capacity: int? + }? + + @description('Optional. The name of RAI policy.') + raiPolicyName: string? +}[]? diff --git a/avm/res/cognitive-services/account/main.json b/avm/res/cognitive-services/account/main.json index db28c215b9..7c8c756cb7 100644 --- a/avm/res/cognitive-services/account/main.json +++ b/avm/res/cognitive-services/account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "1728463055074429069" + "templateHash": "2360701930449304487" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service.", @@ -475,6 +475,77 @@ } }, "nullable": true + }, + "deploymentsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of cognitive service account deployment." + } + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account deployment model." + } + }, + "format": { + "type": "string", + "metadata": { + "description": "Required. The format of Cognitive Services account deployment model." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. The version of Cognitive Services account deployment model." + } + } + }, + "metadata": { + "description": "Required. Properties of Cognitive Services account deployment model." + } + }, + "sku": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource model definition representing SKU." + } + }, + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the resource model definition representing SKU." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource model definition representing SKU." + } + }, + "raiPolicyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of RAI policy." + } + } + } + }, + "nullable": true } }, "parameters": { @@ -685,6 +756,12 @@ "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "deployments": { + "$ref": "#/definitions/deploymentsType", + "metadata": { + "description": "Optional. Array of deployments about cognitive service accounts to create." + } } }, "variables": { @@ -803,6 +880,25 @@ "cMKUserAssignedIdentity" ] }, + "cognitiveService_deployments": { + "copy": { + "name": "cognitiveService_deployments", + "count": "[length(coalesce(parameters('deployments'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'name'), format('{0}-deployments', parameters('name'))))]", + "properties": { + "model": "[coalesce(parameters('deployments'), createArray())[copyIndex()].model]", + "raiPolicyName": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'raiPolicyName')]" + }, + "sku": "[coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'sku'), createObject('name', parameters('sku')))]", + "dependsOn": [ + "cognitiveService" + ] + }, "cognitiveService_lock": { "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", "type": "Microsoft.Authorization/locks", diff --git a/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep new file mode 100644 index 0000000000..7ee3f5be51 --- /dev/null +++ b/avm/res/cognitive-services/account/tests/e2e/ai-model-deployment/main.test.bicep @@ -0,0 +1,63 @@ +targetScope = 'subscription' + +metadata name = 'Using `deployments` in parameter set' +metadata description = 'This instance deploys the module with the AI model deployment feature.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-cognitiveservices.accounts-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'csad' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}-ai' + params: { + name: '${namePrefix}${serviceShort}002' + kind: 'AIServices' + location: resourceLocation + customSubDomainName: '${namePrefix}x${serviceShort}ai' + deployments: [ + { + name: 'gpt-35-turbo' + model: { + format: 'OpenAI' + name: 'gpt-35-turbo' + version: '0301' + } + sku: { + name: 'Standard' + capacity: 10 + } + } + ] + } + } +] From b5d76b9c9931ba130db315643edcfc064e855bd2 Mon Sep 17 00:00:00 2001 From: Zach Trocinski <30884663+oZakari@users.noreply.github.com> Date: Wed, 17 Apr 2024 03:50:38 -0500 Subject: [PATCH 10/32] fix: Align container-app to WAF Reliability requirements (#1689) ## Description For container-apps, set the scaleMinReplicas to 3 & set scaleMaxReplicas to 10 to align to WAF Reliability Review. Closes #1188 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.app.container-app](https://github.com/oZakari/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=container-app-reliability-review)](https://github.com/oZakari/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Maher Aldineh Co-authored-by: Maher Aldineh --- avm/res/app/container-app/README.md | 8 ++++---- avm/res/app/container-app/main.bicep | 6 +++--- avm/res/app/container-app/main.json | 10 +++++----- avm/res/app/container-app/version.json | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/avm/res/app/container-app/README.md b/avm/res/app/container-app/README.md index d55ef832d9..236de18344 100644 --- a/avm/res/app/container-app/README.md +++ b/avm/res/app/container-app/README.md @@ -481,7 +481,7 @@ module containerApp 'br/public:avm/res/app/container-app:' = { | [`revisionSuffix`](#parameter-revisionsuffix) | string | User friendly suffix that is appended to the revision name. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`scaleMaxReplicas`](#parameter-scalemaxreplicas) | int | Maximum number of container replicas. Defaults to 10 if not set. | -| [`scaleMinReplicas`](#parameter-scaleminreplicas) | int | Minimum number of container replicas. | +| [`scaleMinReplicas`](#parameter-scaleminreplicas) | int | Minimum number of container replicas. Defaults to 3 if not set. | | [`scaleRules`](#parameter-scalerules) | array | Scaling rules. | | [`secrets`](#parameter-secrets) | secureObject | The secrets of the Container App. | | [`tags`](#parameter-tags) | object | Tags of the resource. | @@ -808,15 +808,15 @@ Maximum number of container replicas. Defaults to 10 if not set. - Required: No - Type: int -- Default: `1` +- Default: `10` ### Parameter: `scaleMinReplicas` -Minimum number of container replicas. +Minimum number of container replicas. Defaults to 3 if not set. - Required: No - Type: int -- Default: `0` +- Default: `3` ### Parameter: `scaleRules` diff --git a/avm/res/app/container-app/main.bicep b/avm/res/app/container-app/main.bicep index 744ad3411a..7ef6ed7246 100644 --- a/avm/res/app/container-app/main.bicep +++ b/avm/res/app/container-app/main.bicep @@ -27,10 +27,10 @@ param ingressAllowInsecure bool = true param ingressTargetPort int = 80 @description('Optional. Maximum number of container replicas. Defaults to 10 if not set.') -param scaleMaxReplicas int = 1 +param scaleMaxReplicas int = 10 -@description('Optional. Minimum number of container replicas.') -param scaleMinReplicas int = 0 +@description('Optional. Minimum number of container replicas. Defaults to 3 if not set.') +param scaleMinReplicas int = 3 @description('Optional. Scaling rules.') param scaleRules array = [] diff --git a/avm/res/app/container-app/main.json b/avm/res/app/container-app/main.json index 7ec3817453..97c1bf140a 100644 --- a/avm/res/app/container-app/main.json +++ b/avm/res/app/container-app/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2491578115600463169" + "version": "0.26.170.59819", + "templateHash": "11829581770848647649" }, "name": "Container Apps", "description": "This module deploys a Container App.", @@ -178,16 +178,16 @@ }, "scaleMaxReplicas": { "type": "int", - "defaultValue": 1, + "defaultValue": 10, "metadata": { "description": "Optional. Maximum number of container replicas. Defaults to 10 if not set." } }, "scaleMinReplicas": { "type": "int", - "defaultValue": 0, + "defaultValue": 3, "metadata": { - "description": "Optional. Minimum number of container replicas." + "description": "Optional. Minimum number of container replicas. Defaults to 3 if not set." } }, "scaleRules": { diff --git a/avm/res/app/container-app/version.json b/avm/res/app/container-app/version.json index 7fa401bdf7..9481fea58e 100644 --- a/avm/res/app/container-app/version.json +++ b/avm/res/app/container-app/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.1", + "version": "0.2", "pathFilters": [ "./main.json" ] From 3ef0a9b78b1b96cd0ab7d96b5b347fa28edf566a Mon Sep 17 00:00:00 2001 From: Richard Hooper Date: Wed, 17 Apr 2024 20:56:05 +0100 Subject: [PATCH 11/32] feat: Enable cost analysis add-on in managed cluster module (#1682) ## Description This pull request adds the ability to enable the newly GA cost analysis feature of AKS. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.container-service.managed-cluster](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg)](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Signed-off-by: PixelRobots <22979170+PixelRobots@users.noreply.github.com> --- avm/res/container-service/managed-cluster/README.md | 9 +++++++++ avm/res/container-service/managed-cluster/main.bicep | 10 +++++++++- avm/res/container-service/managed-cluster/main.json | 11 +++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/avm/res/container-service/managed-cluster/README.md b/avm/res/container-service/managed-cluster/README.md index 9c6755ddb5..e5154ea6db 100644 --- a/avm/res/container-service/managed-cluster/README.md +++ b/avm/res/container-service/managed-cluster/README.md @@ -1501,6 +1501,7 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster: Date: Thu, 18 Apr 2024 00:41:35 +0200 Subject: [PATCH 12/32] fix: Unorphaned res\insights\activity-log-alert module as @donk-msft recently became the owner of this module. (#1692) ## Description New owner to orphaned module changes. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.insights.activity-log-alert](https://github.com/donk-msft/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml/badge.svg?branch=feat-avm%2Fres%2Finsights%2Factivity-log-alert)](https://github.com/donk-msft/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml) | ## Type of Change - [X] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings Co-authored-by: DonKoning --- avm/res/insights/activity-log-alert/ORPHANED.md | 4 ---- avm/res/insights/activity-log-alert/README.md | 5 ----- 2 files changed, 9 deletions(-) delete mode 100644 avm/res/insights/activity-log-alert/ORPHANED.md diff --git a/avm/res/insights/activity-log-alert/ORPHANED.md b/avm/res/insights/activity-log-alert/ORPHANED.md deleted file mode 100644 index ef8fa911d2..0000000000 --- a/avm/res/insights/activity-log-alert/ORPHANED.md +++ /dev/null @@ -1,4 +0,0 @@ -⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ - -- Only security and bug fixes are being handled by the AVM core team at present. -- If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! \ No newline at end of file diff --git a/avm/res/insights/activity-log-alert/README.md b/avm/res/insights/activity-log-alert/README.md index 0917daf0a2..95fff9ed23 100644 --- a/avm/res/insights/activity-log-alert/README.md +++ b/avm/res/insights/activity-log-alert/README.md @@ -1,10 +1,5 @@ # Activity Log Alerts `[Microsoft.Insights/activityLogAlerts]` -> ⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ -> -> - Only security and bug fixes are being handled by the AVM core team at present. -> - If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! - This module deploys an Activity Log Alert. ## Navigation From 1c315417b3f27c4a8f170d0db04f4bb7dcd9bcff Mon Sep 17 00:00:00 2001 From: Ilhaan Rasheed Date: Wed, 17 Apr 2024 22:50:09 -0700 Subject: [PATCH 13/32] fix: aks module ARM template (#1695) ## Description Updating main.json for AKS module with latest changes in main branch. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.container-service.managed-cluster](https://github.com/ilhaan/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=fix-aks-module-armtemplate-apr2024)](https://github.com/ilhaan/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/container-service/managed-cluster/main.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/avm/res/container-service/managed-cluster/main.json b/avm/res/container-service/managed-cluster/main.json index 3fdbdb4db4..f2c24f7405 100644 --- a/avm/res/container-service/managed-cluster/main.json +++ b/avm/res/container-service/managed-cluster/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "11298093745131885845" + "templateHash": "3806996475520060110" }, "name": "Azure Kubernetes Service (AKS) Managed Clusters", "description": "This module deploys an Azure Kubernetes Service (AKS) Managed Cluster.", @@ -1528,6 +1528,11 @@ "agentPoolProfiles": "[parameters('primaryAgentPoolProfile')]", "linuxProfile": "[if(not(empty(parameters('sshPublicKey'))), createObject('adminUsername', parameters('adminUsername'), 'ssh', createObject('publicKeys', createArray(createObject('keyData', coalesce(parameters('sshPublicKey'), ''))))), null())]", "servicePrincipalProfile": "[parameters('aksServicePrincipalProfile')]", + "metricsProfile": { + "costAnalysis": { + "enabled": "[if(equals(parameters('skuTier'), 'free'), false(), parameters('costAnalysisEnabled'))]" + } + }, "ingressProfile": { "webAppRouting": { "enabled": "[parameters('webApplicationRoutingEnabled')]", @@ -1640,7 +1645,7 @@ "enabled": "[parameters('enableStorageProfileBlobCSIDriver')]" }, "diskCSIDriver": { - "enabled": "[if(equals(parameters('costAnalysisEnabled'), true()), true(), parameters('enableStorageProfileDiskCSIDriver'))]" + "enabled": "[if(and(equals(parameters('costAnalysisEnabled'), true()), not(equals(parameters('skuTier'), 'free'))), true(), parameters('enableStorageProfileDiskCSIDriver'))]" }, "fileCSIDriver": { "enabled": "[parameters('enableStorageProfileFileCSIDriver')]" From 6e086ba1bd31e3199954fbead329cf88d621a2ca Mon Sep 17 00:00:00 2001 From: Felix Borst <17405838+fblix@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:51:10 +0200 Subject: [PATCH 14/32] fix: Failing Pipeline: Nullable types are not allowed (#1698) ## Description This PR solves the failing pipelines currently present in main. The fix includes changing the `networkAclsType` to not be nullable anymore. Fixes #1453 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.storage.storage-account](https://github.com/fblix/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Ffblix%2F1453)](https://github.com/fblix/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Felix Borst --- avm/res/storage/storage-account/README.md | 14 +++++ .../container/immutability-policy/main.json | 4 +- .../blob-service/container/main.json | 8 +-- .../storage-account/blob-service/main.json | 12 ++--- .../storage-account/file-service/main.json | 12 ++--- .../file-service/share/main.json | 8 +-- .../storage-account/local-user/main.json | 4 +- avm/res/storage/storage-account/main.bicep | 2 +- avm/res/storage/storage-account/main.json | 52 +++++++++---------- .../management-policy/main.json | 4 +- .../storage-account/queue-service/main.json | 8 +-- .../queue-service/queue/main.json | 4 +- .../storage-account/table-service/main.json | 8 +-- .../table-service/table/main.json | 4 +- .../tests/e2e/defaults/main.test.bicep | 2 +- 15 files changed, 80 insertions(+), 66 deletions(-) diff --git a/avm/res/storage/storage-account/README.md b/avm/res/storage/storage-account/README.md index 4bdbae8b8e..83fba9bd49 100644 --- a/avm/res/storage/storage-account/README.md +++ b/avm/res/storage/storage-account/README.md @@ -181,6 +181,11 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { name: 'ssamin001' // Non-required parameters allowBlobPublicAccess: false + location: '' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + } } } ``` @@ -204,6 +209,15 @@ module storageAccount 'br/public:avm/res/storage/storage-account:' = { // Non-required parameters "allowBlobPublicAccess": { "value": false + }, + "location": { + "value": "" + }, + "networkAcls": { + "value": { + "bypass": "AzureServices", + "defaultAction": "Deny" + } } } } diff --git a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json index 91a31edc0d..a45e614327 100644 --- a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json +++ b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2429139364933838439" + "version": "0.26.170.59819", + "templateHash": "12849754295459852309" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/container/main.json b/avm/res/storage/storage-account/blob-service/container/main.json index a41cc7b8ce..0d487d2827 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.json +++ b/avm/res/storage/storage-account/blob-service/container/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3297137219061666309" + "version": "0.26.170.59819", + "templateHash": "3805384021483033369" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -274,8 +274,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2429139364933838439" + "version": "0.26.170.59819", + "templateHash": "12849754295459852309" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/main.json b/avm/res/storage/storage-account/blob-service/main.json index 2ac6992281..1ffa10179f 100644 --- a/avm/res/storage/storage-account/blob-service/main.json +++ b/avm/res/storage/storage-account/blob-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12175020972967228537" + "version": "0.26.170.59819", + "templateHash": "7278814029590745003" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -403,8 +403,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3297137219061666309" + "version": "0.26.170.59819", + "templateHash": "3805384021483033369" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -672,8 +672,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2429139364933838439" + "version": "0.26.170.59819", + "templateHash": "12849754295459852309" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/file-service/main.json b/avm/res/storage/storage-account/file-service/main.json index 38dcbcb7c7..31a38bc821 100644 --- a/avm/res/storage/storage-account/file-service/main.json +++ b/avm/res/storage/storage-account/file-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13860799899517512764" + "version": "0.26.170.59819", + "templateHash": "4306536926065797375" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -286,8 +286,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "18405917853511220559" + "version": "0.26.170.59819", + "templateHash": "13618261904162439978" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -486,8 +486,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16221226236637657314" + "version": "0.26.170.59819", + "templateHash": "6057169747302051267" } }, "parameters": { diff --git a/avm/res/storage/storage-account/file-service/share/main.json b/avm/res/storage/storage-account/file-service/share/main.json index 813cd74fbd..5e5f8ff5c6 100644 --- a/avm/res/storage/storage-account/file-service/share/main.json +++ b/avm/res/storage/storage-account/file-service/share/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "18405917853511220559" + "version": "0.26.170.59819", + "templateHash": "13618261904162439978" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -205,8 +205,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16221226236637657314" + "version": "0.26.170.59819", + "templateHash": "6057169747302051267" } }, "parameters": { diff --git a/avm/res/storage/storage-account/local-user/main.json b/avm/res/storage/storage-account/local-user/main.json index 07d5ea2520..c5936241dd 100644 --- a/avm/res/storage/storage-account/local-user/main.json +++ b/avm/res/storage/storage-account/local-user/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3388645117303533667" + "version": "0.26.170.59819", + "templateHash": "14593329616022153178" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", diff --git a/avm/res/storage/storage-account/main.bicep b/avm/res/storage/storage-account/main.bicep index 1b4437ab21..58ba1d817e 100644 --- a/avm/res/storage/storage-account/main.bicep +++ b/avm/res/storage/storage-account/main.bicep @@ -701,7 +701,7 @@ type networkAclsType = { @description('Required. Specifies the default action of allow or deny when no other rules match.') defaultAction: ('Allow' | 'Deny') -}? +} type privateEndpointType = { @description('Optional. The name of the private endpoint.') diff --git a/avm/res/storage/storage-account/main.json b/avm/res/storage/storage-account/main.json index d4a1fa422f..91eb9c9f57 100644 --- a/avm/res/storage/storage-account/main.json +++ b/avm/res/storage/storage-account/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13878793352750035767" + "version": "0.26.170.59819", + "templateHash": "8863225181818962940" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -1691,8 +1691,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "14052382142427178929" + "version": "0.26.170.59819", + "templateHash": "4153955640795225346" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", @@ -1801,8 +1801,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3388645117303533667" + "version": "0.26.170.59819", + "templateHash": "14593329616022153178" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", @@ -2019,8 +2019,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12175020972967228537" + "version": "0.26.170.59819", + "templateHash": "7278814029590745003" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -2417,8 +2417,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "3297137219061666309" + "version": "0.26.170.59819", + "templateHash": "3805384021483033369" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -2686,8 +2686,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2429139364933838439" + "version": "0.26.170.59819", + "templateHash": "12849754295459852309" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", @@ -2865,8 +2865,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13860799899517512764" + "version": "0.26.170.59819", + "templateHash": "4306536926065797375" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -3146,8 +3146,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "18405917853511220559" + "version": "0.26.170.59819", + "templateHash": "13618261904162439978" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -3346,8 +3346,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "16221226236637657314" + "version": "0.26.170.59819", + "templateHash": "6057169747302051267" } }, "parameters": { @@ -3615,8 +3615,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13677013226831946461" + "version": "0.26.170.59819", + "templateHash": "4494965320132257914" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -3860,8 +3860,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17219066722763101567" + "version": "0.26.170.59819", + "templateHash": "2870814699283520878" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -4117,8 +4117,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17373442840517371502" + "version": "0.26.170.59819", + "templateHash": "6806949296172999226" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -4359,8 +4359,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10226619499666007193" + "version": "0.26.170.59819", + "templateHash": "5583637725561111612" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", diff --git a/avm/res/storage/storage-account/management-policy/main.json b/avm/res/storage/storage-account/management-policy/main.json index 323df90a49..d751651d08 100644 --- a/avm/res/storage/storage-account/management-policy/main.json +++ b/avm/res/storage/storage-account/management-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "14052382142427178929" + "version": "0.26.170.59819", + "templateHash": "4153955640795225346" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", diff --git a/avm/res/storage/storage-account/queue-service/main.json b/avm/res/storage/storage-account/queue-service/main.json index ea2dd644a3..b1ddfd7258 100644 --- a/avm/res/storage/storage-account/queue-service/main.json +++ b/avm/res/storage/storage-account/queue-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "13677013226831946461" + "version": "0.26.170.59819", + "templateHash": "4494965320132257914" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -250,8 +250,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17219066722763101567" + "version": "0.26.170.59819", + "templateHash": "2870814699283520878" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", diff --git a/avm/res/storage/storage-account/queue-service/queue/main.json b/avm/res/storage/storage-account/queue-service/queue/main.json index 981f57b5d0..7463266ec4 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.json +++ b/avm/res/storage/storage-account/queue-service/queue/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17219066722763101567" + "version": "0.26.170.59819", + "templateHash": "2870814699283520878" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", diff --git a/avm/res/storage/storage-account/table-service/main.json b/avm/res/storage/storage-account/table-service/main.json index bb058e623a..cd4f72dbef 100644 --- a/avm/res/storage/storage-account/table-service/main.json +++ b/avm/res/storage/storage-account/table-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "17373442840517371502" + "version": "0.26.170.59819", + "templateHash": "6806949296172999226" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -247,8 +247,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10226619499666007193" + "version": "0.26.170.59819", + "templateHash": "5583637725561111612" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", diff --git a/avm/res/storage/storage-account/table-service/table/main.json b/avm/res/storage/storage-account/table-service/table/main.json index 1ba6eab43d..0abcc4d463 100644 --- a/avm/res/storage/storage-account/table-service/table/main.json +++ b/avm/res/storage/storage-account/table-service/table/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "10226619499666007193" + "version": "0.26.170.59819", + "templateHash": "5583637725561111612" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", diff --git a/avm/res/storage/storage-account/tests/e2e/defaults/main.test.bicep b/avm/res/storage/storage-account/tests/e2e/defaults/main.test.bicep index 31e888a7ff..c6de9c25bf 100644 --- a/avm/res/storage/storage-account/tests/e2e/defaults/main.test.bicep +++ b/avm/res/storage/storage-account/tests/e2e/defaults/main.test.bicep @@ -45,7 +45,7 @@ module testDeployment '../../../main.bicep' = [ allowBlobPublicAccess: false location: resourceLocation networkAcls: { - defaultAction: 'Allow' + defaultAction: 'Deny' bypass: 'AzureServices' } } From d9a53b87e12bb7ea991d45c8679bde27481b25cb Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Thu, 18 Apr 2024 12:41:58 -0600 Subject: [PATCH 15/32] feat: `avm/ptn/authorization/role-assignment` (#1641) ## Description Migrating module from CARML to AVM Closes https://github.com/Azure/Azure-Verified-Modules/issues/775 ## Pipeline Reference [![avm.ptn.authorization.role-assignment](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml/badge.svg?branch=avm%2Fptn%2Fauthorization%2Frole-assignment)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml) ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Co-authored-by: Alexander Sehr --- .github/CODEOWNERS | 3 +- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../avm.ptn.authorization.role-assignment.yml | 86 +++ .../authorization/role-assignment/ORPHANED.md | 4 + .../authorization/role-assignment/README.md | 567 +++++++++++++++ .../authorization/role-assignment/main.bicep | 127 ++++ .../authorization/role-assignment/main.json | 666 ++++++++++++++++++ .../modules/management-group.bicep | 75 ++ .../modules/resource-group.bicep | 78 ++ .../modules/subscription.bicep | 75 ++ .../tests/e2e/mg.defaults/dependencies.bicep | 13 + .../mg.defaults/interim.dependencies.bicep | 28 + .../tests/e2e/mg.defaults/main.test.bicep | 53 ++ .../tests/e2e/mg.max/dependencies.bicep | 13 + .../e2e/mg.max/interim.dependencies.bicep | 28 + .../tests/e2e/mg.max/main.test.bicep | 55 ++ .../tests/e2e/rg.default/dependencies.bicep | 13 + .../tests/e2e/rg.default/main.test.bicep | 65 ++ .../tests/e2e/rg.max/dependencies.bicep | 13 + .../tests/e2e/rg.max/main.test.bicep | 64 ++ .../tests/e2e/sub.default/dependencies.bicep | 13 + .../sub.default/interim.dependencies.bicep | 28 + .../tests/e2e/sub.default/main.test.bicep | 65 ++ .../tests/e2e/sub.max/dependencies.bicep | 13 + .../e2e/sub.max/interim.dependencies.bicep | 28 + .../tests/e2e/sub.max/main.test.bicep | 66 ++ .../role-assignment/version.json | 7 + 27 files changed, 2246 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/avm.ptn.authorization.role-assignment.yml create mode 100644 avm/ptn/authorization/role-assignment/ORPHANED.md create mode 100644 avm/ptn/authorization/role-assignment/README.md create mode 100644 avm/ptn/authorization/role-assignment/main.bicep create mode 100644 avm/ptn/authorization/role-assignment/main.json create mode 100644 avm/ptn/authorization/role-assignment/modules/management-group.bicep create mode 100644 avm/ptn/authorization/role-assignment/modules/resource-group.bicep create mode 100644 avm/ptn/authorization/role-assignment/modules/subscription.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/interim.dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.max/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.max/interim.dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/mg.max/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/rg.default/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/rg.default/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/rg.max/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/rg.max/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.default/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.default/interim.dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.default/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.max/dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.max/interim.dependencies.bicep create mode 100644 avm/ptn/authorization/role-assignment/tests/e2e/sub.max/main.test.bicep create mode 100644 avm/ptn/authorization/role-assignment/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bf9aa06b23..7290c25df5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,7 +3,8 @@ /scripts/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /avm/ @Azure/avm-core-team-technical-bicep /avm/utilities/ @Azure/avm-core-team-technical-bicep -/avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/ptn/authorization/role-assignment/ @Azure/avm-ptn-authorization-roleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep /avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/api-management/service/ @Azure/avm-res-apimanagement-service-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/app/container-app/ @Azure/avm-res-app-containerapp-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 77d58e66d1..681087787b 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -38,6 +38,7 @@ body: description: Which existing AVM module is this issue related to? options: - "" + - "avm/ptn/authorization/role-assignment" # - "avm/ptn/avd-lza/insights" # - "avm/ptn/avd-lza/management-plane" # - "avm/ptn/avd-lza/networking" diff --git a/.github/workflows/avm.ptn.authorization.role-assignment.yml b/.github/workflows/avm.ptn.authorization.role-assignment.yml new file mode 100644 index 0000000000..c9ad828152 --- /dev/null +++ b/.github/workflows/avm.ptn.authorization.role-assignment.yml @@ -0,0 +1,86 @@ +name: "avm.ptn.authorization.role-assignment" + +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.ptn.authorization.role-assignment.yml" + - "avm/ptn/authorization/role-assignment/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/authorization/role-assignment" + workflowPath: ".github/workflows/avm.ptn.authorization.role-assignment.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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/ptn/authorization/role-assignment/ORPHANED.md b/avm/ptn/authorization/role-assignment/ORPHANED.md new file mode 100644 index 0000000000..ef8fa911d2 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/ORPHANED.md @@ -0,0 +1,4 @@ +⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ + +- Only security and bug fixes are being handled by the AVM core team at present. +- If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! \ No newline at end of file diff --git a/avm/ptn/authorization/role-assignment/README.md b/avm/ptn/authorization/role-assignment/README.md new file mode 100644 index 0000000000..bbe4b6d33e --- /dev/null +++ b/avm/ptn/authorization/role-assignment/README.md @@ -0,0 +1,567 @@ +# Role Assignments (All scopes) `[Microsoft.Authorization/roleAssignments]` + +> ⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ +> +> - Only security and bug fixes are being handled by the AVM core team at present. +> - If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! + +This module deploys a Role Assignment at a Management Group, Subscription or Resource Group scope. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | + +## 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/ptn/authorization/role-assignment:`. + +- [Role Assignments (Management Group scope)](#example-1-role-assignments-management-group-scope) +- [Role Assignments (Management Group scope)](#example-2-role-assignments-management-group-scope) +- [Role Assignments (Resource Group scope)](#example-3-role-assignments-resource-group-scope) +- [Role Assignments (Resource Group)](#example-4-role-assignments-resource-group) +- [Role Assignments (Subscription scope)](#example-5-role-assignments-subscription-scope) +- [Role Assignments (Subscription scope)](#example-6-role-assignments-subscription-scope) + +### Example 1: _Role Assignments (Management Group scope)_ + +This module deploys a Role Assignment at a Management Group scope using minimal parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: 'Resource Policy Contributor' + // Non-required parameters + location: '' + principalType: 'ServicePrincipal' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "Resource Policy Contributor" + }, + // Non-required parameters + "location": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + } + } +} +``` + +
+

+ +### Example 2: _Role Assignments (Management Group scope)_ + +This module deploys a Role Assignment at a Management Group scope using common parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: 'Management Group Reader' + // Non-required parameters + description: 'Role Assignment (management group scope)' + location: '' + managementGroupId: '' + principalType: 'ServicePrincipal' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "Management Group Reader" + }, + // Non-required parameters + "description": { + "value": "Role Assignment (management group scope)" + }, + "location": { + "value": "" + }, + "managementGroupId": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + } + } +} +``` + +
+

+ +### Example 3: _Role Assignments (Resource Group scope)_ + +This module deploys a Role Assignment at a Resource Group scope using minimal parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + // Non-required parameters + location: '' + principalType: 'ServicePrincipal' + resourceGroupName: '' + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11" + }, + // Non-required parameters + "location": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "resourceGroupName": { + "value": "" + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _Role Assignments (Resource Group)_ + +This module deploys a Role Assignment at a Resource Group scope using common parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: 'Reader' + // Non-required parameters + description: 'Role Assignment (resource group scope)' + location: '' + principalType: 'ServicePrincipal' + resourceGroupName: '' + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "Reader" + }, + // Non-required parameters + "description": { + "value": "Role Assignment (resource group scope)" + }, + "location": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "resourceGroupName": { + "value": "" + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 5: _Role Assignments (Subscription scope)_ + +This module deploys a Role Assignment at a Subscription scope using minimal parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: '' + // Non-required parameters + location: '' + principalType: 'ServicePrincipal' + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 6: _Role Assignments (Subscription scope)_ + +This module deploys a Role Assignment at a Subscription scope using common parameters. + + +

+ +via Bicep module + +```bicep +module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:' = { + name: 'roleAssignmentDeployment' + params: { + // Required parameters + principalId: '' + roleDefinitionIdOrName: 'Reader' + // Non-required parameters + description: 'Role Assignment (subscription scope)' + location: '' + principalType: 'ServicePrincipal' + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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 + "principalId": { + "value": "" + }, + "roleDefinitionIdOrName": { + "value": "Reader" + }, + // Non-required parameters + "description": { + "value": "Role Assignment (subscription scope)" + }, + "location": { + "value": "" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-principalid) | string | The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity). | +| [`roleDefinitionIdOrName`](#parameter-roledefinitionidorname) | string | You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), 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-condition) | string | The conditions on the role assignment. This limits the resources it can be assigned to. | +| [`conditionVersion`](#parameter-conditionversion) | string | Version of the condition. Currently accepted value is "2.0". | +| [`delegatedManagedIdentityResourceId`](#parameter-delegatedmanagedidentityresourceid) | string | ID of the delegated managed identity resource. | +| [`description`](#parameter-description) | string | The description of the role assignment. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | Location deployment metadata. | +| [`managementGroupId`](#parameter-managementgroupid) | string | Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment. | +| [`principalType`](#parameter-principaltype) | string | The principal type of the assigned principal ID. | +| [`resourceGroupName`](#parameter-resourcegroupname) | string | Name of the Resource Group to assign the RBAC role to. If Resource Group name is provided, and Subscription ID is provided, the module deploys at resource group level, therefore assigns the provided RBAC role to the resource group. | +| [`subscriptionId`](#parameter-subscriptionid) | string | Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription. | + +### Parameter: `principalId` + +The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity). + +- Required: Yes +- Type: string + +### Parameter: `roleDefinitionIdOrName` + +You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `conditionVersion` + +Version of the condition. Currently accepted value is "2.0". + +- Required: No +- Type: string +- Default: `'2.0'` +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `delegatedManagedIdentityResourceId` + +ID of the delegated managed identity resource. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `description` + +The description of the role assignment. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `location` + +Location deployment metadata. + +- Required: No +- Type: string +- Default: `[deployment().location]` + +### Parameter: `managementGroupId` + +Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment. + +- Required: No +- Type: string +- Default: `[managementGroup().name]` + +### Parameter: `principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `resourceGroupName` + +Name of the Resource Group to assign the RBAC role to. If Resource Group name is provided, and Subscription ID is provided, the module deploys at resource group level, therefore assigns the provided RBAC role to the resource group. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `subscriptionId` + +Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription. + +- Required: No +- Type: string +- Default: `''` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The GUID of the Role Assignment. | +| `resourceId` | string | The resource ID of the Role Assignment. | +| `scope` | string | The scope this Role Assignment applies to. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/authorization/role-assignment/main.bicep b/avm/ptn/authorization/role-assignment/main.bicep new file mode 100644 index 0000000000..36669e0e95 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/main.bicep @@ -0,0 +1,127 @@ +metadata name = 'Role Assignments (All scopes)' +metadata description = 'This module deploys a Role Assignment at a Management Group, Subscription or Resource Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'managementGroup' + +@sys.description('Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleDefinitionIdOrName string + +@sys.description('Required. The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity).') +param principalId string + +@sys.description('Optional. Name of the Resource Group to assign the RBAC role to. If Resource Group name is provided, and Subscription ID is provided, the module deploys at resource group level, therefore assigns the provided RBAC role to the resource group.') +param resourceGroupName string = '' + +@sys.description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '' + +@sys.description('Optional. Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment.') +param managementGroupId string = managementGroup().name + +@sys.description('Optional. Location deployment metadata.') +param location string = deployment().location + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. ID of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to.') +param condition string = '' + +@sys.description('Optional. Version of the condition. Currently accepted value is "2.0".') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.ptn.authorization-roleassignment.${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' + } + } + } + } + location:location + } + +module roleAssignment_mg 'modules/management-group.bicep' = if (empty(subscriptionId) && empty(resourceGroupName)) { + name: '${uniqueString(deployment().name, location)}-RoleAssignment-MG-Module' + scope: managementGroup(managementGroupId) + params: { + roleDefinitionIdOrName: roleDefinitionIdOrName + principalId: principalId + managementGroupId: managementGroupId + description: !empty(description) ? description : '' + principalType: !empty(principalType) ? principalType : '' + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : '' + conditionVersion: conditionVersion + condition: !empty(condition) ? condition : '' + } +} + +module roleAssignment_sub 'modules/subscription.bicep' = if (!empty(subscriptionId) && empty(resourceGroupName)) { + name: '${uniqueString(deployment().name, location)}-RoleAssignment-Sub-Module' + scope: subscription(subscriptionId) + params: { + roleDefinitionIdOrName: roleDefinitionIdOrName + principalId: principalId + subscriptionId: subscriptionId + description: !empty(description) ? description : '' + principalType: !empty(principalType) ? principalType : '' + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : '' + conditionVersion: conditionVersion + condition: !empty(condition) ? condition : '' + } +} + +module roleAssignment_rg 'modules/resource-group.bicep' = if (!empty(resourceGroupName) && !empty(subscriptionId)) { + name: '${uniqueString(deployment().name, location)}-RoleAssignment-RG-Module' + scope: resourceGroup(subscriptionId, resourceGroupName) + params: { + roleDefinitionIdOrName: roleDefinitionIdOrName + principalId: principalId + subscriptionId: subscriptionId + resourceGroupName: resourceGroupName + description: !empty(description) ? description : '' + principalType: !empty(principalType) ? principalType : '' + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : '' + conditionVersion: conditionVersion + condition: !empty(condition) ? condition : '' + } +} + +@sys.description('The GUID of the Role Assignment.') +output name string = empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_mg.outputs.name : (!empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_sub.outputs.name : roleAssignment_rg.outputs.name) + +@sys.description('The resource ID of the Role Assignment.') +output resourceId string = empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_mg.outputs.resourceId : (!empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_sub.outputs.resourceId : roleAssignment_rg.outputs.resourceId) + +@sys.description('The scope this Role Assignment applies to.') +output scope string = empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_mg.outputs.scope : (!empty(subscriptionId) && empty(resourceGroupName) ? roleAssignment_sub.outputs.scope : roleAssignment_rg.outputs.scope) diff --git a/avm/ptn/authorization/role-assignment/main.json b/avm/ptn/authorization/role-assignment/main.json new file mode 100644 index 0000000000..d8570e9088 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/main.json @@ -0,0 +1,666 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "3360936249154372689" + }, + "name": "Role Assignments (All scopes)", + "description": "This module deploys a Role Assignment at a Management Group, Subscription or Resource Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), 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 or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity)." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Resource Group to assign the RBAC role to. If Resource Group name is provided, and Subscription ID is provided, the module deploys at resource group level, therefore assigns the provided RBAC role to the resource group." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription." + } + }, + "managementGroupId": { + "type": "string", + "defaultValue": "[managementGroup().name]", + "metadata": { + "description": "Optional. Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "Optional. Location deployment metadata." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the delegated managed identity resource." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to." + } + }, + "conditionVersion": { + "type": "string", + "defaultValue": "2.0", + "allowedValues": [ + "2.0" + ], + "metadata": { + "description": "Optional. Version of the condition. Currently accepted value is \"2.0\"." + } + }, + "principalType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "ServicePrincipal", + "Group", + "User", + "ForeignGroup", + "Device", + "" + ], + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.ptn.authorization-roleassignment.{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" + } + } + } + }, + "location": "[parameters('location')]" + }, + { + "condition": "[and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-RoleAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))]", + "scope": "[format('Microsoft.Management/managementGroups/{0}', parameters('managementGroupId'))]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "roleDefinitionIdOrName": { + "value": "[parameters('roleDefinitionIdOrName')]" + }, + "principalId": { + "value": "[parameters('principalId')]" + }, + "managementGroupId": { + "value": "[parameters('managementGroupId')]" + }, + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "principalType": "[if(not(empty(parameters('principalType'))), createObject('value', parameters('principalType')), createObject('value', ''))]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), createObject('value', parameters('delegatedManagedIdentityResourceId')), createObject('value', ''))]", + "conditionVersion": { + "value": "[parameters('conditionVersion')]" + }, + "condition": "[if(not(empty(parameters('condition'))), createObject('value', parameters('condition')), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "13749459126745145624" + }, + "name": "Role Assignments (Management Group scope)", + "description": "This module deploys a Role Assignment at a Management Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), 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 or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity)." + } + }, + "managementGroupId": { + "type": "string", + "defaultValue": "[managementGroup().name]", + "metadata": { + "description": "Optional. Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the delegated managed identity resource." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to." + } + }, + "conditionVersion": { + "type": "string", + "defaultValue": "2.0", + "allowedValues": [ + "2.0" + ], + "metadata": { + "description": "Optional. Version of the condition. Currently accepted value is \"2.0\"." + } + }, + "principalType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "ServicePrincipal", + "Group", + "User", + "ForeignGroup", + "Device", + "" + ], + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Resource Policy Contributor": "/providers/Microsoft.Authorization/roleDefinitions/36243c78-bf99-498c-9df9-86d9f8d28608", + "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')]", + "Management Group Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ac63b705-f282-497d-ac71-919bf39d939d')]" + }, + "roleDefinitionIdVar": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('managementGroupId'), variables('roleDefinitionIdVar'), parameters('principalId'))]", + "properties": { + "roleDefinitionId": "[variables('roleDefinitionIdVar')]", + "principalId": "[parameters('principalId')]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]", + "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The GUID of the Role Assignment." + }, + "value": "[guid(parameters('managementGroupId'), variables('roleDefinitionIdVar'), parameters('principalId'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Role Assignment." + }, + "value": "[extensionResourceId(managementGroup().id, 'Microsoft.Authorization/roleAssignments', guid(parameters('managementGroupId'), variables('roleDefinitionIdVar'), parameters('principalId')))]" + }, + "scope": { + "type": "string", + "metadata": { + "description": "The scope this Role Assignment applies to." + }, + "value": "[resourceId('Microsoft.Management/managementGroups', parameters('managementGroupId'))]" + } + } + } + } + }, + { + "condition": "[and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-RoleAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[parameters('subscriptionId')]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "roleDefinitionIdOrName": { + "value": "[parameters('roleDefinitionIdOrName')]" + }, + "principalId": { + "value": "[parameters('principalId')]" + }, + "subscriptionId": { + "value": "[parameters('subscriptionId')]" + }, + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "principalType": "[if(not(empty(parameters('principalType'))), createObject('value', parameters('principalType')), createObject('value', ''))]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), createObject('value', parameters('delegatedManagedIdentityResourceId')), createObject('value', ''))]", + "conditionVersion": { + "value": "[parameters('conditionVersion')]" + }, + "condition": "[if(not(empty(parameters('condition'))), createObject('value', parameters('condition')), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "4516670639800961845" + }, + "name": "Role Assignments (Subscription scope)", + "description": "This module deploys a Role Assignment at a Subscription scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), 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 or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity)." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "[subscription().subscriptionId]", + "metadata": { + "description": "Optional. Subscription ID of the subscription to assign the RBAC role to. If not provided, will use the current scope for deployment." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the delegated managed identity resource." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to." + } + }, + "conditionVersion": { + "type": "string", + "defaultValue": "2.0", + "allowedValues": [ + "2.0" + ], + "metadata": { + "description": "Optional. Version of the condition. Currently accepted value is \"2.0\"." + } + }, + "principalType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "ServicePrincipal", + "Group", + "User", + "ForeignGroup", + "Device", + "" + ], + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + } + }, + "variables": { + "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')]" + }, + "roleDefinitionIdVar": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('subscriptionId'), variables('roleDefinitionIdVar'), parameters('principalId'))]", + "properties": { + "roleDefinitionId": "[variables('roleDefinitionIdVar')]", + "principalId": "[parameters('principalId')]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]", + "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The GUID of the Role Assignment." + }, + "value": "[guid(parameters('subscriptionId'), variables('roleDefinitionIdVar'), parameters('principalId'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Role Assignment." + }, + "value": "[subscriptionResourceId('Microsoft.Authorization/roleAssignments', guid(parameters('subscriptionId'), variables('roleDefinitionIdVar'), parameters('principalId')))]" + }, + "subscriptionName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the role assignment was applied at." + }, + "value": "[subscription().displayName]" + }, + "scope": { + "type": "string", + "metadata": { + "description": "The scope this Role Assignment applies to." + }, + "value": "[subscription().id]" + } + } + } + } + }, + { + "condition": "[and(not(empty(parameters('resourceGroupName'))), not(empty(parameters('subscriptionId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-RoleAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[parameters('subscriptionId')]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "roleDefinitionIdOrName": { + "value": "[parameters('roleDefinitionIdOrName')]" + }, + "principalId": { + "value": "[parameters('principalId')]" + }, + "subscriptionId": { + "value": "[parameters('subscriptionId')]" + }, + "resourceGroupName": { + "value": "[parameters('resourceGroupName')]" + }, + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "principalType": "[if(not(empty(parameters('principalType'))), createObject('value', parameters('principalType')), createObject('value', ''))]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), createObject('value', parameters('delegatedManagedIdentityResourceId')), createObject('value', ''))]", + "conditionVersion": { + "value": "[parameters('conditionVersion')]" + }, + "condition": "[if(not(empty(parameters('condition'))), createObject('value', parameters('condition')), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "7241874480439813582" + }, + "name": "Role Assignments (Resource Group scope)", + "description": "This module deploys a Role Assignment at a Resource Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), 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 or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity)." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Optional. Name of the Resource Group to assign the RBAC role to. If not provided, will use the current scope for deployment." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "[subscription().subscriptionId]", + "metadata": { + "description": "Optional. Subscription ID of the subscription to assign the RBAC role to. If not provided, will use the current scope for deployment." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the delegated managed identity resource." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to." + } + }, + "conditionVersion": { + "type": "string", + "defaultValue": "2.0", + "allowedValues": [ + "2.0" + ], + "metadata": { + "description": "Optional. Version of the condition. Currently accepted value is \"2.0\"." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "ServicePrincipal", + "Group", + "User", + "ForeignGroup", + "Device", + "" + ], + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + } + }, + "variables": { + "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')]" + }, + "roleDefinitionIdVar": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('subscriptionId'), parameters('resourceGroupName'), variables('roleDefinitionIdVar'), parameters('principalId'))]", + "properties": { + "roleDefinitionId": "[variables('roleDefinitionIdVar')]", + "principalId": "[parameters('principalId')]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]", + "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The GUID of the Role Assignment." + }, + "value": "[guid(parameters('subscriptionId'), parameters('resourceGroupName'), variables('roleDefinitionIdVar'), parameters('principalId'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Role Assignment." + }, + "value": "[resourceId('Microsoft.Authorization/roleAssignments', guid(parameters('subscriptionId'), parameters('resourceGroupName'), variables('roleDefinitionIdVar'), parameters('principalId')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the role assignment was applied at." + }, + "value": "[resourceGroup().name]" + }, + "scope": { + "type": "string", + "metadata": { + "description": "The scope this Role Assignment applies to." + }, + "value": "[resourceGroup().id]" + } + } + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The GUID of the Role Assignment." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Role Assignment." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value))]" + }, + "scope": { + "type": "string", + "metadata": { + "description": "The scope this Role Assignment applies to." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.scope.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.scope.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-RoleAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.scope.value))]" + } + } +} \ No newline at end of file diff --git a/avm/ptn/authorization/role-assignment/modules/management-group.bicep b/avm/ptn/authorization/role-assignment/modules/management-group.bicep new file mode 100644 index 0000000000..b1a51d40f9 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/modules/management-group.bicep @@ -0,0 +1,75 @@ +metadata name = 'Role Assignments (Management Group scope)' +metadata description = 'This module deploys a Role Assignment at a Management Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'managementGroup' + +@sys.description('Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleDefinitionIdOrName string + +@sys.description('Required. The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity).') +param principalId string + +@sys.description('Optional. Group ID of the Management Group to assign the RBAC role to. If not provided, will use the current scope for deployment.') +param managementGroupId string = managementGroup().name + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. ID of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to.') +param condition string = '' + +@sys.description('Optional. Version of the condition. Currently accepted value is "2.0".') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635' + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Resource Policy Contributor': '/providers/Microsoft.Authorization/roleDefinitions/36243c78-bf99-498c-9df9-86d9f8d28608' + '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') + 'Management Group Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ac63b705-f282-497d-ac71-919bf39d939d') +} + +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) + + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(managementGroupId, roleDefinitionIdVar, principalId) + properties: { + roleDefinitionId: roleDefinitionIdVar + principalId: principalId + description: !empty(description) ? description : null + principalType: !empty(principalType) ? any(principalType) : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + condition: !empty(condition) ? condition : null + } +} + +@sys.description('The GUID of the Role Assignment.') +output name string = roleAssignment.name + +@sys.description('The resource ID of the Role Assignment.') +output resourceId string = roleAssignment.id + +@sys.description('The scope this Role Assignment applies to.') +output scope string = az.resourceId('Microsoft.Management/managementGroups', managementGroupId) diff --git a/avm/ptn/authorization/role-assignment/modules/resource-group.bicep b/avm/ptn/authorization/role-assignment/modules/resource-group.bicep new file mode 100644 index 0000000000..459bde3f99 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/modules/resource-group.bicep @@ -0,0 +1,78 @@ +metadata name = 'Role Assignments (Resource Group scope)' +metadata description = 'This module deploys a Role Assignment at a Resource Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'resourceGroup' + +@sys.description('Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleDefinitionIdOrName string + +@sys.description('Required. The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity).') +param principalId string + +@sys.description('Optional. Name of the Resource Group to assign the RBAC role to. If not provided, will use the current scope for deployment.') +param resourceGroupName string = resourceGroup().name + +@sys.description('Optional. Subscription ID of the subscription to assign the RBAC role to. If not provided, will use the current scope for deployment.') +param subscriptionId string = subscription().subscriptionId + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. ID of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to.') +param condition string = '' + +@sys.description('Optional. Version of the condition. Currently accepted value is "2.0".') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string + +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') +} + +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscriptionId, resourceGroupName, roleDefinitionIdVar, principalId) + properties: { + roleDefinitionId: roleDefinitionIdVar + principalId: principalId + description: !empty(description) ? description : null + principalType: !empty(principalType) ? any(principalType) : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + condition: !empty(condition) ? condition : null + } +} + +@sys.description('The GUID of the Role Assignment.') +output name string = roleAssignment.name + +@sys.description('The resource ID of the Role Assignment.') +output resourceId string = roleAssignment.id + +@sys.description('The name of the resource group the role assignment was applied at.') +output resourceGroupName string = resourceGroup().name + +@sys.description('The scope this Role Assignment applies to.') +output scope string = resourceGroup().id diff --git a/avm/ptn/authorization/role-assignment/modules/subscription.bicep b/avm/ptn/authorization/role-assignment/modules/subscription.bicep new file mode 100644 index 0000000000..2a41bc6b07 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/modules/subscription.bicep @@ -0,0 +1,75 @@ +metadata name = 'Role Assignments (Subscription scope)' +metadata description = 'This module deploys a Role Assignment at a Subscription scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'subscription' + +@sys.description('Required. You can provide either the display name of the role definition (must be configured in the variable `builtInRoleNames`), or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleDefinitionIdOrName string + +@sys.description('Required. The Principal or Object ID of the Security Principal (User, Group, Service Principal, Managed Identity).') +param principalId string + +@sys.description('Optional. Subscription ID of the subscription to assign the RBAC role to. If not provided, will use the current scope for deployment.') +param subscriptionId string = subscription().subscriptionId + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. ID of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to.') +param condition string = '' + +@sys.description('Optional. Version of the condition. Currently accepted value is "2.0".') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +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') +} + +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscriptionId, roleDefinitionIdVar, principalId) + properties: { + roleDefinitionId: roleDefinitionIdVar + principalId: principalId + description: !empty(description) ? description : null + principalType: !empty(principalType) ? any(principalType) : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + condition: !empty(condition) ? condition : null + } +} + +@sys.description('The GUID of the Role Assignment.') +output name string = roleAssignment.name + +@sys.description('The resource ID of the Role Assignment.') +output resourceId string = roleAssignment.id + +@sys.description('The name of the resource group the role assignment was applied at.') +output subscriptionName string = subscription().displayName + +@sys.description('The scope this Role Assignment applies to.') +output scope string = subscription().id diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/dependencies.bicep new file mode 100644 index 0000000000..d367770432 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/interim.dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/interim.dependencies.bicep new file mode 100644 index 0000000000..55ada1deea --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/interim.dependencies.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Required. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +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: { + managedIdentityName: managedIdentityName + location: location + } +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = nestedDependencies.outputs.managedIdentityPrincipalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/main.test.bicep new file mode 100644 index 0000000000..0dfff91298 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.defaults/main.test.bicep @@ -0,0 +1,53 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Management Group scope)' +metadata description = 'This module deploys a Role Assignment at a Management Group scope using minimal parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'aramgmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module nestedDependencies 'interim.dependencies.bicep' = { + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-${serviceShort}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + resourceGroupName: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: 'Resource Policy Contributor' + principalType: 'ServicePrincipal' + location: resourceLocation + } +} diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/dependencies.bicep new file mode 100644 index 0000000000..d367770432 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/interim.dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/interim.dependencies.bicep new file mode 100644 index 0000000000..55ada1deea --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/interim.dependencies.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Required. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +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: { + managedIdentityName: managedIdentityName + location: location + } +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = nestedDependencies.outputs.managedIdentityPrincipalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/main.test.bicep new file mode 100644 index 0000000000..b7e9d1bd87 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/mg.max/main.test.bicep @@ -0,0 +1,55 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Management Group scope)' +metadata description = 'This module deploys a Role Assignment at a Management Group scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'aramgmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module nestedDependencies 'interim.dependencies.bicep' = { + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-${serviceShort}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + resourceGroupName: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: 'Management Group Reader' + description: 'Role Assignment (management group scope)' + managementGroupId: last(split(managementGroup().id, '/')) + principalType: 'ServicePrincipal' + location: resourceLocation + } +} diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/dependencies.bicep new file mode 100644 index 0000000000..5681a89989 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/main.test.bicep new file mode 100644 index 0000000000..99cebb3035 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/rg.default/main.test.bicep @@ -0,0 +1,65 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Resource Group scope)' +metadata description = 'This module deploys a Role Assignment at a Resource Group scope using minimal parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'arargmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module resourceGroupDeploy 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } + + +} +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup(subscriptionId, resourceGroupName) + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalType: 'ServicePrincipal' + location: resourceLocation + subscriptionId: subscriptionId + resourceGroupName: resourceGroupName + } +} diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/dependencies.bicep new file mode 100644 index 0000000000..5681a89989 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/main.test.bicep new file mode 100644 index 0000000000..b154901d94 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/rg.max/main.test.bicep @@ -0,0 +1,64 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Resource Group)' +metadata description = 'This module deploys a Role Assignment at a Resource Group scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'arargmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module resourceGroupDeploy 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup(subscriptionId, resourceGroupName) + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: 'Reader' + description: 'Role Assignment (resource group scope)' + principalType: 'ServicePrincipal' + location: resourceLocation + subscriptionId: subscriptionId + resourceGroupName: resourceGroupName + } +} diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/dependencies.bicep new file mode 100644 index 0000000000..d367770432 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/interim.dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/interim.dependencies.bicep new file mode 100644 index 0000000000..55ada1deea --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/interim.dependencies.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Required. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +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: { + managedIdentityName: managedIdentityName + location: location + } +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = nestedDependencies.outputs.managedIdentityPrincipalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/main.test.bicep new file mode 100644 index 0000000000..4b1543c60b --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.default/main.test.bicep @@ -0,0 +1,65 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Subscription scope)' +metadata description = 'This module deploys a Role Assignment at a Subscription scope using minimal parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'arasubmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= + + +module resourceGroup 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } +} + +module nestedDependencies 'interim.dependencies.bicep' = { + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + resourceGroupName: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + principalType: 'ServicePrincipal' + location: resourceLocation + subscriptionId: subscriptionId + } +} diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/dependencies.bicep new file mode 100644 index 0000000000..d367770432 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/interim.dependencies.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/interim.dependencies.bicep new file mode 100644 index 0000000000..55ada1deea --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/interim.dependencies.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Required. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +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: { + managedIdentityName: managedIdentityName + location: location + } +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = nestedDependencies.outputs.managedIdentityPrincipalId diff --git a/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/main.test.bicep b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/main.test.bicep new file mode 100644 index 0000000000..0f41d75951 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/tests/e2e/sub.max/main.test.bicep @@ -0,0 +1,66 @@ +targetScope = 'managementGroup' +metadata name = 'Role Assignments (Subscription scope)' +metadata description = 'This module deploys a Role Assignment at a Subscription scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.roleassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'arasubmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= + +module resourceGroup 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } + + +} +module nestedDependencies 'interim.dependencies.bicep' = { + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + resourceGroupName: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + roleDefinitionIdOrName: 'Reader' + description: 'Role Assignment (subscription scope)' + principalType: 'ServicePrincipal' + location: resourceLocation + subscriptionId: subscriptionId + } +} diff --git a/avm/ptn/authorization/role-assignment/version.json b/avm/ptn/authorization/role-assignment/version.json new file mode 100644 index 0000000000..7fa401bdf7 --- /dev/null +++ b/avm/ptn/authorization/role-assignment/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From caf42624a86138df6826559564e9d572dc92d872 Mon Sep 17 00:00:00 2001 From: Richard Hooper Date: Thu, 18 Apr 2024 22:00:15 +0100 Subject: [PATCH 16/32] feat: add option for keda addon container-service - `avm/res/container-service/managed-cluster` (#1691) ## Description This is adding in the option to enable Keda on an AKS cluster. I would probably do this one after https://github.com/Azure/bicep-registry-modules/pull/1682 as i used my main branch that has the cost anlayasis changes in it. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.container-service.managed-cluster](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=feat-Keda)](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml?query=branch%3Afeat-Keda) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Signed-off-by: PixelRobots <22979170+PixelRobots@users.noreply.github.com> --- .../container-service/managed-cluster/README.md | 9 +++++++++ .../container-service/managed-cluster/main.bicep | 8 ++++++++ .../container-service/managed-cluster/main.json | 14 +++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/avm/res/container-service/managed-cluster/README.md b/avm/res/container-service/managed-cluster/README.md index e5154ea6db..43e75d0fb7 100644 --- a/avm/res/container-service/managed-cluster/README.md +++ b/avm/res/container-service/managed-cluster/README.md @@ -1536,6 +1536,7 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster: Date: Thu, 18 Apr 2024 18:34:05 -0600 Subject: [PATCH 17/32] feat: avm/ptn/authorization/policy assignment2 (#1706) ## Description Taking ownership of authorization policy-assignment pattern module ## Pipeline Reference | Pipeline | | -------- | | | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ X] Update to documentation ## Checklist - [ X] I'm sure there are no other open Pull Requests for the same update/change - [ X] I have run `Set-AVMModule` locally to generate the supporting module files. - [ X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- avm/ptn/authorization/role-assignment/ORPHANED.md | 4 ---- avm/ptn/authorization/role-assignment/README.md | 5 ----- 2 files changed, 9 deletions(-) delete mode 100644 avm/ptn/authorization/role-assignment/ORPHANED.md diff --git a/avm/ptn/authorization/role-assignment/ORPHANED.md b/avm/ptn/authorization/role-assignment/ORPHANED.md deleted file mode 100644 index ef8fa911d2..0000000000 --- a/avm/ptn/authorization/role-assignment/ORPHANED.md +++ /dev/null @@ -1,4 +0,0 @@ -⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ - -- Only security and bug fixes are being handled by the AVM core team at present. -- If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! \ No newline at end of file diff --git a/avm/ptn/authorization/role-assignment/README.md b/avm/ptn/authorization/role-assignment/README.md index bbe4b6d33e..dd1e9be0ca 100644 --- a/avm/ptn/authorization/role-assignment/README.md +++ b/avm/ptn/authorization/role-assignment/README.md @@ -1,10 +1,5 @@ # Role Assignments (All scopes) `[Microsoft.Authorization/roleAssignments]` -> ⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ -> -> - Only security and bug fixes are being handled by the AVM core team at present. -> - If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! - This module deploys a Role Assignment at a Management Group, Subscription or Resource Group scope. ## Navigation From f3307c719961db3b27dc8e2f5741ad1bd7552bba Mon Sep 17 00:00:00 2001 From: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Date: Fri, 19 Apr 2024 09:25:58 +0200 Subject: [PATCH 18/32] fix: change secret reference to the correct name (#1707) --- .github/workflows/avm.platform.manage-workflow-issue.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/avm.platform.manage-workflow-issue.yml b/.github/workflows/avm.platform.manage-workflow-issue.yml index 9c80793d18..2125028fe3 100644 --- a/.github/workflows/avm.platform.manage-workflow-issue.yml +++ b/.github/workflows/avm.platform.manage-workflow-issue.yml @@ -18,8 +18,8 @@ jobs: - uses: tibdex/github-app-token@v2 id: generate-token with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} + app_id: ${{ secrets.TEAM_LINTER_APP_ID }} + private_key: ${{ secrets.TEAM_LINTER_PRIVATE_KEY }} - name: Manage issues shell: pwsh env: From 4f0f0a04ced5080d4c616e984e78827a95eee918 Mon Sep 17 00:00:00 2001 From: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:45:44 +0200 Subject: [PATCH 19/32] fix: add missing folder reference (#1712) Add missing folder reference, so script can be found. --- .../pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 b/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 index 42bbfda099..3ef3637972 100644 --- a/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 +++ b/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 @@ -35,6 +35,7 @@ function Set-AvmGitHubIssueOwnerConfig { # Loading helper functions . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Get-AvmCsvData.ps1') + . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'platform' 'helper' 'Add-GithubIssueToProject.ps1') $issue = gh issue view $IssueUrl.Replace('api.', '').Replace('repos/', '') --json 'author,title,url,body,comments' --repo $Repo | ConvertFrom-Json -Depth 100 From e4baf3f37bab7dff7879e9c70ca5a4053616a447 Mon Sep 17 00:00:00 2001 From: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:42:05 +0200 Subject: [PATCH 20/32] fix: minor fixes in issue automation (#1717) --- .github/workflows/avm.platform.manage-workflow-issue.yml | 3 ++- .../avm.platform.set-avm-github-issue-owner-config.yml | 2 +- .github/workflows/avm.platform.sync-avm-modules-list.yml | 1 + .../pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 | 1 + .../pipelines/platform/helper/Add-GithubIssueToProject.ps1 | 2 +- avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 | 1 - 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/avm.platform.manage-workflow-issue.yml b/.github/workflows/avm.platform.manage-workflow-issue.yml index 2125028fe3..9b36fb37ad 100644 --- a/.github/workflows/avm.platform.manage-workflow-issue.yml +++ b/.github/workflows/avm.platform.manage-workflow-issue.yml @@ -1,4 +1,5 @@ -name: "avm.platform.manage-workflow-issue" +# Workflow for creating issues for failing workflows +name: .Platform - Manage workflow issue on: schedule: diff --git a/.github/workflows/avm.platform.set-avm-github-issue-owner-config.yml b/.github/workflows/avm.platform.set-avm-github-issue-owner-config.yml index 48cc265747..84f3f7b138 100644 --- a/.github/workflows/avm.platform.set-avm-github-issue-owner-config.yml +++ b/.github/workflows/avm.platform.set-avm-github-issue-owner-config.yml @@ -1,5 +1,5 @@ # Workflow for notifying and assigning issues on creation -name: avm.platform.set-avm-github-issue-owner-config +name: .Platform - Set AVM GitHub issue owner config on: issues: diff --git a/.github/workflows/avm.platform.sync-avm-modules-list.yml b/.github/workflows/avm.platform.sync-avm-modules-list.yml index 7557309692..06c584a7ff 100644 --- a/.github/workflows/avm.platform.sync-avm-modules-list.yml +++ b/.github/workflows/avm.platform.sync-avm-modules-list.yml @@ -1,3 +1,4 @@ +# Workflow to create an issue, if AVM module list is not in sync with CSV file name: .Platform - Sync AVM module list on: diff --git a/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 b/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 index a2c1a5f98d..fed872e51d 100644 --- a/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 +++ b/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 @@ -95,6 +95,7 @@ function Set-AvmGithubIssueForWorkflow { $ProjectNumber = 538 # AVM - Issue Triage $comment = @" > [!IMPORTANT] +> This module is currently orphaned (has no owner), therefore expect a higher response time. > @Azure/avm-core-team-technical-bicep, the workflow for the ``$moduleName`` module has failed. Please investigate the failed workflow run. "@ diff --git a/avm/utilities/pipelines/platform/helper/Add-GithubIssueToProject.ps1 b/avm/utilities/pipelines/platform/helper/Add-GithubIssueToProject.ps1 index cd9d0222e4..ea303f47e3 100644 --- a/avm/utilities/pipelines/platform/helper/Add-GithubIssueToProject.ps1 +++ b/avm/utilities/pipelines/platform/helper/Add-GithubIssueToProject.ps1 @@ -44,7 +44,7 @@ function Add-GithubIssueToProject { }' -f organization=$Organization -F number=$ProjectNumber | ConvertFrom-Json -Depth 10 $ProjectId = $Project.data.organization.projectV2.id - $IssueId = (gh issue view $IssueUrl --repo $Repo --json 'id' | ConvertFrom-Json -Depth 100).id + $IssueId = (gh issue view $IssueUrl.Replace('api.', '').Replace('repos/', '') --repo $Repo --json 'id' | ConvertFrom-Json -Depth 100).id gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { diff --git a/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 b/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 index 17f6e077b9..821f476bb7 100644 --- a/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 +++ b/avm/utilities/pipelines/platform/helper/Get-AvmCsvData.ps1 @@ -30,7 +30,6 @@ Function Get-AvmCsvData { 'Bicep-Resource' { try { $unfilteredCSV = Invoke-WebRequest -Uri $BicepResourceUrl - } catch { throw 'Unable to retrieve CSV file - Check network connection.' } From 573eedd0004388ebb9e6580f0a29b26c3a39b488 Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Fri, 19 Apr 2024 09:28:05 -0600 Subject: [PATCH 21/32] feat: `avm/ptn/authorization/policy-assignment` (#1688) ## Description Migration of policy-assignment pattern module from CARML to AVM Closes https://github.com/Azure/Azure-Verified-Modules/issues/774 ## Pipeline Reference [![avm.ptn.authorization.policy-assignment](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml/badge.svg?branch=avm%2Fptn%2Fauthorization%2Fpolicy-assignment)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml) ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .github/CODEOWNERS | 3 +- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + ...m.ptn.authorization.policy-assignment.yml} | 10 +- .../authorization/policy-assignment/README.md | 1063 +++++++++++++++++ .../policy-assignment/main.bicep | 172 +++ .../authorization/policy-assignment/main.json | 989 +++++++++++++++ .../modules/management-group.bicep | 112 ++ .../modules/resource-group.bicep | 119 ++ .../modules/subscription.bicep | 112 ++ .../tests/e2e/mg.defaults/main.test.bicep | 33 + .../tests/e2e/mg.max/main.test.bicep | 96 ++ .../tests/e2e/rg.defaults/main.test.bicep | 57 + .../tests/e2e/rg.max/dependencies.bicep | 33 + .../tests/e2e/rg.max/main.test.bicep | 127 ++ .../tests/e2e/sub.defaults/main.test.bicep | 39 + .../tests/e2e/sub.max/dependencies.bicep | 13 + .../e2e/sub.max/interim.dependencies.bicep | 28 + .../tests/e2e/sub.max/main.test.bicep | 126 ++ .../policy-assignment/version.json | 7 + 19 files changed, 3134 insertions(+), 6 deletions(-) rename .github/workflows/{avm.ptn.authorization.role-assignment.yml => avm.ptn.authorization.policy-assignment.yml} (89%) create mode 100644 avm/ptn/authorization/policy-assignment/README.md create mode 100644 avm/ptn/authorization/policy-assignment/main.bicep create mode 100644 avm/ptn/authorization/policy-assignment/main.json create mode 100644 avm/ptn/authorization/policy-assignment/modules/management-group.bicep create mode 100644 avm/ptn/authorization/policy-assignment/modules/resource-group.bicep create mode 100644 avm/ptn/authorization/policy-assignment/modules/subscription.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/mg.defaults/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/mg.max/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/rg.defaults/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/dependencies.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/sub.defaults/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/dependencies.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/interim.dependencies.bicep create mode 100644 avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/main.test.bicep create mode 100644 avm/ptn/authorization/policy-assignment/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7290c25df5..2851e9058b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,7 +3,9 @@ /scripts/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /avm/ @Azure/avm-core-team-technical-bicep /avm/utilities/ @Azure/avm-core-team-technical-bicep +/avm/ptn/authorization/policy-assignment/ @Azure/avm-ptn-authorization-policyassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/ptn/authorization/role-assignment/ @Azure/avm-ptn-authorization-roleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep /avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/api-management/service/ @Azure/avm-res-apimanagement-service-module-owners-bicep @Azure/avm-core-team-technical-bicep @@ -11,7 +13,6 @@ /avm/res/app/managed-environment/ @Azure/avm-res-app-managedenvironment-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/app-configuration/configuration-store/ @Azure/avm-res-appconfiguration-configurationstore-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/res/authorization/lock/ @Azure/avm-res-authorization-lock-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/authorization/policy-assignment/ @Azure/avm-res-authorization-policyassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/res/authorization/policy-definition/ @Azure/avm-res-authorization-policydefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/res/authorization/policy-exemption/ @Azure/avm-res-authorization-policyexemption-module-owners-bicep @Azure/avm-core-team-technical-bicep #/avm/res/authorization/policy-set-definition/ @Azure/avm-res-authorization-policysetdefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 681087787b..6c1a8d60bf 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -38,6 +38,7 @@ body: description: Which existing AVM module is this issue related to? options: - "" + - "avm/ptn/authorization/policy-assignment" - "avm/ptn/authorization/role-assignment" # - "avm/ptn/avd-lza/insights" # - "avm/ptn/avd-lza/management-plane" diff --git a/.github/workflows/avm.ptn.authorization.role-assignment.yml b/.github/workflows/avm.ptn.authorization.policy-assignment.yml similarity index 89% rename from .github/workflows/avm.ptn.authorization.role-assignment.yml rename to .github/workflows/avm.ptn.authorization.policy-assignment.yml index c9ad828152..89b9f28b0f 100644 --- a/.github/workflows/avm.ptn.authorization.role-assignment.yml +++ b/.github/workflows/avm.ptn.authorization.policy-assignment.yml @@ -1,4 +1,4 @@ -name: "avm.ptn.authorization.role-assignment" +name: "avm.ptn.authorization.policy-assignment" on: schedule: @@ -26,15 +26,15 @@ on: paths: - ".github/actions/templates/avm-**" - ".github/workflows/avm.template.module.yml" - - ".github/workflows/avm.ptn.authorization.role-assignment.yml" - - "avm/ptn/authorization/role-assignment/**" + - ".github/workflows/avm.ptn.authorization.policy-assignment.yml" + - "avm/ptn/authorization/policy-assignment/**" - "avm/utilities/pipelines/**" - "!avm/utilities/pipelines/platform/**" - "!*/**/README.md" env: - modulePath: "avm/ptn/authorization/role-assignment" - workflowPath: ".github/workflows/avm.ptn.authorization.role-assignment.yml" + modulePath: "avm/ptn/authorization/policy-assignment" + workflowPath: ".github/workflows/avm.ptn.authorization.policy-assignment.yml" concurrency: group: ${{ github.workflow }} diff --git a/avm/ptn/authorization/policy-assignment/README.md b/avm/ptn/authorization/policy-assignment/README.md new file mode 100644 index 0000000000..040d6979dd --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/README.md @@ -0,0 +1,1063 @@ +# Policy Assignments (All scopes) `[Microsoft.Authorization/policyAssignments]` + +This module deploys a Policy Assignment at a Management Group, Subscription or Resource Group scope. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/policyAssignments` | [2022-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-06-01/policyAssignments) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | + +## 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/ptn/authorization/policy-assignment:`. + +- [Policy Assignments (Management Group scope)](#example-1-policy-assignments-management-group-scope) +- [Policy Assignments (Management Group scope)](#example-2-policy-assignments-management-group-scope) +- [Policy Assignments (Resource Group)](#example-3-policy-assignments-resource-group) +- [Policy Assignments (Resource Group)](#example-4-policy-assignments-resource-group) +- [Policy Assignments (Subscription)](#example-5-policy-assignments-subscription) +- [Policy Assignments (Subscription)](#example-6-policy-assignments-subscription) + +### Example 1: _Policy Assignments (Management Group scope)_ + +This module deploys a Policy Assignment at a Management Group scope using minimal parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apamgmin001' + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + // Non-required parameters + location: '' + metadata: { + assignedBy: 'Bicep' + } + } +} +``` + +
+

+ +

+ +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": "apamgmin001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d" + }, + // Non-required parameters + "location": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep" + } + } + } +} +``` + +
+

+ +### Example 2: _Policy Assignments (Management Group scope)_ + +This module deploys a Policy Assignment at a Management Group scope using common parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apamgmax001' + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + // Non-required parameters + description: '[Description] Policy Assignment at the management group scope' + displayName: '[Display Name] Policy Assignment at the management group scope' + enforcementMode: 'DoNotEnforce' + identity: 'SystemAssigned' + location: '' + managementGroupId: '' + metadata: { + assignedBy: 'Bicep' + category: 'Security' + version: '1.0' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + '/subscriptions/${subscriptionId}/resourceGroups/validation-rg' + ] + overrides: [ + { + kind: 'policyEffect' + selectors: [ + { + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + kind: 'policyDefinitionReferenceId' + } + ] + value: 'Disabled' + } + ] + parameters: { + effect: { + value: 'Disabled' + } + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + } + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + in: [ + 'Microsoft.Compute/virtualMachines' + ] + kind: 'resourceType' + } + { + in: [ + 'westeurope' + ] + kind: 'resourceLocation' + } + ] + } + ] + roleDefinitionIds: [ + '/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + ] + } +} +``` + +
+

+ +

+ +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": "apamgmax001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611" + }, + // Non-required parameters + "description": { + "value": "[Description] Policy Assignment at the management group scope" + }, + "displayName": { + "value": "[Display Name] Policy Assignment at the management group scope" + }, + "enforcementMode": { + "value": "DoNotEnforce" + }, + "identity": { + "value": "SystemAssigned" + }, + "location": { + "value": "" + }, + "managementGroupId": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep", + "category": "Security", + "version": "1.0" + } + }, + "nonComplianceMessages": { + "value": [ + { + "message": "Violated Policy Assignment - This is a Non Compliance Message" + } + ] + }, + "notScopes": { + "value": [ + "/subscriptions/${subscriptionId}/resourceGroups/validation-rg" + ] + }, + "overrides": { + "value": [ + { + "kind": "policyEffect", + "selectors": [ + { + "in": [ + "ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent", + "ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent" + ], + "kind": "policyDefinitionReferenceId" + } + ], + "value": "Disabled" + } + ] + }, + "parameters": { + "value": { + "effect": { + "value": "Disabled" + }, + "enableCollectionOfSqlQueriesForSecurityResearch": { + "value": false + } + } + }, + "resourceSelectors": { + "value": [ + { + "name": "resourceSelector-test", + "selectors": [ + { + "in": [ + "Microsoft.Compute/virtualMachines" + ], + "kind": "resourceType" + }, + { + "in": [ + "westeurope" + ], + "kind": "resourceLocation" + } + ] + } + ] + }, + "roleDefinitionIds": { + "value": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + } + } +} +``` + +
+

+ +### Example 3: _Policy Assignments (Resource Group)_ + +This module deploys a Policy Assignment at a Resource Group scope using minimal parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apargmin001' + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + // Non-required parameters + location: '' + metadata: { + assignedBy: 'Bicep' + } + resourceGroupName: '' + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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": "apargmin001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d" + }, + // Non-required parameters + "location": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep" + } + }, + "resourceGroupName": { + "value": "" + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _Policy Assignments (Resource Group)_ + +This module deploys a Policy Assignment at a Resource Group scope using common parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apargmax001' + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + // Non-required parameters + description: '[Description] Policy Assignment at the resource group scope' + displayName: '[Display Name] Policy Assignment at the resource group scope' + enforcementMode: 'DoNotEnforce' + identity: 'UserAssigned' + location: '' + metadata: { + assignedBy: 'Bicep' + category: 'Security' + version: '1.0' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + '' + ] + overrides: [ + { + kind: 'policyEffect' + selectors: [ + { + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + kind: 'policyDefinitionReferenceId' + } + ] + value: 'Disabled' + } + ] + parameters: { + effect: { + value: 'Disabled' + } + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + } + resourceGroupName: '' + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + in: [ + 'Microsoft.Compute/virtualMachines' + ] + kind: 'resourceType' + } + { + in: [ + 'westeurope' + ] + kind: 'resourceLocation' + } + ] + } + ] + roleDefinitionIds: [ + '/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + ] + subscriptionId: '' + userAssignedIdentityId: '' + } +} +``` + +
+

+ +

+ +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": "apargmax001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611" + }, + // Non-required parameters + "description": { + "value": "[Description] Policy Assignment at the resource group scope" + }, + "displayName": { + "value": "[Display Name] Policy Assignment at the resource group scope" + }, + "enforcementMode": { + "value": "DoNotEnforce" + }, + "identity": { + "value": "UserAssigned" + }, + "location": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep", + "category": "Security", + "version": "1.0" + } + }, + "nonComplianceMessages": { + "value": [ + { + "message": "Violated Policy Assignment - This is a Non Compliance Message" + } + ] + }, + "notScopes": { + "value": [ + "" + ] + }, + "overrides": { + "value": [ + { + "kind": "policyEffect", + "selectors": [ + { + "in": [ + "ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent", + "ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent" + ], + "kind": "policyDefinitionReferenceId" + } + ], + "value": "Disabled" + } + ] + }, + "parameters": { + "value": { + "effect": { + "value": "Disabled" + }, + "enableCollectionOfSqlQueriesForSecurityResearch": { + "value": false + } + } + }, + "resourceGroupName": { + "value": "" + }, + "resourceSelectors": { + "value": [ + { + "name": "resourceSelector-test", + "selectors": [ + { + "in": [ + "Microsoft.Compute/virtualMachines" + ], + "kind": "resourceType" + }, + { + "in": [ + "westeurope" + ], + "kind": "resourceLocation" + } + ] + } + ] + }, + "roleDefinitionIds": { + "value": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + }, + "subscriptionId": { + "value": "" + }, + "userAssignedIdentityId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 5: _Policy Assignments (Subscription)_ + +This module deploys a Policy Assignment at a Subscription scope using common parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apasubmin001' + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + // Non-required parameters + location: '' + metadata: { + assignedBy: 'Bicep' + category: 'Security' + version: '1.0' + } + subscriptionId: '' + } +} +``` + +
+

+ +

+ +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": "apasubmin001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d" + }, + // Non-required parameters + "location": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep", + "category": "Security", + "version": "1.0" + } + }, + "subscriptionId": { + "value": "" + } + } +} +``` + +
+

+ +### Example 6: _Policy Assignments (Subscription)_ + +This module deploys a Policy Assignment at a Subscription scope using common parameters. + + +

+ +via Bicep module + +```bicep +module policyAssignment 'br/public:avm/ptn/authorization/policy-assignment:' = { + name: 'policyAssignmentDeployment' + params: { + // Required parameters + name: 'apasubmax001' + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + // Non-required parameters + description: '[Description] Policy Assignment at the subscription scope' + displayName: '[Display Name] Policy Assignment at the subscription scope' + enforcementMode: 'DoNotEnforce' + identity: 'UserAssigned' + location: '' + metadata: { + assignedBy: 'Bicep' + category: 'Security' + version: '1.0' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + '/subscriptions/${subscriptionId}/resourceGroups/validation-rg' + ] + overrides: [ + { + kind: 'policyEffect' + selectors: [ + { + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + kind: 'policyDefinitionReferenceId' + } + ] + value: 'Disabled' + } + ] + parameters: { + effect: { + value: 'Disabled' + } + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + } + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + in: [ + 'Microsoft.Compute/virtualMachines' + ] + kind: 'resourceType' + } + { + in: [ + 'westeurope' + ] + kind: 'resourceLocation' + } + ] + } + ] + roleDefinitionIds: [ + '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + ] + subscriptionId: '' + userAssignedIdentityId: '' + } +} +``` + +
+

+ +

+ +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": "apasubmax001" + }, + "policyDefinitionId": { + "value": "/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611" + }, + // Non-required parameters + "description": { + "value": "[Description] Policy Assignment at the subscription scope" + }, + "displayName": { + "value": "[Display Name] Policy Assignment at the subscription scope" + }, + "enforcementMode": { + "value": "DoNotEnforce" + }, + "identity": { + "value": "UserAssigned" + }, + "location": { + "value": "" + }, + "metadata": { + "value": { + "assignedBy": "Bicep", + "category": "Security", + "version": "1.0" + } + }, + "nonComplianceMessages": { + "value": [ + { + "message": "Violated Policy Assignment - This is a Non Compliance Message" + } + ] + }, + "notScopes": { + "value": [ + "/subscriptions/${subscriptionId}/resourceGroups/validation-rg" + ] + }, + "overrides": { + "value": [ + { + "kind": "policyEffect", + "selectors": [ + { + "in": [ + "ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent", + "ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent" + ], + "kind": "policyDefinitionReferenceId" + } + ], + "value": "Disabled" + } + ] + }, + "parameters": { + "value": { + "effect": { + "value": "Disabled" + }, + "enableCollectionOfSqlQueriesForSecurityResearch": { + "value": false + } + } + }, + "resourceSelectors": { + "value": [ + { + "name": "resourceSelector-test", + "selectors": [ + { + "in": [ + "Microsoft.Compute/virtualMachines" + ], + "kind": "resourceType" + }, + { + "in": [ + "westeurope" + ], + "kind": "resourceLocation" + } + ] + } + ] + }, + "roleDefinitionIds": { + "value": [ + "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + }, + "subscriptionId": { + "value": "" + }, + "userAssignedIdentityId": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope, 64 characters for subscription and resource group scopes. | +| [`policyDefinitionId`](#parameter-policydefinitionid) | string | Specifies the ID of the policy definition or policy set definition being assigned. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`description`](#parameter-description) | string | This message will be part of response in case of policy violation. | +| [`displayName`](#parameter-displayname) | string | The display name of the policy assignment. Maximum length is 128 characters. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`enforcementMode`](#parameter-enforcementmode) | string | The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce. | +| [`identity`](#parameter-identity) | string | The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`managementGroupId`](#parameter-managementgroupid) | string | The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment. | +| [`metadata`](#parameter-metadata) | object | The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs. | +| [`nonComplianceMessages`](#parameter-noncompliancemessages) | array | The messages that describe why a resource is non-compliant with the policy. | +| [`notScopes`](#parameter-notscopes) | array | The policy excluded scopes. | +| [`overrides`](#parameter-overrides) | array | The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition. | +| [`parameters`](#parameter-parameters) | object | Parameters for the policy assignment if needed. | +| [`resourceGroupName`](#parameter-resourcegroupname) | string | The Target Scope for the Policy. The name of the resource group for the policy assignment. | +| [`resourceSelectors`](#parameter-resourceselectors) | array | The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location. | +| [`roleDefinitionIds`](#parameter-roledefinitionids) | array | The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition. | +| [`subscriptionId`](#parameter-subscriptionid) | string | The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. | +| [`userAssignedIdentityId`](#parameter-userassignedidentityid) | string | The Resource ID for the user assigned identity to assign to the policy assignment. | + +### Parameter: `name` + +Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope, 64 characters for subscription and resource group scopes. + +- Required: Yes +- Type: string + +### Parameter: `policyDefinitionId` + +Specifies the ID of the policy definition or policy set definition being assigned. + +- Required: Yes +- Type: string + +### Parameter: `description` + +This message will be part of response in case of policy violation. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `displayName` + +The display name of the policy assignment. Maximum length is 128 characters. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `enforcementMode` + +The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce. + +- Required: No +- Type: string +- Default: `'Default'` +- Allowed: + ```Bicep + [ + 'Default' + 'DoNotEnforce' + ] + ``` + +### Parameter: `identity` + +The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions. + +- Required: No +- Type: string +- Default: `'SystemAssigned'` +- Allowed: + ```Bicep + [ + 'None' + 'SystemAssigned' + 'UserAssigned' + ] + ``` + +### Parameter: `location` + +Location for all resources. + +- Required: No +- Type: string +- Default: `[deployment().location]` + +### Parameter: `managementGroupId` + +The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment. + +- Required: No +- Type: string +- Default: `[managementGroup().name]` + +### Parameter: `metadata` + +The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `nonComplianceMessages` + +The messages that describe why a resource is non-compliant with the policy. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `notScopes` + +The policy excluded scopes. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `overrides` + +The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `parameters` + +Parameters for the policy assignment if needed. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `resourceGroupName` + +The Target Scope for the Policy. The name of the resource group for the policy assignment. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `resourceSelectors` + +The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `roleDefinitionIds` + +The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `subscriptionId` + +The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `userAssignedIdentityId` + +The Resource ID for the user assigned identity to assign to the policy assignment. + +- Required: No +- Type: string +- Default: `''` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | Policy Assignment Name. | +| `principalId` | string | Policy Assignment principal ID. | +| `resourceId` | string | Policy Assignment resource ID. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/authorization/policy-assignment/main.bicep b/avm/ptn/authorization/policy-assignment/main.bicep new file mode 100644 index 0000000000..286669834a --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/main.bicep @@ -0,0 +1,172 @@ +metadata name = 'Policy Assignments (All scopes)' +metadata description = 'This module deploys a Policy Assignment at a Management Group, Subscription or Resource Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'managementGroup' + +@sys.description('Required. Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope, 64 characters for subscription and resource group scopes.') +param name string + +@sys.description('Optional. This message will be part of response in case of policy violation.') +param description string = '' + +@sys.description('Optional. The display name of the policy assignment. Maximum length is 128 characters.') +@maxLength(128) +param displayName string = '' + +@sys.description('Required. Specifies the ID of the policy definition or policy set definition being assigned.') +param policyDefinitionId string + +@sys.description('Optional. Parameters for the policy assignment if needed.') +param parameters object = {} + +@sys.description('Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning \'Modify\' policy definitions.') +@allowed([ + 'SystemAssigned' + 'UserAssigned' + 'None' +]) +param identity string = 'SystemAssigned' + +@sys.description('Optional. The Resource ID for the user assigned identity to assign to the policy assignment.') +param userAssignedIdentityId string = '' + +@sys.description('Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition.') +param roleDefinitionIds array = [] + +@sys.description('Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs.') +param metadata object = {} + +@sys.description('Optional. The messages that describe why a resource is non-compliant with the policy.') +param nonComplianceMessages array = [] + +@sys.description('Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce.') +@allowed([ + 'Default' + 'DoNotEnforce' +]) +param enforcementMode string = 'Default' + +@sys.description('Optional. The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment.') +param managementGroupId string = managementGroup().name + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment.') +param subscriptionId string = '' + +@sys.description('Optional. The Target Scope for the Policy. The name of the resource group for the policy assignment.') +param resourceGroupName string = '' + +@sys.description('Optional. The policy excluded scopes.') +param notScopes array = [] + +@sys.description('Optional. Location for all resources.') +param location string = deployment().location + +@sys.description('Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition.') +param overrides array = [] + +@sys.description('Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location.') +param resourceSelectors array = [] + +@sys.description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: take('46d3xbcp.ptn.authorization-policyassignment.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}', 64) + location: location + 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' + } + } + } + } +} + +module policyAssignment_mg 'modules/management-group.bicep' = if (empty(subscriptionId) && empty(resourceGroupName)) { + name: '${uniqueString(deployment().name, location)}-PolicyAssignment-MG-Module' + scope: managementGroup(managementGroupId) + params: { + name: name + policyDefinitionId: policyDefinitionId + displayName: !empty(displayName) ? displayName : '' + description: !empty(description) ? description : '' + parameters: !empty(parameters) ? parameters : {} + identity: identity + userAssignedIdentityId: userAssignedIdentityId + roleDefinitionIds: !empty(roleDefinitionIds) ? roleDefinitionIds : [] + metadata: !empty(metadata) ? metadata : {} + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + managementGroupId: managementGroupId + location: location + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } +} + +module policyAssignment_sub 'modules/subscription.bicep' = if (!empty(subscriptionId) && empty(resourceGroupName)) { + name: '${uniqueString(deployment().name, location)}-PolicyAssignment-Sub-Module' + scope: subscription(subscriptionId) + params: { + name: name + policyDefinitionId: policyDefinitionId + displayName: !empty(displayName) ? displayName : '' + description: !empty(description) ? description : '' + parameters: !empty(parameters) ? parameters : {} + identity: identity + userAssignedIdentityId: userAssignedIdentityId + roleDefinitionIds: !empty(roleDefinitionIds) ? roleDefinitionIds : [] + metadata: !empty(metadata) ? metadata : {} + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + subscriptionId: subscriptionId + location: location + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } +} + +module policyAssignment_rg 'modules/resource-group.bicep' = if (!empty(resourceGroupName) && !empty(subscriptionId)) { + name: '${uniqueString(deployment().name, location)}-PolicyAssignment-RG-Module' + scope: resourceGroup(subscriptionId, resourceGroupName) + params: { + name: name + policyDefinitionId: policyDefinitionId + displayName: !empty(displayName) ? displayName : '' + description: !empty(description) ? description : '' + parameters: !empty(parameters) ? parameters : {} + identity: identity + userAssignedIdentityId: userAssignedIdentityId + roleDefinitionIds: !empty(roleDefinitionIds) ? roleDefinitionIds : [] + metadata: !empty(metadata) ? metadata : {} + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + subscriptionId: subscriptionId + location: location + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } +} + +@sys.description('Policy Assignment Name.') +output name string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.name : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.name : policyAssignment_rg.outputs.name) + +@sys.description('Policy Assignment principal ID.') +output principalId string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.principalId : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.principalId : policyAssignment_rg.outputs.principalId) + +@sys.description('Policy Assignment resource ID.') +output resourceId string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.resourceId : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.resourceId : policyAssignment_rg.outputs.resourceId) + +@sys.description('The location the resource was deployed into.') +output location string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.location : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.location : policyAssignment_rg.outputs.location) diff --git a/avm/ptn/authorization/policy-assignment/main.json b/avm/ptn/authorization/policy-assignment/main.json new file mode 100644 index 0000000000..97fcaefd0c --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/main.json @@ -0,0 +1,989 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "3274497359181095240" + }, + "name": "Policy Assignments (All scopes)", + "description": "This module deploys a Policy Assignment at a Management Group, Subscription or Resource Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope, 64 characters for subscription and resource group scopes." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. This message will be part of response in case of policy violation." + } + }, + "displayName": { + "type": "string", + "defaultValue": "", + "maxLength": 128, + "metadata": { + "description": "Optional. The display name of the policy assignment. Maximum length is 128 characters." + } + }, + "policyDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the ID of the policy definition or policy set definition being assigned." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Parameters for the policy assignment if needed." + } + }, + "identity": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions." + } + }, + "userAssignedIdentityId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource ID for the user assigned identity to assign to the policy assignment." + } + }, + "roleDefinitionIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs." + } + }, + "nonComplianceMessages": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The messages that describe why a resource is non-compliant with the policy." + } + }, + "enforcementMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "DoNotEnforce" + ], + "metadata": { + "description": "Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce." + } + }, + "managementGroupId": { + "type": "string", + "defaultValue": "[managementGroup().name]", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The name of the resource group for the policy assignment." + } + }, + "notScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy excluded scopes." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "overrides": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition." + } + }, + "resourceSelectors": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[take(format('46d3xbcp.ptn.authorization-policyassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4)), 64)]", + "location": "[parameters('location')]", + "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" + } + } + } + } + }, + { + "condition": "[and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PolicyAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))]", + "scope": "[format('Microsoft.Management/managementGroups/{0}', parameters('managementGroupId'))]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "policyDefinitionId": { + "value": "[parameters('policyDefinitionId')]" + }, + "displayName": "[if(not(empty(parameters('displayName'))), createObject('value', parameters('displayName')), createObject('value', ''))]", + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "parameters": "[if(not(empty(parameters('parameters'))), createObject('value', parameters('parameters')), createObject('value', createObject()))]", + "identity": { + "value": "[parameters('identity')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + }, + "roleDefinitionIds": "[if(not(empty(parameters('roleDefinitionIds'))), createObject('value', parameters('roleDefinitionIds')), createObject('value', createArray()))]", + "metadata": "[if(not(empty(parameters('metadata'))), createObject('value', parameters('metadata')), createObject('value', createObject()))]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), createObject('value', parameters('nonComplianceMessages')), createObject('value', createArray()))]", + "enforcementMode": { + "value": "[parameters('enforcementMode')]" + }, + "notScopes": "[if(not(empty(parameters('notScopes'))), createObject('value', parameters('notScopes')), createObject('value', createArray()))]", + "managementGroupId": { + "value": "[parameters('managementGroupId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "overrides": "[if(not(empty(parameters('overrides'))), createObject('value', parameters('overrides')), createObject('value', createArray()))]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), createObject('value', parameters('resourceSelectors')), createObject('value', createArray()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "15016174258473942920" + }, + "name": "Policy Assignments (Management Group scope)", + "description": "This module deploys a Policy Assignment at a Management Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. This message will be part of response in case of policy violation." + } + }, + "displayName": { + "type": "string", + "defaultValue": "", + "maxLength": 128, + "metadata": { + "description": "Optional. The display name of the policy assignment. Maximum length is 128 characters." + } + }, + "policyDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the ID of the policy definition or policy set definition being assigned." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Parameters for the policy assignment if needed." + } + }, + "identity": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions." + } + }, + "userAssignedIdentityId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource ID for the user assigned identity to assign to the policy assignment." + } + }, + "roleDefinitionIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs." + } + }, + "nonComplianceMessages": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The messages that describe why a resource is non-compliant with the policy." + } + }, + "enforcementMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "DoNotEnforce" + ], + "metadata": { + "description": "Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce." + } + }, + "managementGroupId": { + "type": "string", + "defaultValue": "[managementGroup().name]", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment." + } + }, + "notScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy excluded scopes." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "overrides": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition." + } + }, + "resourceSelectors": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location." + } + } + }, + "variables": { + "identityVar": "[if(equals(parameters('identity'), 'SystemAssigned'), createObject('type', parameters('identity')), if(equals(parameters('identity'), 'UserAssigned'), createObject('type', parameters('identity'), 'userAssignedIdentities', createObject(format('{0}', parameters('userAssignedIdentityId')), createObject())), null()))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2022-06-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "displayName": "[if(not(empty(parameters('displayName'))), parameters('displayName'), null())]", + "metadata": "[if(not(empty(parameters('metadata'))), parameters('metadata'), null())]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "policyDefinitionId": "[parameters('policyDefinitionId')]", + "parameters": "[parameters('parameters')]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), parameters('nonComplianceMessages'), createArray())]", + "enforcementMode": "[parameters('enforcementMode')]", + "notScopes": "[if(not(empty(parameters('notScopes'))), parameters('notScopes'), createArray())]", + "overrides": "[if(not(empty(parameters('overrides'))), parameters('overrides'), createArray())]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), parameters('resourceSelectors'), createArray())]" + }, + "identity": "[variables('identityVar')]" + }, + { + "copy": { + "name": "roleAssignment", + "count": "[length(parameters('roleDefinitionIds'))]" + }, + "condition": "[and(not(empty(parameters('roleDefinitionIds'))), equals(parameters('identity'), 'SystemAssigned'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('managementGroupId'), parameters('roleDefinitionIds')[copyIndex()], parameters('location'), parameters('name'))]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionIds')[copyIndex()]]", + "principalId": "[reference(extensionResourceId(managementGroup().id, 'Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[extensionResourceId(managementGroup().id, 'Microsoft.Authorization/policyAssignments', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Policy Assignment Name." + }, + "value": "[parameters('name')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Policy Assignment principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference(extensionResourceId(managementGroup().id, 'Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Policy Assignment resource ID." + }, + "value": "[extensionResourceId(managementGroup().id, 'Microsoft.Authorization/policyAssignments', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference(extensionResourceId(managementGroup().id, 'Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').location]" + } + } + } + } + }, + { + "condition": "[and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PolicyAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[parameters('subscriptionId')]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "policyDefinitionId": { + "value": "[parameters('policyDefinitionId')]" + }, + "displayName": "[if(not(empty(parameters('displayName'))), createObject('value', parameters('displayName')), createObject('value', ''))]", + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "parameters": "[if(not(empty(parameters('parameters'))), createObject('value', parameters('parameters')), createObject('value', createObject()))]", + "identity": { + "value": "[parameters('identity')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + }, + "roleDefinitionIds": "[if(not(empty(parameters('roleDefinitionIds'))), createObject('value', parameters('roleDefinitionIds')), createObject('value', createArray()))]", + "metadata": "[if(not(empty(parameters('metadata'))), createObject('value', parameters('metadata')), createObject('value', createObject()))]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), createObject('value', parameters('nonComplianceMessages')), createObject('value', createArray()))]", + "enforcementMode": { + "value": "[parameters('enforcementMode')]" + }, + "notScopes": "[if(not(empty(parameters('notScopes'))), createObject('value', parameters('notScopes')), createObject('value', createArray()))]", + "subscriptionId": { + "value": "[parameters('subscriptionId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "overrides": "[if(not(empty(parameters('overrides'))), createObject('value', parameters('overrides')), createObject('value', createArray()))]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), createObject('value', parameters('resourceSelectors')), createObject('value', createArray()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "977404313320239280" + }, + "name": "Policy Assignments (Subscription scope)", + "description": "This module deploys a Policy Assignment at a Subscription scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 64, + "metadata": { + "description": "Required. Specifies the name of the policy assignment. Maximum length is 64 characters for subscription scope." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. This message will be part of response in case of policy violation." + } + }, + "displayName": { + "type": "string", + "defaultValue": "", + "maxLength": 128, + "metadata": { + "description": "Optional. The display name of the policy assignment. Maximum length is 128 characters." + } + }, + "policyDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the ID of the policy definition or policy set definition being assigned." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Parameters for the policy assignment if needed." + } + }, + "identity": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions." + } + }, + "userAssignedIdentityId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource ID for the user assigned identity to assign to the policy assignment." + } + }, + "roleDefinitionIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs." + } + }, + "nonComplianceMessages": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The messages that describe why a resource is non-compliant with the policy." + } + }, + "enforcementMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "DoNotEnforce" + ], + "metadata": { + "description": "Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce." + } + }, + "notScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy excluded scopes." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "overrides": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition." + } + }, + "resourceSelectors": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "[subscription().subscriptionId]", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment." + } + } + }, + "variables": { + "identityVar": "[if(equals(parameters('identity'), 'SystemAssigned'), createObject('type', parameters('identity')), if(equals(parameters('identity'), 'UserAssigned'), createObject('type', parameters('identity'), 'userAssignedIdentities', createObject(format('{0}', parameters('userAssignedIdentityId')), createObject())), null()))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2022-06-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "displayName": "[if(not(empty(parameters('displayName'))), parameters('displayName'), null())]", + "metadata": "[if(not(empty(parameters('metadata'))), parameters('metadata'), null())]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "policyDefinitionId": "[parameters('policyDefinitionId')]", + "parameters": "[parameters('parameters')]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), parameters('nonComplianceMessages'), createArray())]", + "enforcementMode": "[parameters('enforcementMode')]", + "notScopes": "[if(not(empty(parameters('notScopes'))), parameters('notScopes'), createArray())]", + "overrides": "[if(not(empty(parameters('overrides'))), parameters('overrides'), createArray())]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), parameters('resourceSelectors'), createArray())]" + }, + "identity": "[variables('identityVar')]" + }, + { + "copy": { + "name": "roleAssignment", + "count": "[length(parameters('roleDefinitionIds'))]" + }, + "condition": "[and(not(empty(parameters('roleDefinitionIds'))), equals(parameters('identity'), 'SystemAssigned'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('subscriptionId'), parameters('roleDefinitionIds')[copyIndex()], parameters('location'), parameters('name'))]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionIds')[copyIndex()]]", + "principalId": "[reference(subscriptionResourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Authorization/policyAssignments', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Policy Assignment Name." + }, + "value": "[parameters('name')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Policy Assignment principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference(subscriptionResourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Policy Assignment resource ID." + }, + "value": "[subscriptionResourceId('Microsoft.Authorization/policyAssignments', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').location]" + } + } + } + } + }, + { + "condition": "[and(not(empty(parameters('resourceGroupName'))), not(empty(parameters('subscriptionId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PolicyAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[parameters('subscriptionId')]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "policyDefinitionId": { + "value": "[parameters('policyDefinitionId')]" + }, + "displayName": "[if(not(empty(parameters('displayName'))), createObject('value', parameters('displayName')), createObject('value', ''))]", + "description": "[if(not(empty(parameters('description'))), createObject('value', parameters('description')), createObject('value', ''))]", + "parameters": "[if(not(empty(parameters('parameters'))), createObject('value', parameters('parameters')), createObject('value', createObject()))]", + "identity": { + "value": "[parameters('identity')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + }, + "roleDefinitionIds": "[if(not(empty(parameters('roleDefinitionIds'))), createObject('value', parameters('roleDefinitionIds')), createObject('value', createArray()))]", + "metadata": "[if(not(empty(parameters('metadata'))), createObject('value', parameters('metadata')), createObject('value', createObject()))]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), createObject('value', parameters('nonComplianceMessages')), createObject('value', createArray()))]", + "enforcementMode": { + "value": "[parameters('enforcementMode')]" + }, + "notScopes": "[if(not(empty(parameters('notScopes'))), createObject('value', parameters('notScopes')), createObject('value', createArray()))]", + "subscriptionId": { + "value": "[parameters('subscriptionId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "overrides": "[if(not(empty(parameters('overrides'))), createObject('value', parameters('overrides')), createObject('value', createArray()))]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), createObject('value', parameters('resourceSelectors')), createObject('value', createArray()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "17106923564853756802" + }, + "name": "Policy Assignments (Resource Group scope)", + "description": "This module deploys a Policy Assignment at a Resource Group scope.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 64, + "metadata": { + "description": "Required. Specifies the name of the policy assignment. Maximum length is 64 characters for resource group scope." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. This message will be part of response in case of policy violation." + } + }, + "displayName": { + "type": "string", + "defaultValue": "", + "maxLength": 128, + "metadata": { + "description": "Optional. The display name of the policy assignment. Maximum length is 128 characters." + } + }, + "policyDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the ID of the policy definition or policy set definition being assigned." + } + }, + "parameters": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Parameters for the policy assignment if needed." + } + }, + "identity": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning 'Modify' policy definitions." + } + }, + "userAssignedIdentityId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource ID for the user assigned identity to assign to the policy assignment." + } + }, + "roleDefinitionIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs." + } + }, + "nonComplianceMessages": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The messages that describe why a resource is non-compliant with the policy." + } + }, + "enforcementMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "DoNotEnforce" + ], + "metadata": { + "description": "Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce." + } + }, + "notScopes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy excluded scopes." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "overrides": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition." + } + }, + "resourceSelectors": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location." + } + }, + "subscriptionId": { + "type": "string", + "defaultValue": "[subscription().subscriptionId]", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Optional. The Target Scope for the Policy. The name of the resource group for the policy assignment. If not provided, will use the current scope for deployment." + } + } + }, + "variables": { + "identityVar": "[if(equals(parameters('identity'), 'SystemAssigned'), createObject('type', parameters('identity')), if(equals(parameters('identity'), 'UserAssigned'), createObject('type', parameters('identity'), 'userAssignedIdentities', createObject(format('{0}', parameters('userAssignedIdentityId')), createObject())), null()))]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2022-06-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "displayName": "[if(not(empty(parameters('displayName'))), parameters('displayName'), null())]", + "metadata": "[if(not(empty(parameters('metadata'))), parameters('metadata'), null())]", + "description": "[if(not(empty(parameters('description'))), parameters('description'), null())]", + "policyDefinitionId": "[parameters('policyDefinitionId')]", + "parameters": "[parameters('parameters')]", + "nonComplianceMessages": "[if(not(empty(parameters('nonComplianceMessages'))), parameters('nonComplianceMessages'), createArray())]", + "enforcementMode": "[parameters('enforcementMode')]", + "notScopes": "[if(not(empty(parameters('notScopes'))), parameters('notScopes'), createArray())]", + "overrides": "[if(not(empty(parameters('overrides'))), parameters('overrides'), createArray())]", + "resourceSelectors": "[if(not(empty(parameters('resourceSelectors'))), parameters('resourceSelectors'), createArray())]" + }, + "identity": "[variables('identityVar')]" + }, + { + "copy": { + "name": "roleAssignment", + "count": "[length(parameters('roleDefinitionIds'))]" + }, + "condition": "[and(not(empty(parameters('roleDefinitionIds'))), equals(parameters('identity'), 'SystemAssigned'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('subscriptionId'), parameters('resourceGroupName'), parameters('roleDefinitionIds')[copyIndex()], parameters('location'), parameters('name'))]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionIds')[copyIndex()]]", + "principalId": "[reference(resourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Authorization/policyAssignments', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Policy Assignment Name." + }, + "value": "[parameters('name')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Policy Assignment principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference(resourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Policy Assignment resource ID." + }, + "value": "[resourceId('Microsoft.Authorization/policyAssignments', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the policy was assigned to." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference(resourceId('Microsoft.Authorization/policyAssignments', parameters('name')), '2022-06-01', 'full').location]" + } + } + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Policy Assignment Name." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.name.value))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Policy Assignment principal ID." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.principalId.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.principalId.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.principalId.value))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Policy Assignment resource ID." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.resourceId.value))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[if(and(empty(parameters('subscriptionId')), empty(parameters('resourceGroupName'))), reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', parameters('managementGroupId')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-MG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.location.value, if(and(not(empty(parameters('subscriptionId'))), empty(parameters('resourceGroupName'))), reference(subscriptionResourceId(parameters('subscriptionId'), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-Sub-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.location.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('{0}-PolicyAssignment-RG-Module', uniqueString(deployment().name, parameters('location')))), '2022-09-01').outputs.location.value))]" + } + } +} \ No newline at end of file diff --git a/avm/ptn/authorization/policy-assignment/modules/management-group.bicep b/avm/ptn/authorization/policy-assignment/modules/management-group.bicep new file mode 100644 index 0000000000..721899031a --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/modules/management-group.bicep @@ -0,0 +1,112 @@ +metadata name = 'Policy Assignments (Management Group scope)' +metadata description = 'This module deploys a Policy Assignment at a Management Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'managementGroup' + +@sys.description('Required. Specifies the name of the policy assignment. Maximum length is 24 characters for management group scope.') +@maxLength(24) +param name string + +@sys.description('Optional. This message will be part of response in case of policy violation.') +param description string = '' + +@sys.description('Optional. The display name of the policy assignment. Maximum length is 128 characters.') +@maxLength(128) +param displayName string = '' + +@sys.description('Required. Specifies the ID of the policy definition or policy set definition being assigned.') +param policyDefinitionId string + +@sys.description('Optional. Parameters for the policy assignment if needed.') +param parameters object = {} + +@sys.description('Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning \'Modify\' policy definitions.') +@allowed([ + 'SystemAssigned' + 'UserAssigned' + 'None' +]) +param identity string = 'SystemAssigned' + +@sys.description('Optional. The Resource ID for the user assigned identity to assign to the policy assignment.') +param userAssignedIdentityId string = '' + +@sys.description('Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition.') +param roleDefinitionIds array = [] + +@sys.description('Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs.') +param metadata object = {} + +@sys.description('Optional. The messages that describe why a resource is non-compliant with the policy.') +param nonComplianceMessages array = [] + +@sys.description('Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce.') +@allowed([ + 'Default' + 'DoNotEnforce' +]) +param enforcementMode string = 'Default' + +@sys.description('Optional. The Target Scope for the Policy. The name of the management group for the policy assignment. If not provided, will use the current scope for deployment.') +param managementGroupId string = managementGroup().name + +@sys.description('Optional. The policy excluded scopes.') +param notScopes array = [] + +@sys.description('Optional. Location for all resources.') +param location string = deployment().location + +@sys.description('Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition.') +param overrides array = [] + +@sys.description('Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location.') +param resourceSelectors array = [] + +var identityVar = identity == 'SystemAssigned' ? { + type: identity +} : identity == 'UserAssigned' ? { + type: identity + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } +} : null + +resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = { + name: name + location: location + properties: { + displayName: !empty(displayName) ? displayName : null + metadata: !empty(metadata) ? metadata : null + description: !empty(description) ? description : null + policyDefinitionId: policyDefinitionId + parameters: parameters + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } + identity: identityVar +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for roleDefinitionId in roleDefinitionIds: if (!empty(roleDefinitionIds) && identity == 'SystemAssigned') { + name: guid(managementGroupId, roleDefinitionId, location, name) + properties: { + roleDefinitionId: roleDefinitionId + principalId: policyAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +}] + +@sys.description('Policy Assignment Name.') +output name string = policyAssignment.name + +@sys.description('Policy Assignment principal ID.') +output principalId string = policyAssignment.?identity.?principalId ?? '' + +@sys.description('Policy Assignment resource ID.') +output resourceId string = policyAssignment.id + +@sys.description('The location the resource was deployed into.') +output location string = policyAssignment.location diff --git a/avm/ptn/authorization/policy-assignment/modules/resource-group.bicep b/avm/ptn/authorization/policy-assignment/modules/resource-group.bicep new file mode 100644 index 0000000000..8850bf099c --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/modules/resource-group.bicep @@ -0,0 +1,119 @@ +metadata name = 'Policy Assignments (Resource Group scope)' +metadata description = 'This module deploys a Policy Assignment at a Resource Group scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'resourceGroup' + +@sys.description('Required. Specifies the name of the policy assignment. Maximum length is 64 characters for resource group scope.') +@maxLength(64) +param name string + +@sys.description('Optional. This message will be part of response in case of policy violation.') +param description string = '' + +@sys.description('Optional. The display name of the policy assignment. Maximum length is 128 characters.') +@maxLength(128) +param displayName string = '' + +@sys.description('Required. Specifies the ID of the policy definition or policy set definition being assigned.') +param policyDefinitionId string + +@sys.description('Optional. Parameters for the policy assignment if needed.') +param parameters object = {} + +@sys.description('Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning \'Modify\' policy definitions.') +@allowed([ + 'SystemAssigned' + 'UserAssigned' + 'None' +]) +param identity string = 'SystemAssigned' + +@sys.description('Optional. The Resource ID for the user assigned identity to assign to the policy assignment.') +param userAssignedIdentityId string = '' + +@sys.description('Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition.') +param roleDefinitionIds array = [] + +@sys.description('Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs.') +param metadata object = {} + +@sys.description('Optional. The messages that describe why a resource is non-compliant with the policy.') +param nonComplianceMessages array = [] + +@sys.description('Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce.') +@allowed([ + 'Default' + 'DoNotEnforce' +]) +param enforcementMode string = 'Default' + +@sys.description('Optional. The policy excluded scopes.') +param notScopes array = [] + +@sys.description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@sys.description('Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition.') +param overrides array = [] + +@sys.description('Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location.') +param resourceSelectors array = [] + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment.') +param subscriptionId string = subscription().subscriptionId + +@sys.description('Optional. The Target Scope for the Policy. The name of the resource group for the policy assignment. If not provided, will use the current scope for deployment.') +param resourceGroupName string = resourceGroup().name + + +var identityVar = identity == 'SystemAssigned' ? { + type: identity +} : identity == 'UserAssigned' ? { + type: identity + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } +} : null + +resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = { + name: name + location: location + properties: { + displayName: !empty(displayName) ? displayName : null + metadata: !empty(metadata) ? metadata : null + description: !empty(description) ? description : null + policyDefinitionId: policyDefinitionId + parameters: parameters + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } + identity: identityVar +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for roleDefinitionId in roleDefinitionIds: if (!empty(roleDefinitionIds) && identity == 'SystemAssigned') { + name: guid(subscriptionId, resourceGroupName, roleDefinitionId, location, name) + properties: { + roleDefinitionId: roleDefinitionId + principalId: policyAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +}] + +@sys.description('Policy Assignment Name.') +output name string = policyAssignment.name + +@sys.description('Policy Assignment principal ID.') +output principalId string = policyAssignment.?identity.?principalId ?? '' + +@sys.description('Policy Assignment resource ID.') +output resourceId string = policyAssignment.id + +@sys.description('The name of the resource group the policy was assigned to.') +output resourceGroupName string = resourceGroup().name + +@sys.description('The location the resource was deployed into.') +output location string = policyAssignment.location diff --git a/avm/ptn/authorization/policy-assignment/modules/subscription.bicep b/avm/ptn/authorization/policy-assignment/modules/subscription.bicep new file mode 100644 index 0000000000..1740e32987 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/modules/subscription.bicep @@ -0,0 +1,112 @@ +metadata name = 'Policy Assignments (Subscription scope)' +metadata description = 'This module deploys a Policy Assignment at a Subscription scope.' +metadata owner = 'Azure/module-maintainers' + +targetScope = 'subscription' + +@sys.description('Required. Specifies the name of the policy assignment. Maximum length is 64 characters for subscription scope.') +@maxLength(64) +param name string + +@sys.description('Optional. This message will be part of response in case of policy violation.') +param description string = '' + +@sys.description('Optional. The display name of the policy assignment. Maximum length is 128 characters.') +@maxLength(128) +param displayName string = '' + +@sys.description('Required. Specifies the ID of the policy definition or policy set definition being assigned.') +param policyDefinitionId string + +@sys.description('Optional. Parameters for the policy assignment if needed.') +param parameters object = {} + +@sys.description('Optional. The managed identity associated with the policy assignment. Policy assignments must include a resource identity when assigning \'Modify\' policy definitions.') +@allowed([ + 'SystemAssigned' + 'UserAssigned' + 'None' +]) +param identity string = 'SystemAssigned' + +@sys.description('Optional. The Resource ID for the user assigned identity to assign to the policy assignment.') +param userAssignedIdentityId string = '' + +@sys.description('Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the identity. You need to provide either the fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition.') +param roleDefinitionIds array = [] + +@sys.description('Optional. The policy assignment metadata. Metadata is an open ended object and is typically a collection of key-value pairs.') +param metadata object = {} + +@sys.description('Optional. The messages that describe why a resource is non-compliant with the policy.') +param nonComplianceMessages array = [] + +@sys.description('Optional. The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. - Default or DoNotEnforce.') +@allowed([ + 'Default' + 'DoNotEnforce' +]) +param enforcementMode string = 'Default' + +@sys.description('Optional. The policy excluded scopes.') +param notScopes array = [] + +@sys.description('Optional. Location for all resources.') +param location string = deployment().location + +@sys.description('Optional. The policy property value override. Allows changing the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition.') +param overrides array = [] + +@sys.description('Optional. The resource selector list to filter policies by resource properties. Facilitates safe deployment practices (SDP) by enabling gradual roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location.') +param resourceSelectors array = [] + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment.') +param subscriptionId string = subscription().subscriptionId + +var identityVar = identity == 'SystemAssigned' ? { + type: identity +} : identity == 'UserAssigned' ? { + type: identity + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } +} : null + +resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = { + name: name + location: location + properties: { + displayName: !empty(displayName) ? displayName : null + metadata: !empty(metadata) ? metadata : null + description: !empty(description) ? description : null + policyDefinitionId: policyDefinitionId + parameters: parameters + nonComplianceMessages: !empty(nonComplianceMessages) ? nonComplianceMessages : [] + enforcementMode: enforcementMode + notScopes: !empty(notScopes) ? notScopes : [] + overrides: !empty(overrides) ? overrides : [] + resourceSelectors: !empty(resourceSelectors) ? resourceSelectors : [] + } + identity: identityVar +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for roleDefinitionId in roleDefinitionIds: if (!empty(roleDefinitionIds) && identity == 'SystemAssigned') { + name: guid(subscriptionId, roleDefinitionId, location, name) + properties: { + roleDefinitionId: roleDefinitionId + principalId: policyAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +}] + +@sys.description('Policy Assignment Name.') +output name string = policyAssignment.name + +@sys.description('Policy Assignment principal ID.') +output principalId string = policyAssignment.?identity.?principalId ?? '' + +@sys.description('Policy Assignment resource ID.') +output resourceId string = policyAssignment.id + +@sys.description('The location the resource was deployed into.') +output location string = policyAssignment.location diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/mg.defaults/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/mg.defaults/main.test.bicep new file mode 100644 index 0000000000..2c043ce041 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/mg.defaults/main.test.bicep @@ -0,0 +1,33 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Management Group scope)' +metadata description = 'This module deploys a Policy Assignment at a Management Group scope using minimal parameters.' + +// ========== // +// Parameters // +// ========== // + +@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 = 'apamgmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Audit VMs that do not use managed disks + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + metadata: { + assignedBy: 'Bicep' + } + location: resourceLocation + } +} diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/mg.max/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/mg.max/main.test.bicep new file mode 100644 index 0000000000..07d5b2309a --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/mg.max/main.test.bicep @@ -0,0 +1,96 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Management Group scope)' +metadata description = 'This module deploys a Policy Assignment at a Management Group scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@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 = 'apamgmax' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment.') +param subscriptionId string = '#_subscriptionId_#' + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Configure Azure Defender for SQL agents on virtual machines + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + description: '[Description] Policy Assignment at the management group scope' + displayName: '[Display Name] Policy Assignment at the management group scope' + enforcementMode: 'DoNotEnforce' + identity: 'SystemAssigned' + location: resourceLocation + managementGroupId: last(split(managementGroup().id, '/')) + metadata: { + category: 'Security' + version: '1.0' + assignedBy: 'Bicep' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + '/subscriptions/${subscriptionId}/resourceGroups/validation-rg' + ] + parameters: { + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + effect: { + value: 'Disabled' + } + } + roleDefinitionIds: [ + '/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor role + ] + overrides: [ + { + kind: 'policyEffect' + value: 'Disabled' + selectors: [ + { + kind: 'policyDefinitionReferenceId' + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + } + ] + } + ] + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + kind: 'resourceType' + in: [ + 'Microsoft.Compute/virtualMachines' + ] + } + { + kind: 'resourceLocation' + in: [ + 'westeurope' + ] + } + ] + } + ] + } +} diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/rg.defaults/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.defaults/main.test.bicep new file mode 100644 index 0000000000..ff1eaeb6a7 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.defaults/main.test.bicep @@ -0,0 +1,57 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Resource Group)' +metadata description = 'This module deploys a Policy Assignment at a Resource Group scope using minimal parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.policyassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'apargmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module resourceGroupDeploy 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Audit VMs that do not use managed disks + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + metadata: { + assignedBy: 'Bicep' + } + location: resourceLocation + resourceGroupName: resourceGroupDeploy.outputs.name + subscriptionId: subscriptionId + } +} diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/dependencies.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/dependencies.bicep new file mode 100644 index 0000000000..f4151d61c7 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/dependencies.bicep @@ -0,0 +1,33 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + accessPolicies: [] + } +} + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/main.test.bicep new file mode 100644 index 0000000000..9dabc3b7a1 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/rg.max/main.test.bicep @@ -0,0 +1,127 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Resource Group)' +metadata description = 'This module deploys a Policy Assignment at a Resource Group scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.policyassignments-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'apargmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Optional. Subscription ID of the subscription to assign the RBAC role to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided RBAC role to the subscription.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module resourceGroupDeploy 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup(subscriptionId, resourceGroupName) + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Configure Azure Defender for SQL agents on virtual machines + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + description: '[Description] Policy Assignment at the resource group scope' + displayName: '[Display Name] Policy Assignment at the resource group scope' + enforcementMode: 'DoNotEnforce' + identity: 'UserAssigned' + location: resourceLocation + metadata: { + category: 'Security' + version: '1.0' + assignedBy: 'Bicep' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + nestedDependencies.outputs.keyVaultResourceId + ] + parameters: { + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + effect: { + value: 'Disabled' + } + } + resourceGroupName: resourceGroupDeploy.outputs.name + roleDefinitionIds: [ + '/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + ] + overrides: [ + { + kind: 'policyEffect' + value: 'Disabled' + selectors: [ + { + kind: 'policyDefinitionReferenceId' + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + } + ] + } + ] + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + kind: 'resourceType' + in: [ + 'Microsoft.Compute/virtualMachines' + ] + } + { + kind: 'resourceLocation' + in: [ + 'westeurope' + ] + } + ] + } + ] + subscriptionId: subscriptionId + userAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + } +} diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/sub.defaults/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.defaults/main.test.bicep new file mode 100644 index 0000000000..1cd7910296 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.defaults/main.test.bicep @@ -0,0 +1,39 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Subscription)' +metadata description = 'This module deploys a Policy Assignment at a Subscription scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@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 = 'apasubmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment.') +param subscriptionId string = '#_subscriptionId_#' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Audit VMs that do not use managed disks + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d' + subscriptionId: subscriptionId + metadata: { + category: 'Security' + version: '1.0' + assignedBy: 'Bicep' + } + location: resourceLocation + } +} diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/dependencies.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/dependencies.bicep new file mode 100644 index 0000000000..71b8f3cee1 --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/interim.dependencies.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/interim.dependencies.bicep new file mode 100644 index 0000000000..119660120c --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/interim.dependencies.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Required. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +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: { + managedIdentityName: managedIdentityName + location: location + } +} + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = nestedDependencies.outputs.managedIdentityResourceId diff --git a/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/main.test.bicep b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/main.test.bicep new file mode 100644 index 0000000000..0d90ce4c8c --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/tests/e2e/sub.max/main.test.bicep @@ -0,0 +1,126 @@ +targetScope = 'managementGroup' +metadata name = 'Policy Assignments (Subscription)' +metadata description = 'This module deploys a Policy Assignment at a Subscription scope using common parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-authorization.policyassignments-${serviceShort}-rg' + +@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 = 'apasubmax' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@sys.description('Optional. The Target Scope for the Policy. The subscription ID of the subscription for the policy assignment. If not provided, will use the current scope for deployment.') +param subscriptionId string = '#_subscriptionId_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module resourceGroup 'br/public:avm/res/resources/resource-group:0.2.3' ={ + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-resourceGroup' + params: { + name: resourceGroupName + location: resourceLocation + } +} + +module nestedDependencies 'interim.dependencies.bicep' = { + scope: subscription('${subscriptionId}') + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + resourceGroupName: resourceGroupName + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + //Configure Azure Defender for SQL agents on virtual machines + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/39a366e6-fdde-4f41-bbf8-3757f46d1611' + description: '[Description] Policy Assignment at the subscription scope' + displayName: '[Display Name] Policy Assignment at the subscription scope' + enforcementMode: 'DoNotEnforce' + identity: 'UserAssigned' + location: resourceLocation + metadata: { + category: 'Security' + version: '1.0' + assignedBy: 'Bicep' + } + nonComplianceMessages: [ + { + message: 'Violated Policy Assignment - This is a Non Compliance Message' + } + ] + notScopes: [ + '/subscriptions/${subscriptionId}/resourceGroups/validation-rg' + ] + parameters: { + enableCollectionOfSqlQueriesForSecurityResearch: { + value: false + } + effect: { + value: 'Disabled' + } + } + roleDefinitionIds: [ + '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + ] + overrides: [ + { + kind: 'policyEffect' + value: 'Disabled' + selectors: [ + { + kind: 'policyDefinitionReferenceId' + in: [ + 'ASC_DeployAzureDefenderForSqlAdvancedThreatProtectionWindowsAgent' + 'ASC_DeployAzureDefenderForSqlVulnerabilityAssessmentWindowsAgent' + ] + } + ] + } + ] + resourceSelectors: [ + { + name: 'resourceSelector-test' + selectors: [ + { + kind: 'resourceType' + in: [ + 'Microsoft.Compute/virtualMachines' + ] + } + { + kind: 'resourceLocation' + in: [ + 'westeurope' + ] + } + ] + } + ] + subscriptionId: subscriptionId + userAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + } +} diff --git a/avm/ptn/authorization/policy-assignment/version.json b/avm/ptn/authorization/policy-assignment/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/ptn/authorization/policy-assignment/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 77b523b1f592ca3b27ef51411dbe883309afd813 Mon Sep 17 00:00:00 2001 From: Buddy <38195643+tsc-buddy@users.noreply.github.com> Date: Sat, 20 Apr 2024 03:39:53 +1200 Subject: [PATCH 22/32] fix: 1507 linux asp (#1711) ## Description Fixes [issue 1507](https://github.com/Azure/bicep-registry-modules/issues/1507) logged relating to Linux ASP selection and the reserved property. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.web.serverfarm](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=fix%2F1507-linux-asp)](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/web/serverfarm/README.md | 26 +++++++++---------- avm/res/web/serverfarm/main.bicep | 2 +- avm/res/web/serverfarm/main.json | 6 ++--- .../serverfarm/tests/e2e/max/main.test.bicep | 12 ++++----- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/avm/res/web/serverfarm/README.md b/avm/res/web/serverfarm/README.md index 7788f77636..1a3e585904 100644 --- a/avm/res/web/serverfarm/README.md +++ b/avm/res/web/serverfarm/README.md @@ -112,11 +112,11 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { // Required parameters name: 'wsfmax001' sku: { - capacity: 3 - family: 'P' - name: 'P1v3' - size: 'P1v3' - tier: 'Premium' + capacity: 1 + family: 'S' + name: 'S1' + size: 'S1' + tier: 'Standard' } // Non-required parameters diagnosticSettings: [ @@ -162,7 +162,7 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { 'hidden-title': 'This is visible in the resource name' Role: 'DeploymentValidation' } - zoneRedundant: true + zoneRedundant: false } } ``` @@ -185,11 +185,11 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { }, "sku": { "value": { - "capacity": 3, - "family": "P", - "name": "P1v3", - "size": "P1v3", - "tier": "Premium" + "capacity": 1, + "family": "S", + "name": "S1", + "size": "S1", + "tier": "Standard" } }, // Non-required parameters @@ -251,7 +251,7 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { } }, "zoneRedundant": { - "value": true + "value": false } } } @@ -439,7 +439,7 @@ Defaults to false when creating Windows/app App Service Plan. Required if creati - Required: No - Type: bool -- Default: `False` +- Default: `[equals(parameters('kind'), 'Linux')]` ### Parameter: `appServiceEnvironmentId` diff --git a/avm/res/web/serverfarm/main.bicep b/avm/res/web/serverfarm/main.bicep index 7cff682e16..5deb3fe099 100644 --- a/avm/res/web/serverfarm/main.bicep +++ b/avm/res/web/serverfarm/main.bicep @@ -35,7 +35,7 @@ param location string = resourceGroup().location param kind string = 'App' @description('Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true.') -param reserved bool = false +param reserved bool = (kind == 'Linux') @description('Optional. The Resource ID of the App Service Environment to use for the App Service Plan.') param appServiceEnvironmentId string = '' diff --git a/avm/res/web/serverfarm/main.json b/avm/res/web/serverfarm/main.json index bf40400bed..68999ed0f2 100644 --- a/avm/res/web/serverfarm/main.json +++ b/avm/res/web/serverfarm/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12495804892051718765" + "version": "0.26.170.59819", + "templateHash": "16669238654401736455" }, "name": "App Service Plan", "description": "This module deploys an App Service Plan.", @@ -231,7 +231,7 @@ }, "reserved": { "type": "bool", - "defaultValue": false, + "defaultValue": "[equals(parameters('kind'), 'Linux')]", "metadata": { "description": "Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true." } diff --git a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep index 5451356bd1..f7e0fb3a88 100644 --- a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep @@ -68,14 +68,14 @@ module testDeployment '../../../main.bicep' = [ name: '${namePrefix}${serviceShort}001' location: tempLocation sku: { - name: 'P1v3' - tier: 'Premium' - size: 'P1v3' - family: 'P' - capacity: 3 + name: 'S1' + tier: 'Standard' + size: 'S1' + family: 'S' + capacity: 1 } perSiteScaling: true - zoneRedundant: true + zoneRedundant: false kind: 'App' lock: { name: 'lock' From cc66c2b41a9438dbaed78aa4501124f775029a36 Mon Sep 17 00:00:00 2001 From: Ilhaan Rasheed Date: Fri, 19 Apr 2024 08:41:25 -0700 Subject: [PATCH 23/32] feat: `avm/res/network/application-gateway` (#835) New module for Application Gateway migrated from CARML [![avm.res.network.application-gateway](https://github.com/ilhaan/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml/badge.svg?branch=avm-application-gateway)](https://github.com/ilhaan/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml) --------- Co-authored-by: Alexander Sehr Co-authored-by: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 2 +- .../avm.res.network.application-gateway.yml | 86 + avm/res/network/application-gateway/README.md | 3257 +++++++++++++++++ .../network/application-gateway/main.bicep | 616 ++++ avm/res/network/application-gateway/main.json | 1633 +++++++++ .../tests/e2e/defaults/dependencies.bicep | 60 + .../tests/e2e/defaults/main.test.bicep | 146 + .../tests/e2e/max/dependencies.bicep | 154 + .../tests/e2e/max/main.test.bicep | 524 +++ .../tests/e2e/waf-aligned/dependencies.bicep | 154 + .../tests/e2e/waf-aligned/main.test.bicep | 486 +++ .../network/application-gateway/version.json | 7 + 13 files changed, 7125 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/avm.res.network.application-gateway.yml create mode 100644 avm/res/network/application-gateway/README.md create mode 100644 avm/res/network/application-gateway/main.bicep create mode 100644 avm/res/network/application-gateway/main.json create mode 100644 avm/res/network/application-gateway/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/network/application-gateway/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/network/application-gateway/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/network/application-gateway/tests/e2e/max/main.test.bicep create mode 100644 avm/res/network/application-gateway/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/network/application-gateway/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/network/application-gateway/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2851e9058b..e04de640ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -78,7 +78,7 @@ #/avm/res/managed-services/registration-definition/ @Azure/avm-res-managedservices-registrationdefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/management/management-group/ @Azure/avm-res-management-managementgroup-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/net-app/net-app-account/ @Azure/avm-res-netapp-netappaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/network/application-gateway/ @Azure/avm-res-network-applicationgateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/application-gateway/ @Azure/avm-res-network-applicationgateway-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/network/application-gateway-web-application-firewall-policy/ @Azure/avm-res-network-applicationgatewaywebapplicationfirewallpolicy-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/network/application-security-group/ @Azure/avm-res-network-applicationsecuritygroup-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/network/azure-firewall/ @Azure/avm-res-network-azurefirewall-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 6c1a8d60bf..40c763986f 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -111,7 +111,7 @@ body: # - "avm/res/managed-services/registration-definition" - "avm/res/management/management-group" - "avm/res/net-app/net-app-account" - # - "avm/res/network/application-gateway" + - "avm/res/network/application-gateway" - "avm/res/network/application-gateway-web-application-firewall-policy" - "avm/res/network/application-security-group" - "avm/res/network/azure-firewall" diff --git a/.github/workflows/avm.res.network.application-gateway.yml b/.github/workflows/avm.res.network.application-gateway.yml new file mode 100644 index 0000000000..e55979ee5e --- /dev/null +++ b/.github/workflows/avm.res.network.application-gateway.yml @@ -0,0 +1,86 @@ +name: "avm.res.network.application-gateway" + +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 + - avm-application-gateway + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.network.application-gateway.yml" + - "avm/res/network/application-gateway/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/network/application-gateway" + workflowPath: ".github/workflows/avm.res.network.application-gateway.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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/network/application-gateway/README.md b/avm/res/network/application-gateway/README.md new file mode 100644 index 0000000000..f3faed0358 --- /dev/null +++ b/avm/res/network/application-gateway/README.md @@ -0,0 +1,3257 @@ +# Network Application Gateways `[Microsoft.Network/applicationGateways]` + +This module deploys a Network Application Gateway. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## 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.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/applicationGateways` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/applicationGateways) | +| `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) | + +## 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/network/application-gateway:`. + +- [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 applicationGateway 'br/public:avm/res/network/application-gateway:' = { + name: 'applicationGatewayDeployment' + params: { + // Required parameters + name: '' + // Non-required parameters + backendAddressPools: [ + { + name: 'backendAddressPool1' + } + ] + backendHttpSettingsCollection: [ + { + name: 'backendHttpSettings1' + properties: { + cookieBasedAffinity: 'Disabled' + port: 80 + protocol: 'Http' + } + } + ] + frontendIPConfigurations: [ + { + name: 'frontendIPConfig1' + properties: { + publicIPAddress: { + id: '' + } + } + } + ] + frontendPorts: [ + { + name: 'frontendPort1' + properties: { + port: 80 + } + } + ] + gatewayIPConfigurations: [ + { + name: 'publicIPConfig1' + properties: { + subnet: { + id: '' + } + } + } + ] + httpListeners: [ + { + name: 'httpListener1' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostName: 'www.contoso.com' + protocol: 'Http' + } + } + ] + location: '' + requestRoutingRules: [ + { + name: 'requestRoutingRule1' + properties: { + backendAddressPool: { + id: '' + } + backendHttpSettings: { + id: '' + } + httpListener: { + id: '' + } + priority: 100 + ruleType: 'Basic' + } + } + ] + webApplicationFirewallConfiguration: { + enabled: false + } + zones: [ + '1' + '2' + '3' + ] + } +} +``` + +
+

+ +

+ +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": "" + }, + // Non-required parameters + "backendAddressPools": { + "value": [ + { + "name": "backendAddressPool1" + } + ] + }, + "backendHttpSettingsCollection": { + "value": [ + { + "name": "backendHttpSettings1", + "properties": { + "cookieBasedAffinity": "Disabled", + "port": 80, + "protocol": "Http" + } + } + ] + }, + "frontendIPConfigurations": { + "value": [ + { + "name": "frontendIPConfig1", + "properties": { + "publicIPAddress": { + "id": "" + } + } + } + ] + }, + "frontendPorts": { + "value": [ + { + "name": "frontendPort1", + "properties": { + "port": 80 + } + } + ] + }, + "gatewayIPConfigurations": { + "value": [ + { + "name": "publicIPConfig1", + "properties": { + "subnet": { + "id": "" + } + } + } + ] + }, + "httpListeners": { + "value": [ + { + "name": "httpListener1", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostName": "www.contoso.com", + "protocol": "Http" + } + } + ] + }, + "location": { + "value": "" + }, + "requestRoutingRules": { + "value": [ + { + "name": "requestRoutingRule1", + "properties": { + "backendAddressPool": { + "id": "" + }, + "backendHttpSettings": { + "id": "" + }, + "httpListener": { + "id": "" + }, + "priority": 100, + "ruleType": "Basic" + } + } + ] + }, + "webApplicationFirewallConfiguration": { + "value": { + "enabled": false + } + }, + "zones": { + "value": [ + "1", + "2", + "3" + ] + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module applicationGateway 'br/public:avm/res/network/application-gateway:' = { + name: 'applicationGatewayDeployment' + params: { + // Required parameters + name: '' + // Non-required parameters + backendAddressPools: [ + { + name: 'appServiceBackendPool' + properties: { + backendAddresses: [ + { + fqdn: 'aghapp.azurewebsites.net' + } + ] + } + } + { + name: 'privateVmBackendPool' + properties: { + backendAddresses: [ + { + ipAddress: '10.0.0.4' + } + ] + } + } + ] + backendHttpSettingsCollection: [ + { + name: 'appServiceBackendHttpsSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: true + port: 443 + protocol: 'Https' + requestTimeout: 30 + } + } + { + name: 'privateVmHttpSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: false + port: 80 + probe: { + id: '' + } + protocol: 'Http' + requestTimeout: 30 + } + } + ] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + enableHttp2: true + enableTelemetry: '' + frontendIPConfigurations: [ + { + name: 'private' + properties: { + privateIPAddress: '10.0.0.20' + privateIPAllocationMethod: 'Static' + subnet: { + id: '' + } + } + } + { + name: 'public' + properties: { + privateIPAllocationMethod: 'Dynamic' + privateLinkConfiguration: { + id: '' + } + publicIPAddress: { + id: '' + } + } + } + ] + frontendPorts: [ + { + name: 'port443' + properties: { + port: 443 + } + } + { + name: 'port4433' + properties: { + port: 4433 + } + } + { + name: 'port80' + properties: { + port: 80 + } + } + { + name: 'port8080' + properties: { + port: 8080 + } + } + ] + gatewayIPConfigurations: [ + { + name: 'apw-ip-configuration' + properties: { + subnet: { + id: '' + } + } + } + ] + httpListeners: [ + { + name: 'public443' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '' + } + } + } + { + name: 'private4433' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '' + } + } + } + { + name: 'httpRedirect80' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + { + name: 'httpRedirect8080' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + userAssignedResourceIds: [ + '' + ] + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + service: 'public' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + privateLinkConfigurations: [ + { + id: '' + name: 'pvtlink01' + properties: { + ipConfigurations: [ + { + id: '' + name: 'privateLinkIpConfig1' + properties: { + primary: false + privateIPAllocationMethod: 'Dynamic' + subnet: { + id: '' + } + } + } + ] + } + } + ] + probes: [ + { + name: 'privateVmHttpSettingProbe' + properties: { + host: '10.0.0.4' + interval: 60 + match: { + statusCodes: [ + '200' + '401' + ] + } + minServers: 3 + path: '/' + pickHostNameFromBackendHttpSettings: false + protocol: 'Http' + timeout: 15 + unhealthyThreshold: 5 + } + } + ] + redirectConfigurations: [ + { + name: 'httpRedirect80' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '' + } + ] + targetListener: { + id: '' + } + } + } + { + name: 'httpRedirect8080' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '' + } + ] + targetListener: { + id: '' + } + } + } + ] + requestRoutingRules: [ + { + name: 'public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting' + properties: { + backendAddressPool: { + id: '' + } + backendHttpSettings: { + id: '' + } + httpListener: { + id: '' + } + priority: 200 + ruleType: 'Basic' + } + } + { + name: 'private4433-privateVmHttpSetting-privateVmHttpSetting' + properties: { + backendAddressPool: { + id: '' + } + backendHttpSettings: { + id: '' + } + httpListener: { + id: '' + } + priority: 250 + ruleType: 'Basic' + } + } + { + name: 'httpRedirect80-public443' + properties: { + httpListener: { + id: '' + } + priority: 300 + redirectConfiguration: { + id: '' + } + ruleType: 'Basic' + } + } + { + name: 'httpRedirect8080-private4433' + properties: { + httpListener: { + id: '' + } + priority: 350 + redirectConfiguration: { + id: '' + } + rewriteRuleSet: { + id: '' + } + ruleType: 'Basic' + } + } + ] + rewriteRuleSets: [ + { + id: '' + name: 'customRewrite' + properties: { + rewriteRules: [ + { + actionSet: { + requestHeaderConfigurations: [ + { + headerName: 'Content-Type' + headerValue: 'JSON' + } + { + headerName: 'someheader' + } + ] + responseHeaderConfigurations: [] + } + conditions: [] + name: 'NewRewrite' + ruleSequence: 100 + } + ] + } + } + ] + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + ] + sku: 'WAF_v2' + sslCertificates: [ + { + name: 'az-apgw-x-001-ssl-certificate' + properties: { + keyVaultSecretId: '' + } + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + webApplicationFirewallConfiguration: { + disabledRuleGroups: [ + { + ruleGroupName: 'Known-CVEs' + } + { + ruleGroupName: 'REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION' + } + { + ruleGroupName: 'REQUEST-941-APPLICATION-ATTACK-XSS' + } + ] + enabled: true + exclusions: [ + { + matchVariable: 'RequestHeaderNames' + selector: 'hola' + selectorMatchOperator: 'StartsWith' + } + ] + fileUploadLimitInMb: 100 + firewallMode: 'Detection' + maxRequestBodySizeInKb: 128 + requestBodyCheck: true + ruleSetType: 'OWASP' + ruleSetVersion: '3.0' + } + zones: [ + '1' + '2' + '3' + ] + } +} +``` + +
+

+ +

+ +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": "" + }, + // Non-required parameters + "backendAddressPools": { + "value": [ + { + "name": "appServiceBackendPool", + "properties": { + "backendAddresses": [ + { + "fqdn": "aghapp.azurewebsites.net" + } + ] + } + }, + { + "name": "privateVmBackendPool", + "properties": { + "backendAddresses": [ + { + "ipAddress": "10.0.0.4" + } + ] + } + } + ] + }, + "backendHttpSettingsCollection": { + "value": [ + { + "name": "appServiceBackendHttpsSetting", + "properties": { + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": true, + "port": 443, + "protocol": "Https", + "requestTimeout": 30 + } + }, + { + "name": "privateVmHttpSetting", + "properties": { + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "probe": { + "id": "" + }, + "protocol": "Http", + "requestTimeout": 30 + } + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "enableHttp2": { + "value": true + }, + "enableTelemetry": { + "value": "" + }, + "frontendIPConfigurations": { + "value": [ + { + "name": "private", + "properties": { + "privateIPAddress": "10.0.0.20", + "privateIPAllocationMethod": "Static", + "subnet": { + "id": "" + } + } + }, + { + "name": "public", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "privateLinkConfiguration": { + "id": "" + }, + "publicIPAddress": { + "id": "" + } + } + } + ] + }, + "frontendPorts": { + "value": [ + { + "name": "port443", + "properties": { + "port": 443 + } + }, + { + "name": "port4433", + "properties": { + "port": 4433 + } + }, + { + "name": "port80", + "properties": { + "port": 80 + } + }, + { + "name": "port8080", + "properties": { + "port": 8080 + } + } + ] + }, + "gatewayIPConfigurations": { + "value": [ + { + "name": "apw-ip-configuration", + "properties": { + "subnet": { + "id": "" + } + } + } + ] + }, + "httpListeners": { + "value": [ + { + "name": "public443", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "https", + "requireServerNameIndication": false, + "sslCertificate": { + "id": "" + } + } + }, + { + "name": "private4433", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "https", + "requireServerNameIndication": false, + "sslCertificate": { + "id": "" + } + } + }, + { + "name": "httpRedirect80", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "Http", + "requireServerNameIndication": false + } + }, + { + "name": "httpRedirect8080", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "Http", + "requireServerNameIndication": false + } + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "" + ] + } + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "service": "public", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + ] + }, + "privateLinkConfigurations": { + "value": [ + { + "id": "", + "name": "pvtlink01", + "properties": { + "ipConfigurations": [ + { + "id": "", + "name": "privateLinkIpConfig1", + "properties": { + "primary": false, + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "" + } + } + } + ] + } + } + ] + }, + "probes": { + "value": [ + { + "name": "privateVmHttpSettingProbe", + "properties": { + "host": "10.0.0.4", + "interval": 60, + "match": { + "statusCodes": [ + "200", + "401" + ] + }, + "minServers": 3, + "path": "/", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Http", + "timeout": 15, + "unhealthyThreshold": 5 + } + } + ] + }, + "redirectConfigurations": { + "value": [ + { + "name": "httpRedirect80", + "properties": { + "includePath": true, + "includeQueryString": true, + "redirectType": "Permanent", + "requestRoutingRules": [ + { + "id": "" + } + ], + "targetListener": { + "id": "" + } + } + }, + { + "name": "httpRedirect8080", + "properties": { + "includePath": true, + "includeQueryString": true, + "redirectType": "Permanent", + "requestRoutingRules": [ + { + "id": "" + } + ], + "targetListener": { + "id": "" + } + } + } + ] + }, + "requestRoutingRules": { + "value": [ + { + "name": "public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting", + "properties": { + "backendAddressPool": { + "id": "" + }, + "backendHttpSettings": { + "id": "" + }, + "httpListener": { + "id": "" + }, + "priority": 200, + "ruleType": "Basic" + } + }, + { + "name": "private4433-privateVmHttpSetting-privateVmHttpSetting", + "properties": { + "backendAddressPool": { + "id": "" + }, + "backendHttpSettings": { + "id": "" + }, + "httpListener": { + "id": "" + }, + "priority": 250, + "ruleType": "Basic" + } + }, + { + "name": "httpRedirect80-public443", + "properties": { + "httpListener": { + "id": "" + }, + "priority": 300, + "redirectConfiguration": { + "id": "" + }, + "ruleType": "Basic" + } + }, + { + "name": "httpRedirect8080-private4433", + "properties": { + "httpListener": { + "id": "" + }, + "priority": 350, + "redirectConfiguration": { + "id": "" + }, + "rewriteRuleSet": { + "id": "" + }, + "ruleType": "Basic" + } + } + ] + }, + "rewriteRuleSets": { + "value": [ + { + "id": "", + "name": "customRewrite", + "properties": { + "rewriteRules": [ + { + "actionSet": { + "requestHeaderConfigurations": [ + { + "headerName": "Content-Type", + "headerValue": "JSON" + }, + { + "headerName": "someheader" + } + ], + "responseHeaderConfigurations": [] + }, + "conditions": [], + "name": "NewRewrite", + "ruleSequence": 100 + } + ] + } + } + ] + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + } + ] + }, + "sku": { + "value": "WAF_v2" + }, + "sslCertificates": { + "value": [ + { + "name": "az-apgw-x-001-ssl-certificate", + "properties": { + "keyVaultSecretId": "" + } + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + }, + "webApplicationFirewallConfiguration": { + "value": { + "disabledRuleGroups": [ + { + "ruleGroupName": "Known-CVEs" + }, + { + "ruleGroupName": "REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION" + }, + { + "ruleGroupName": "REQUEST-941-APPLICATION-ATTACK-XSS" + } + ], + "enabled": true, + "exclusions": [ + { + "matchVariable": "RequestHeaderNames", + "selector": "hola", + "selectorMatchOperator": "StartsWith" + } + ], + "fileUploadLimitInMb": 100, + "firewallMode": "Detection", + "maxRequestBodySizeInKb": 128, + "requestBodyCheck": true, + "ruleSetType": "OWASP", + "ruleSetVersion": "3.0" + } + }, + "zones": { + "value": [ + "1", + "2", + "3" + ] + } + } +} +``` + +
+

+ +### 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 applicationGateway 'br/public:avm/res/network/application-gateway:' = { + name: 'applicationGatewayDeployment' + params: { + // Required parameters + name: '' + // Non-required parameters + backendAddressPools: [ + { + name: 'appServiceBackendPool' + properties: { + backendAddresses: [ + { + fqdn: 'aghapp.azurewebsites.net' + } + ] + } + } + { + name: 'privateVmBackendPool' + properties: { + backendAddresses: [ + { + ipAddress: '10.0.0.4' + } + ] + } + } + ] + backendHttpSettingsCollection: [ + { + name: 'appServiceBackendHttpsSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: true + port: 443 + protocol: 'Https' + requestTimeout: 30 + } + } + { + name: 'privateVmHttpSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: false + port: 80 + probe: { + id: '' + } + protocol: 'Http' + requestTimeout: 30 + } + } + ] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + enableHttp2: true + enableTelemetry: '' + frontendIPConfigurations: [ + { + name: 'private' + properties: { + privateIPAddress: '10.0.0.20' + privateIPAllocationMethod: 'Static' + subnet: { + id: '' + } + } + } + { + name: 'public' + properties: { + privateIPAllocationMethod: 'Dynamic' + privateLinkConfiguration: { + id: '' + } + publicIPAddress: { + id: '' + } + } + } + ] + frontendPorts: [ + { + name: 'port443' + properties: { + port: 443 + } + } + { + name: 'port4433' + properties: { + port: 4433 + } + } + { + name: 'port80' + properties: { + port: 80 + } + } + { + name: 'port8080' + properties: { + port: 8080 + } + } + ] + gatewayIPConfigurations: [ + { + name: 'apw-ip-configuration' + properties: { + subnet: { + id: '' + } + } + } + ] + httpListeners: [ + { + name: 'public443' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '' + } + } + } + { + name: 'private4433' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '' + } + } + } + { + name: 'httpRedirect80' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + { + name: 'httpRedirect8080' + properties: { + frontendIPConfiguration: { + id: '' + } + frontendPort: { + id: '' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + userAssignedResourceIds: [ + '' + ] + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + service: 'public' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + privateLinkConfigurations: [ + { + id: '' + name: 'pvtlink01' + properties: { + ipConfigurations: [ + { + id: '' + name: 'privateLinkIpConfig1' + properties: { + primary: false + privateIPAllocationMethod: 'Dynamic' + subnet: { + id: '' + } + } + } + ] + } + } + ] + probes: [ + { + name: 'privateVmHttpSettingProbe' + properties: { + host: '10.0.0.4' + interval: 60 + match: { + statusCodes: [ + '200' + '401' + ] + } + minServers: 3 + path: '/' + pickHostNameFromBackendHttpSettings: false + protocol: 'Http' + timeout: 15 + unhealthyThreshold: 5 + } + } + ] + redirectConfigurations: [ + { + name: 'httpRedirect80' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '' + } + ] + targetListener: { + id: '' + } + } + } + { + name: 'httpRedirect8080' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '' + } + ] + targetListener: { + id: '' + } + } + } + ] + requestRoutingRules: [ + { + name: 'public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting' + properties: { + backendAddressPool: { + id: '' + } + backendHttpSettings: { + id: '' + } + httpListener: { + id: '' + } + priority: 200 + ruleType: 'Basic' + } + } + { + name: 'private4433-privateVmHttpSetting-privateVmHttpSetting' + properties: { + backendAddressPool: { + id: '' + } + backendHttpSettings: { + id: '' + } + httpListener: { + id: '' + } + priority: 250 + ruleType: 'Basic' + } + } + { + name: 'httpRedirect80-public443' + properties: { + httpListener: { + id: '' + } + priority: 300 + redirectConfiguration: { + id: '' + } + ruleType: 'Basic' + } + } + { + name: 'httpRedirect8080-private4433' + properties: { + httpListener: { + id: '' + } + priority: 350 + redirectConfiguration: { + id: '' + } + rewriteRuleSet: { + id: '' + } + ruleType: 'Basic' + } + } + ] + rewriteRuleSets: [ + { + id: '' + name: 'customRewrite' + properties: { + rewriteRules: [ + { + actionSet: { + requestHeaderConfigurations: [ + { + headerName: 'Content-Type' + headerValue: 'JSON' + } + { + headerName: 'someheader' + } + ] + responseHeaderConfigurations: [] + } + conditions: [] + name: 'NewRewrite' + ruleSequence: 100 + } + ] + } + } + ] + sku: 'WAF_v2' + sslCertificates: [ + { + name: 'az-apgw-x-001-ssl-certificate' + properties: { + keyVaultSecretId: '' + } + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + webApplicationFirewallConfiguration: { + enabled: true + fileUploadLimitInMb: 100 + firewallMode: 'Prevention' + maxRequestBodySizeInKb: 128 + requestBodyCheck: true + ruleSetType: 'OWASP' + ruleSetVersion: '3.0' + } + zones: [ + '1' + '2' + '3' + ] + } +} +``` + +
+

+ +

+ +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": "" + }, + // Non-required parameters + "backendAddressPools": { + "value": [ + { + "name": "appServiceBackendPool", + "properties": { + "backendAddresses": [ + { + "fqdn": "aghapp.azurewebsites.net" + } + ] + } + }, + { + "name": "privateVmBackendPool", + "properties": { + "backendAddresses": [ + { + "ipAddress": "10.0.0.4" + } + ] + } + } + ] + }, + "backendHttpSettingsCollection": { + "value": [ + { + "name": "appServiceBackendHttpsSetting", + "properties": { + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": true, + "port": 443, + "protocol": "Https", + "requestTimeout": 30 + } + }, + { + "name": "privateVmHttpSetting", + "properties": { + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "probe": { + "id": "" + }, + "protocol": "Http", + "requestTimeout": 30 + } + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "enableHttp2": { + "value": true + }, + "enableTelemetry": { + "value": "" + }, + "frontendIPConfigurations": { + "value": [ + { + "name": "private", + "properties": { + "privateIPAddress": "10.0.0.20", + "privateIPAllocationMethod": "Static", + "subnet": { + "id": "" + } + } + }, + { + "name": "public", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "privateLinkConfiguration": { + "id": "" + }, + "publicIPAddress": { + "id": "" + } + } + } + ] + }, + "frontendPorts": { + "value": [ + { + "name": "port443", + "properties": { + "port": 443 + } + }, + { + "name": "port4433", + "properties": { + "port": 4433 + } + }, + { + "name": "port80", + "properties": { + "port": 80 + } + }, + { + "name": "port8080", + "properties": { + "port": 8080 + } + } + ] + }, + "gatewayIPConfigurations": { + "value": [ + { + "name": "apw-ip-configuration", + "properties": { + "subnet": { + "id": "" + } + } + } + ] + }, + "httpListeners": { + "value": [ + { + "name": "public443", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "https", + "requireServerNameIndication": false, + "sslCertificate": { + "id": "" + } + } + }, + { + "name": "private4433", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "https", + "requireServerNameIndication": false, + "sslCertificate": { + "id": "" + } + } + }, + { + "name": "httpRedirect80", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "Http", + "requireServerNameIndication": false + } + }, + { + "name": "httpRedirect8080", + "properties": { + "frontendIPConfiguration": { + "id": "" + }, + "frontendPort": { + "id": "" + }, + "hostNames": [], + "protocol": "Http", + "requireServerNameIndication": false + } + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "" + ] + } + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "service": "public", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + ] + }, + "privateLinkConfigurations": { + "value": [ + { + "id": "", + "name": "pvtlink01", + "properties": { + "ipConfigurations": [ + { + "id": "", + "name": "privateLinkIpConfig1", + "properties": { + "primary": false, + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "" + } + } + } + ] + } + } + ] + }, + "probes": { + "value": [ + { + "name": "privateVmHttpSettingProbe", + "properties": { + "host": "10.0.0.4", + "interval": 60, + "match": { + "statusCodes": [ + "200", + "401" + ] + }, + "minServers": 3, + "path": "/", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Http", + "timeout": 15, + "unhealthyThreshold": 5 + } + } + ] + }, + "redirectConfigurations": { + "value": [ + { + "name": "httpRedirect80", + "properties": { + "includePath": true, + "includeQueryString": true, + "redirectType": "Permanent", + "requestRoutingRules": [ + { + "id": "" + } + ], + "targetListener": { + "id": "" + } + } + }, + { + "name": "httpRedirect8080", + "properties": { + "includePath": true, + "includeQueryString": true, + "redirectType": "Permanent", + "requestRoutingRules": [ + { + "id": "" + } + ], + "targetListener": { + "id": "" + } + } + } + ] + }, + "requestRoutingRules": { + "value": [ + { + "name": "public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting", + "properties": { + "backendAddressPool": { + "id": "" + }, + "backendHttpSettings": { + "id": "" + }, + "httpListener": { + "id": "" + }, + "priority": 200, + "ruleType": "Basic" + } + }, + { + "name": "private4433-privateVmHttpSetting-privateVmHttpSetting", + "properties": { + "backendAddressPool": { + "id": "" + }, + "backendHttpSettings": { + "id": "" + }, + "httpListener": { + "id": "" + }, + "priority": 250, + "ruleType": "Basic" + } + }, + { + "name": "httpRedirect80-public443", + "properties": { + "httpListener": { + "id": "" + }, + "priority": 300, + "redirectConfiguration": { + "id": "" + }, + "ruleType": "Basic" + } + }, + { + "name": "httpRedirect8080-private4433", + "properties": { + "httpListener": { + "id": "" + }, + "priority": 350, + "redirectConfiguration": { + "id": "" + }, + "rewriteRuleSet": { + "id": "" + }, + "ruleType": "Basic" + } + } + ] + }, + "rewriteRuleSets": { + "value": [ + { + "id": "", + "name": "customRewrite", + "properties": { + "rewriteRules": [ + { + "actionSet": { + "requestHeaderConfigurations": [ + { + "headerName": "Content-Type", + "headerValue": "JSON" + }, + { + "headerName": "someheader" + } + ], + "responseHeaderConfigurations": [] + }, + "conditions": [], + "name": "NewRewrite", + "ruleSequence": 100 + } + ] + } + } + ] + }, + "sku": { + "value": "WAF_v2" + }, + "sslCertificates": { + "value": [ + { + "name": "az-apgw-x-001-ssl-certificate", + "properties": { + "keyVaultSecretId": "" + } + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + }, + "webApplicationFirewallConfiguration": { + "value": { + "enabled": true, + "fileUploadLimitInMb": 100, + "firewallMode": "Prevention", + "maxRequestBodySizeInKb": 128, + "requestBodyCheck": true, + "ruleSetType": "OWASP", + "ruleSetVersion": "3.0" + } + }, + "zones": { + "value": [ + "1", + "2", + "3" + ] + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Name of the Application Gateway. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`authenticationCertificates`](#parameter-authenticationcertificates) | array | Authentication certificates of the application gateway resource. | +| [`autoscaleMaxCapacity`](#parameter-autoscalemaxcapacity) | int | Upper bound on number of Application Gateway capacity. | +| [`autoscaleMinCapacity`](#parameter-autoscalemincapacity) | int | Lower bound on number of Application Gateway capacity. | +| [`backendAddressPools`](#parameter-backendaddresspools) | array | Backend address pool of the application gateway resource. | +| [`backendHttpSettingsCollection`](#parameter-backendhttpsettingscollection) | array | Backend http settings of the application gateway resource. | +| [`backendSettingsCollection`](#parameter-backendsettingscollection) | array | Backend settings of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits). | +| [`capacity`](#parameter-capacity) | int | The number of Application instances to be configured. | +| [`customErrorConfigurations`](#parameter-customerrorconfigurations) | array | Custom error configurations of the application gateway resource. | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`enableFips`](#parameter-enablefips) | bool | Whether FIPS is enabled on the application gateway resource. | +| [`enableHttp2`](#parameter-enablehttp2) | bool | Whether HTTP2 is enabled on the application gateway resource. | +| [`enableRequestBuffering`](#parameter-enablerequestbuffering) | bool | Enable request buffering. | +| [`enableResponseBuffering`](#parameter-enableresponsebuffering) | bool | Enable response buffering. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`firewallPolicyId`](#parameter-firewallpolicyid) | string | The resource ID of an associated firewall policy. Should be configured for security reasons. | +| [`frontendIPConfigurations`](#parameter-frontendipconfigurations) | array | Frontend IP addresses of the application gateway resource. | +| [`frontendPorts`](#parameter-frontendports) | array | Frontend ports of the application gateway resource. | +| [`gatewayIPConfigurations`](#parameter-gatewayipconfigurations) | array | Subnets of the application gateway resource. | +| [`httpListeners`](#parameter-httplisteners) | array | Http listeners of the application gateway resource. | +| [`listeners`](#parameter-listeners) | array | Listeners of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits). | +| [`loadDistributionPolicies`](#parameter-loaddistributionpolicies) | array | Load distribution policies of the application gateway resource. | +| [`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. | +| [`privateLinkConfigurations`](#parameter-privatelinkconfigurations) | array | PrivateLink configurations on application gateway. | +| [`probes`](#parameter-probes) | array | Probes of the application gateway resource. | +| [`redirectConfigurations`](#parameter-redirectconfigurations) | array | Redirect configurations of the application gateway resource. | +| [`requestRoutingRules`](#parameter-requestroutingrules) | array | Request routing rules of the application gateway resource. | +| [`rewriteRuleSets`](#parameter-rewriterulesets) | array | Rewrite rules for the application gateway resource. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`routingRules`](#parameter-routingrules) | array | Routing rules of the application gateway resource. | +| [`sku`](#parameter-sku) | string | The name of the SKU for the Application Gateway. | +| [`sslCertificates`](#parameter-sslcertificates) | array | SSL certificates of the application gateway resource. | +| [`sslPolicyCipherSuites`](#parameter-sslpolicyciphersuites) | array | Ssl cipher suites to be enabled in the specified order to application gateway. | +| [`sslPolicyMinProtocolVersion`](#parameter-sslpolicyminprotocolversion) | string | Ssl protocol enums. | +| [`sslPolicyName`](#parameter-sslpolicyname) | string | Ssl predefined policy name enums. | +| [`sslPolicyType`](#parameter-sslpolicytype) | string | Type of Ssl Policy. | +| [`sslProfiles`](#parameter-sslprofiles) | array | SSL profiles of the application gateway resource. | +| [`tags`](#parameter-tags) | object | Resource tags. | +| [`trustedClientCertificates`](#parameter-trustedclientcertificates) | array | Trusted client certificates of the application gateway resource. | +| [`trustedRootCertificates`](#parameter-trustedrootcertificates) | array | Trusted Root certificates of the application gateway resource. | +| [`urlPathMaps`](#parameter-urlpathmaps) | array | URL path map of the application gateway resource. | +| [`webApplicationFirewallConfiguration`](#parameter-webapplicationfirewallconfiguration) | object | Application gateway web application firewall configuration. Should be configured for security reasons. | +| [`zones`](#parameter-zones) | array | A list of availability zones denoting where the resource needs to come from. | + +### Parameter: `name` + +Name of the Application Gateway. + +- Required: Yes +- Type: string + +### Parameter: `authenticationCertificates` + +Authentication certificates of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `autoscaleMaxCapacity` + +Upper bound on number of Application Gateway capacity. + +- Required: No +- Type: int +- Default: `-1` + +### Parameter: `autoscaleMinCapacity` + +Lower bound on number of Application Gateway capacity. + +- Required: No +- Type: int +- Default: `-1` + +### Parameter: `backendAddressPools` + +Backend address pool of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `backendHttpSettingsCollection` + +Backend http settings of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `backendSettingsCollection` + +Backend settings of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits). + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `capacity` + +The number of Application instances to be configured. + +- Required: No +- Type: int +- Default: `2` + +### Parameter: `customErrorConfigurations` + +Custom error configurations of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | string | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| [`eventHubName`](#parameter-diagnosticsettingseventhubname) | string | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | string | A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. | +| [`logCategoriesAndGroups`](#parameter-diagnosticsettingslogcategoriesandgroups) | array | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. | +| [`marketplacePartnerResourceId`](#parameter-diagnosticsettingsmarketplacepartnerresourceid) | string | The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | array | The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. | +| [`name`](#parameter-diagnosticsettingsname) | string | The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | string | Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | string | Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | string | Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. | +| [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. | +| [`enabled`](#parameter-diagnosticsettingslogcategoriesandgroupsenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `enableFips` + +Whether FIPS is enabled on the application gateway resource. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `enableHttp2` + +Whether HTTP2 is enabled on the application gateway resource. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `enableRequestBuffering` + +Enable request buffering. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `enableResponseBuffering` + +Enable response buffering. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `firewallPolicyId` + +The resource ID of an associated firewall policy. Should be configured for security reasons. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `frontendIPConfigurations` + +Frontend IP addresses of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `frontendPorts` + +Frontend ports of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `gatewayIPConfigurations` + +Subnets of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `httpListeners` + +Http listeners of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `listeners` + +Listeners of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits). + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `loadDistributionPolicies` + +Load distribution policies of the application gateway resource. + +- Required: No +- Type: array +- 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 | +| :-- | :-- | :-- | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. | + +### Parameter: `managedIdentities.userAssignedResourceIds` + +The resource ID(s) to assign to the resource. + +- Required: Yes +- Type: array + +### Parameter: `privateEndpoints` + +Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`service`](#parameter-privateendpointsservice) | string | The subresource to deploy the private endpoint for. For example "blob", "table", "queue" or "file". | +| [`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. | +| [`isManualConnection`](#parameter-privateendpointsismanualconnection) | bool | If Manual Private Link Connection is required. | +| [`location`](#parameter-privateendpointslocation) | string | The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | object | Specify the type of lock. | +| [`manualConnectionRequestMessage`](#parameter-privateendpointsmanualconnectionrequestmessage) | string | A message passed to the owner of the remote resource with the manual connection request. | +| [`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. | +| [`privateLinkServiceConnectionName`](#parameter-privateendpointsprivatelinkserviceconnectionname) | string | The name of the private link connection to create. | +| [`resourceGroupName`](#parameter-privateendpointsresourcegroupname) | string | Specify if you want to deploy the Private Endpoint into a different resource group than the main resource. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | array | Array of role assignments to create. | +| [`tags`](#parameter-privateendpointstags) | object | Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.service` + +The subresource to deploy the private endpoint for. For example "blob", "table", "queue" or "file". + +- Required: Yes +- Type: string + +### 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 + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | string | Fqdn that resolves to private endpoint IP address. | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | array | A list of private IP addresses of the private endpoint. | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` + +Fqdn that resolves to private endpoint IP address. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` + +A list of private IP addresses of the private endpoint. + +- Required: Yes +- 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 + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-privateendpointsipconfigurationsname) | string | The name of the resource that is unique within a resource group. | +| [`properties`](#parameter-privateendpointsipconfigurationsproperties) | object | Properties of private endpoint IP configurations. | + +### Parameter: `privateEndpoints.ipConfigurations.name` + +The name of the resource that is unique within a resource group. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.properties` + +Properties of private endpoint IP configurations. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationspropertiesgroupid) | string | The ID of a group obtained from the remote resource that this private endpoint should connect to. | +| [`memberName`](#parameter-privateendpointsipconfigurationspropertiesmembername) | string | The member name of a group obtained from the remote resource that this private endpoint should connect to. | +| [`privateIPAddress`](#parameter-privateendpointsipconfigurationspropertiesprivateipaddress) | string | A private IP address obtained from the private endpoint's subnet. | + +### Parameter: `privateEndpoints.ipConfigurations.properties.groupId` + +The ID of a group obtained from the remote resource that this private endpoint should connect to. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.properties.memberName` + +The member name of a group obtained from the remote resource that this private endpoint should connect to. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.properties.privateIPAddress` + +A private IP address obtained from the private endpoint's subnet. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.isManualConnection` + +If Manual Private Link Connection is required. + +- Required: No +- Type: bool + +### 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.manualConnectionRequestMessage` + +A message passed to the owner of the remote resource with the manual connection request. + +- Required: No +- Type: string + +### 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.privateLinkServiceConnectionName` + +The name of the private link connection to create. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.resourceGroupName` + +Specify if you want to deploy the Private Endpoint into a different resource group than the main resource. + +- Required: No +- Type: string + +### 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.tags` + +Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object + +### Parameter: `privateLinkConfigurations` + +PrivateLink configurations on application gateway. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `probes` + +Probes of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `redirectConfigurations` + +Redirect configurations of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `requestRoutingRules` + +Request routing rules of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `rewriteRuleSets` + +Rewrite rules for the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### 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: `routingRules` + +Routing rules of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `sku` + +The name of the SKU for the Application Gateway. + +- Required: No +- Type: string +- Default: `'WAF_v2'` +- Allowed: + ```Bicep + [ + 'Standard_Large' + 'Standard_Medium' + 'Standard_Small' + 'Standard_v2' + 'WAF_Large' + 'WAF_Medium' + 'WAF_v2' + ] + ``` + +### Parameter: `sslCertificates` + +SSL certificates of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `sslPolicyCipherSuites` + +Ssl cipher suites to be enabled in the specified order to application gateway. + +- Required: No +- Type: array +- Default: + ```Bicep + [ + 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + ] + ``` +- Allowed: + ```Bicep + [ + 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256' + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256' + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' + 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' + 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA' + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256' + 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA' + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384' + 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256' + 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384' + 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + 'TLS_RSA_WITH_3DES_EDE_CBC_SHA' + 'TLS_RSA_WITH_AES_128_CBC_SHA' + 'TLS_RSA_WITH_AES_128_CBC_SHA256' + 'TLS_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_RSA_WITH_AES_256_CBC_SHA' + 'TLS_RSA_WITH_AES_256_CBC_SHA256' + 'TLS_RSA_WITH_AES_256_GCM_SHA384' + ] + ``` + +### Parameter: `sslPolicyMinProtocolVersion` + +Ssl protocol enums. + +- Required: No +- Type: string +- Default: `'TLSv1_2'` +- Allowed: + ```Bicep + [ + 'TLSv1_0' + 'TLSv1_1' + 'TLSv1_2' + 'TLSv1_3' + ] + ``` + +### Parameter: `sslPolicyName` + +Ssl predefined policy name enums. + +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'AppGwSslPolicy20150501' + 'AppGwSslPolicy20170401' + 'AppGwSslPolicy20170401S' + 'AppGwSslPolicy20220101' + 'AppGwSslPolicy20220101S' + ] + ``` + +### Parameter: `sslPolicyType` + +Type of Ssl Policy. + +- Required: No +- Type: string +- Default: `'Custom'` +- Allowed: + ```Bicep + [ + 'Custom' + 'CustomV2' + 'Predefined' + ] + ``` + +### Parameter: `sslProfiles` + +SSL profiles of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `tags` + +Resource tags. + +- Required: No +- Type: object + +### Parameter: `trustedClientCertificates` + +Trusted client certificates of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `trustedRootCertificates` + +Trusted Root certificates of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `urlPathMaps` + +URL path map of the application gateway resource. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `webApplicationFirewallConfiguration` + +Application gateway web application firewall configuration. Should be configured for security reasons. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `zones` + +A list of availability zones denoting where the resource needs to come from. + +- Required: No +- Type: array +- Default: `[]` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the application gateway. | +| `resourceGroupName` | string | The resource group the application gateway was deployed into. | +| `resourceId` | string | The resource ID of the application gateway. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other 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.4.1` | Remote reference | + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/network/application-gateway/main.bicep b/avm/res/network/application-gateway/main.bicep new file mode 100644 index 0000000000..6aa9a00ea9 --- /dev/null +++ b/avm/res/network/application-gateway/main.bicep @@ -0,0 +1,616 @@ +metadata name = 'Network Application Gateways' +metadata description = 'This module deploys a Network Application Gateway.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the Application Gateway.') +@maxLength(80) +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@description('Optional. Authentication certificates of the application gateway resource.') +param authenticationCertificates array = [] + +@description('Optional. Upper bound on number of Application Gateway capacity.') +param autoscaleMaxCapacity int = -1 + +@description('Optional. Lower bound on number of Application Gateway capacity.') +param autoscaleMinCapacity int = -1 + +@description('Optional. Backend address pool of the application gateway resource.') +param backendAddressPools array = [] + +@description('Optional. Backend http settings of the application gateway resource.') +param backendHttpSettingsCollection array = [] + +@description('Optional. Custom error configurations of the application gateway resource.') +param customErrorConfigurations array = [] + +@description('Optional. Whether FIPS is enabled on the application gateway resource.') +param enableFips bool = false + +@description('Optional. Whether HTTP2 is enabled on the application gateway resource.') +param enableHttp2 bool = false + +@description('Optional. The resource ID of an associated firewall policy. Should be configured for security reasons.') +param firewallPolicyId string = '' + +@description('Optional. Frontend IP addresses of the application gateway resource.') +param frontendIPConfigurations array = [] + +@description('Optional. Frontend ports of the application gateway resource.') +param frontendPorts array = [] + +@description('Optional. Subnets of the application gateway resource.') +param gatewayIPConfigurations array = [] + +@description('Optional. Enable request buffering.') +param enableRequestBuffering bool = false + +@description('Optional. Enable response buffering.') +param enableResponseBuffering bool = false + +@description('Optional. Http listeners of the application gateway resource.') +param httpListeners array = [] + +@description('Optional. Load distribution policies of the application gateway resource.') +param loadDistributionPolicies array = [] + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointType + +@description('Optional. PrivateLink configurations on application gateway.') +param privateLinkConfigurations array = [] + +@description('Optional. Probes of the application gateway resource.') +param probes array = [] + +@description('Optional. Redirect configurations of the application gateway resource.') +param redirectConfigurations array = [] + +@description('Optional. Request routing rules of the application gateway resource.') +param requestRoutingRules array = [] + +@description('Optional. Rewrite rules for the application gateway resource.') +param rewriteRuleSets array = [] + +@description('Optional. The name of the SKU for the Application Gateway.') +@allowed([ + 'Standard_Small' + 'Standard_Medium' + 'Standard_Large' + 'WAF_Medium' + 'WAF_Large' + 'Standard_v2' + 'WAF_v2' +]) +param sku string = 'WAF_v2' + +@description('Optional. The number of Application instances to be configured.') +@minValue(0) +@maxValue(10) +param capacity int = 2 + +@description('Optional. SSL certificates of the application gateway resource.') +param sslCertificates array = [] + +@description('Optional. Ssl cipher suites to be enabled in the specified order to application gateway.') +@allowed([ + 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256' + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA' + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256' + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' + 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' + 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA' + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256' + 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA' + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384' + 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256' + 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384' + 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + 'TLS_RSA_WITH_3DES_EDE_CBC_SHA' + 'TLS_RSA_WITH_AES_128_CBC_SHA' + 'TLS_RSA_WITH_AES_128_CBC_SHA256' + 'TLS_RSA_WITH_AES_128_GCM_SHA256' + 'TLS_RSA_WITH_AES_256_CBC_SHA' + 'TLS_RSA_WITH_AES_256_CBC_SHA256' + 'TLS_RSA_WITH_AES_256_GCM_SHA384' +]) +param sslPolicyCipherSuites array = [ + 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' +] + +@description('Optional. Ssl protocol enums.') +@allowed([ + 'TLSv1_0' + 'TLSv1_1' + 'TLSv1_2' + 'TLSv1_3' +]) +param sslPolicyMinProtocolVersion string = 'TLSv1_2' + +@description('Optional. Ssl predefined policy name enums.') +@allowed([ + 'AppGwSslPolicy20150501' + 'AppGwSslPolicy20170401' + 'AppGwSslPolicy20170401S' + 'AppGwSslPolicy20220101' + 'AppGwSslPolicy20220101S' + '' +]) +param sslPolicyName string = '' + +@description('Optional. Type of Ssl Policy.') +@allowed([ + 'Custom' + 'CustomV2' + 'Predefined' +]) +param sslPolicyType string = 'Custom' + +@description('Optional. SSL profiles of the application gateway resource.') +param sslProfiles array = [] + +@description('Optional. Trusted client certificates of the application gateway resource.') +param trustedClientCertificates array = [] + +@description('Optional. Trusted Root certificates of the application gateway resource.') +param trustedRootCertificates array = [] + +@description('Optional. URL path map of the application gateway resource.') +param urlPathMaps array = [] + +@description('Optional. Application gateway web application firewall configuration. Should be configured for security reasons.') +param webApplicationFirewallConfiguration object = {} + +@description('Optional. A list of availability zones denoting where the resource needs to come from.') +param zones array = [] + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +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: !empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Resource tags.') +param tags object? + +@description('Optional. Backend settings of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits).') +param backendSettingsCollection array = [] + +@description('Optional. Listeners of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits).') +param listeners array = [] + +@description('Optional. Routing rules of the application gateway resource.') +param routingRules array = [] + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) +} + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.res.network-appgw.${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 applicationGateway 'Microsoft.Network/applicationGateways@2023-04-01' = { + name: name + location: location + tags: tags + identity: identity + properties: union( + { + authenticationCertificates: authenticationCertificates + autoscaleConfiguration: autoscaleMaxCapacity > 0 && autoscaleMinCapacity >= 0 + ? { + maxCapacity: autoscaleMaxCapacity + minCapacity: autoscaleMinCapacity + } + : null + backendAddressPools: backendAddressPools + backendHttpSettingsCollection: backendHttpSettingsCollection + backendSettingsCollection: backendSettingsCollection + customErrorConfigurations: customErrorConfigurations + enableHttp2: enableHttp2 + firewallPolicy: !empty(firewallPolicyId) + ? { + id: firewallPolicyId + } + : null + forceFirewallPolicyAssociation: !empty(firewallPolicyId) + frontendIPConfigurations: frontendIPConfigurations + frontendPorts: frontendPorts + gatewayIPConfigurations: gatewayIPConfigurations + globalConfiguration: endsWith(sku, 'v2') + ? { + enableRequestBuffering: enableRequestBuffering + enableResponseBuffering: enableResponseBuffering + } + : null + httpListeners: httpListeners + loadDistributionPolicies: loadDistributionPolicies + listeners: listeners + privateLinkConfigurations: privateLinkConfigurations + probes: probes + redirectConfigurations: redirectConfigurations + requestRoutingRules: requestRoutingRules + routingRules: routingRules + rewriteRuleSets: rewriteRuleSets + sku: { + name: sku + tier: endsWith(sku, 'v2') ? sku : substring(sku, 0, indexOf(sku, '_')) + capacity: autoscaleMaxCapacity > 0 && autoscaleMinCapacity >= 0 ? null : capacity + } + sslCertificates: sslCertificates + sslPolicy: sslPolicyType != 'Predefined' + ? { + cipherSuites: sslPolicyCipherSuites + minProtocolVersion: sslPolicyMinProtocolVersion + policyName: empty(sslPolicyName) ? null : sslPolicyName + policyType: sslPolicyType + } + : { + policyName: empty(sslPolicyName) ? null : sslPolicyName + policyType: sslPolicyType + } + sslProfiles: sslProfiles + trustedClientCertificates: trustedClientCertificates + trustedRootCertificates: trustedRootCertificates + urlPathMaps: urlPathMaps + }, + (enableFips + ? { + enableFips: enableFips + } + : {}), + (!empty(webApplicationFirewallConfiguration) + ? { webApplicationFirewallConfiguration: webApplicationFirewallConfiguration } + : {}) + ) + zones: zones +} + +resource applicationGateway_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: applicationGateway + } + +resource applicationGateway_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: applicationGateway + } +] + +module applicationGateway_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.4.1' = [ + for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-applicationGateway-PrivateEndpoint-${index}' + scope: resourceGroup(privateEndpoint.?resourceGroupName ?? '') + params: { + name: privateEndpoint.?name ?? 'pep-${last(split(applicationGateway.id, '/'))}-${privateEndpoint.service}-${index}' + privateLinkServiceConnections: privateEndpoint.?isManualConnection != true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(applicationGateway.id, '/'))}-${privateEndpoint.service}-${index}' + properties: { + privateLinkServiceId: applicationGateway.id + groupIds: [ + privateEndpoint.service + ] + } + } + ] + : null + manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(applicationGateway.id, '/'))}-${privateEndpoint.service}-${index}' + properties: { + privateLinkServiceId: applicationGateway.id + groupIds: [ + privateEndpoint.service + ] + requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.' + } + } + ] + : null + subnetResourceId: privateEndpoint.subnetResourceId + enableTelemetry: privateEndpoint.?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 + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } + } +] + +resource applicationGateway_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(applicationGateway.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: applicationGateway + } +] + +@description('The name of the application gateway.') +output name string = applicationGateway.name + +@description('The resource ID of the application gateway.') +output resourceId string = applicationGateway.id + +@description('The resource group the application gateway was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = applicationGateway.location + +// =============== // +// Definitions // +// =============== // + +type managedIdentitiesType = { + @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 name of the private link connection to create.') + privateLinkServiceConnectionName: string? + + @description('Required. The subresource to deploy the private endpoint for. For example "blob", "table", "queue" or "file".') + 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. If Manual Private Link Connection is required.') + isManualConnection: bool? + + @description('Optional. A message passed to the owner of the remote resource with the manual connection request.') + @maxLength(140) + manualConnectionRequestMessage: 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. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? + + @description('Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource.') + resourceGroupName: string? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs.') + categoryGroup: string? + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics.') + category: string + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/avm/res/network/application-gateway/main.json b/avm/res/network/application-gateway/main.json new file mode 100644 index 0000000000..d775da8b70 --- /dev/null +++ b/avm/res/network/application-gateway/main.json @@ -0,0 +1,1633 @@ +{ + "$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.26.54.24096", + "templateHash": "12167944734183128583" + }, + "name": "Network Application Gateways", + "description": "This module deploys a Network Application Gateway.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\"." + } + }, + "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." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "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." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "resourceGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 80, + "metadata": { + "description": "Required. Name of the Application Gateway." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "authenticationCertificates": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Authentication certificates of the application gateway resource." + } + }, + "autoscaleMaxCapacity": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Upper bound on number of Application Gateway capacity." + } + }, + "autoscaleMinCapacity": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Lower bound on number of Application Gateway capacity." + } + }, + "backendAddressPools": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Backend address pool of the application gateway resource." + } + }, + "backendHttpSettingsCollection": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Backend http settings of the application gateway resource." + } + }, + "customErrorConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Custom error configurations of the application gateway resource." + } + }, + "enableFips": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether FIPS is enabled on the application gateway resource." + } + }, + "enableHttp2": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether HTTP2 is enabled on the application gateway resource." + } + }, + "firewallPolicyId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of an associated firewall policy. Should be configured for security reasons." + } + }, + "frontendIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Frontend IP addresses of the application gateway resource." + } + }, + "frontendPorts": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Frontend ports of the application gateway resource." + } + }, + "gatewayIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Subnets of the application gateway resource." + } + }, + "enableRequestBuffering": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable request buffering." + } + }, + "enableResponseBuffering": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable response buffering." + } + }, + "httpListeners": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Http listeners of the application gateway resource." + } + }, + "loadDistributionPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Load distribution policies of the application gateway resource." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "privateLinkConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. PrivateLink configurations on application gateway." + } + }, + "probes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Probes of the application gateway resource." + } + }, + "redirectConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Redirect configurations of the application gateway resource." + } + }, + "requestRoutingRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Request routing rules of the application gateway resource." + } + }, + "rewriteRuleSets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Rewrite rules for the application gateway resource." + } + }, + "sku": { + "type": "string", + "defaultValue": "WAF_v2", + "allowedValues": [ + "Standard_Small", + "Standard_Medium", + "Standard_Large", + "WAF_Medium", + "WAF_Large", + "Standard_v2", + "WAF_v2" + ], + "metadata": { + "description": "Optional. The name of the SKU for the Application Gateway." + } + }, + "capacity": { + "type": "int", + "defaultValue": 2, + "minValue": 0, + "maxValue": 10, + "metadata": { + "description": "Optional. The number of Application instances to be configured." + } + }, + "sslCertificates": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. SSL certificates of the application gateway resource." + } + }, + "sslPolicyCipherSuites": { + "type": "array", + "defaultValue": [ + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ], + "allowedValues": [ + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384" + ], + "metadata": { + "description": "Optional. Ssl cipher suites to be enabled in the specified order to application gateway." + } + }, + "sslPolicyMinProtocolVersion": { + "type": "string", + "defaultValue": "TLSv1_2", + "allowedValues": [ + "TLSv1_0", + "TLSv1_1", + "TLSv1_2", + "TLSv1_3" + ], + "metadata": { + "description": "Optional. Ssl protocol enums." + } + }, + "sslPolicyName": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "AppGwSslPolicy20150501", + "AppGwSslPolicy20170401", + "AppGwSslPolicy20170401S", + "AppGwSslPolicy20220101", + "AppGwSslPolicy20220101S", + "" + ], + "metadata": { + "description": "Optional. Ssl predefined policy name enums." + } + }, + "sslPolicyType": { + "type": "string", + "defaultValue": "Custom", + "allowedValues": [ + "Custom", + "CustomV2", + "Predefined" + ], + "metadata": { + "description": "Optional. Type of Ssl Policy." + } + }, + "sslProfiles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. SSL profiles of the application gateway resource." + } + }, + "trustedClientCertificates": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Trusted client certificates of the application gateway resource." + } + }, + "trustedRootCertificates": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Trusted Root certificates of the application gateway resource." + } + }, + "urlPathMaps": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. URL path map of the application gateway resource." + } + }, + "webApplicationFirewallConfiguration": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Application gateway web application firewall configuration. Should be configured for security reasons." + } + }, + "zones": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. A list of availability zones denoting where the resource needs to come from." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "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. Resource tags." + } + }, + "backendSettingsCollection": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Backend settings of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits)." + } + }, + "listeners": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Listeners of the application gateway resource. For default limits, see [Application Gateway limits](https://learn.microsoft.com/en-us/azure/azure-subscription-service-limits#application-gateway-limits)." + } + }, + "routingRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Routing rules of the application gateway resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "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(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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-appgw.{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" + } + } + } + } + }, + "applicationGateway": { + "type": "Microsoft.Network/applicationGateways", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": "[union(createObject('authenticationCertificates', parameters('authenticationCertificates'), 'autoscaleConfiguration', if(and(greater(parameters('autoscaleMaxCapacity'), 0), greaterOrEquals(parameters('autoscaleMinCapacity'), 0)), createObject('maxCapacity', parameters('autoscaleMaxCapacity'), 'minCapacity', parameters('autoscaleMinCapacity')), null()), 'backendAddressPools', parameters('backendAddressPools'), 'backendHttpSettingsCollection', parameters('backendHttpSettingsCollection'), 'backendSettingsCollection', parameters('backendSettingsCollection'), 'customErrorConfigurations', parameters('customErrorConfigurations'), 'enableHttp2', parameters('enableHttp2'), 'firewallPolicy', if(not(empty(parameters('firewallPolicyId'))), createObject('id', parameters('firewallPolicyId')), null()), 'forceFirewallPolicyAssociation', not(empty(parameters('firewallPolicyId'))), 'frontendIPConfigurations', parameters('frontendIPConfigurations'), 'frontendPorts', parameters('frontendPorts'), 'gatewayIPConfigurations', parameters('gatewayIPConfigurations'), 'globalConfiguration', if(endsWith(parameters('sku'), 'v2'), createObject('enableRequestBuffering', parameters('enableRequestBuffering'), 'enableResponseBuffering', parameters('enableResponseBuffering')), null()), 'httpListeners', parameters('httpListeners'), 'loadDistributionPolicies', parameters('loadDistributionPolicies'), 'listeners', parameters('listeners'), 'privateLinkConfigurations', parameters('privateLinkConfigurations'), 'probes', parameters('probes'), 'redirectConfigurations', parameters('redirectConfigurations'), 'requestRoutingRules', parameters('requestRoutingRules'), 'routingRules', parameters('routingRules'), 'rewriteRuleSets', parameters('rewriteRuleSets'), 'sku', createObject('name', parameters('sku'), 'tier', if(endsWith(parameters('sku'), 'v2'), parameters('sku'), substring(parameters('sku'), 0, indexOf(parameters('sku'), '_'))), 'capacity', if(and(greater(parameters('autoscaleMaxCapacity'), 0), greaterOrEquals(parameters('autoscaleMinCapacity'), 0)), null(), parameters('capacity'))), 'sslCertificates', parameters('sslCertificates'), 'sslPolicy', if(not(equals(parameters('sslPolicyType'), 'Predefined')), createObject('cipherSuites', parameters('sslPolicyCipherSuites'), 'minProtocolVersion', parameters('sslPolicyMinProtocolVersion'), 'policyName', if(empty(parameters('sslPolicyName')), null(), parameters('sslPolicyName')), 'policyType', parameters('sslPolicyType')), createObject('policyName', if(empty(parameters('sslPolicyName')), null(), parameters('sslPolicyName')), 'policyType', parameters('sslPolicyType'))), 'sslProfiles', parameters('sslProfiles'), 'trustedClientCertificates', parameters('trustedClientCertificates'), 'trustedRootCertificates', parameters('trustedRootCertificates'), 'urlPathMaps', parameters('urlPathMaps')), if(parameters('enableFips'), createObject('enableFips', parameters('enableFips')), createObject()), if(not(empty(parameters('webApplicationFirewallConfiguration'))), createObject('webApplicationFirewallConfiguration', parameters('webApplicationFirewallConfiguration')), createObject()))]", + "zones": "[parameters('zones')]" + }, + "applicationGateway_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/applicationGateways/{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": [ + "applicationGateway" + ] + }, + "applicationGateway_diagnosticSettings": { + "copy": { + "name": "applicationGateway_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/applicationGateways/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "applicationGateway" + ] + }, + "applicationGateway_roleAssignments": { + "copy": { + "name": "applicationGateway_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/applicationGateways/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/applicationGateways', 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": [ + "applicationGateway" + ] + }, + "applicationGateway_privateEndpoints": { + "copy": { + "name": "applicationGateway_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-applicationGateway-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "resourceGroup": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupName'), '')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Network/applicationGateways', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Network/applicationGateways', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Network/applicationGateways', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Network/applicationGateways', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Network/applicationGateways', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), 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'))]" + }, + "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.25.53.49325", + "templateHash": "4120048060064073955" + }, + "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.4.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.25.53.49325", + "templateHash": "11244630631275470040" + }, + "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]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "applicationGateway" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the application gateway." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the application gateway." + }, + "value": "[resourceId('Microsoft.Network/applicationGateways', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the application gateway was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('applicationGateway', '2023-04-01', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/network/application-gateway/tests/e2e/defaults/dependencies.bicep b/avm/res/network/application-gateway/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..4f1950dc66 --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,60 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Public IP to create.') +param publicIPName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +var addressPrefix = '10.0.0.0/16' + +resource publicIP 'Microsoft.Network/publicIPAddresses@2023-04-01' = { + name: publicIPName + location: location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAllocationMethod: 'Static' + } + zones: [ + '1' + '2' + '3' + ] +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + } + } + { + name: 'privateLinkSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 1) + privateLinkServiceNetworkPolicies: 'Disabled' + } + } + ] + } +} + +@description('The resource ID of the created Virtual Network default subnet.') +output defaultSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Public IP.') +output publicIPResourceId string = publicIP.id diff --git a/avm/res/network/application-gateway/tests/e2e/defaults/main.test.bicep b/avm/res/network/application-gateway/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..0998d2c453 --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,146 @@ +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) +// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' +param resourceGroupName string = 'dep-${namePrefix}-network.applicationgateway-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'nagmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + publicIPName: 'dep-${namePrefix}-pip-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var resourceName = '${namePrefix}${serviceShort}001' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: resourceName + zones: [ + '1' + '2' + '3' + ] + location: resourceLocation + gatewayIPConfigurations: [ + { + name: 'publicIPConfig1' + properties: { + subnet: { + id: nestedDependencies.outputs.defaultSubnetResourceId + } + } + } + ] + frontendIPConfigurations: [ + { + name: 'frontendIPConfig1' + properties: { + publicIPAddress: { + id: nestedDependencies.outputs.publicIPResourceId + } + } + } + ] + frontendPorts: [ + { + name: 'frontendPort1' + properties: { + port: 80 + } + } + ] + backendAddressPools: [ + { + name: 'backendAddressPool1' + } + ] + backendHttpSettingsCollection: [ + { + name: 'backendHttpSettings1' + properties: { + port: 80 + protocol: 'Http' + cookieBasedAffinity: 'Disabled' + } + } + ] + httpListeners: [ + { + name: 'httpListener1' + properties: { + hostName: 'www.contoso.com' + protocol: 'Http' + frontendIPConfiguration: { + id: '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${resourceName}/frontendIPConfigurations/frontendIPConfig1' + } + frontendPort: { + id: '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${resourceName}/frontendPorts/frontendPort1' + } + } + } + ] + requestRoutingRules: [ + { + name: 'requestRoutingRule1' + properties: { + ruleType: 'Basic' + priority: 100 + httpListener: { + id: '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${resourceName}/httpListeners/httpListener1' + } + backendAddressPool: { + id: '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${resourceName}/backendAddressPools/backendAddressPool1' + } + backendHttpSettings: { + id: '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${resourceName}/backendHttpSettingsCollection/backendHttpSettings1' + } + } + } + ] + webApplicationFirewallConfiguration: { + enabled: false + } + } + } +] diff --git a/avm/res/network/application-gateway/tests/e2e/max/dependencies.bicep b/avm/res/network/application-gateway/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..2a9c2b0fae --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/max/dependencies.bicep @@ -0,0 +1,154 @@ +@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 Public IP to create.') +param publicIPName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Deployment Script to create for the Certificate generation.') +param certDeploymentScriptName 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, 24, 0) + } + } + { + name: 'privateLinkSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 1) + privateLinkServiceNetworkPolicies: 'Disabled' + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.appgateway.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource publicIP 'Microsoft.Network/publicIPAddresses@2023-04-01' = { + name: publicIPName + location: location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAllocationMethod: 'Static' + } + zones: [ + '1' + '2' + '3' + ] +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${managedIdentity.name}-KeyVault-Admin-RoleAssignment') + scope: keyVault + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '00482a5a-887f-4fb3-b363-3b7fe8e74483' + ) // Key Vault Administrator + principalType: 'ServicePrincipal' + } +} + +resource certDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: certDeploymentScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-KeyVaultName "${keyVault.name}" -CertName "applicationGatewaySslCertificate"' + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1') + } +} + +@description('The resource ID of the created Virtual Network default subnet.') +output defaultSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Virtual Network private link subnet.') +output privateLinkSubnetResourceId string = virtualNetwork.properties.subnets[1].id + +@description('The resource ID of the created Public IP.') +output publicIPResourceId string = publicIP.id + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The URL of the created certificate.') +output certificateSecretUrl string = certDeploymentScript.properties.outputs.secretUrl + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/avm/res/network/application-gateway/tests/e2e/max/main.test.bicep b/avm/res/network/application-gateway/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..7e67afba91 --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/max/main.test.bicep @@ -0,0 +1,524 @@ +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}-network.applicationgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'nagmax' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + publicIPName: 'dep-${namePrefix}-pip-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + certDeploymentScriptName: 'dep-${namePrefix}-ds-${serviceShort}' + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var appGWName = '${namePrefix}${serviceShort}001' +var appGWExpectedResourceID = '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${appGWName}' +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + enableTelemetry: enableTelemetry + name: appGWName + zones: [ + '1' + '2' + '3' + ] + backendAddressPools: [ + { + name: 'appServiceBackendPool' + properties: { + backendAddresses: [ + { + fqdn: 'aghapp.azurewebsites.net' + } + ] + } + } + { + name: 'privateVmBackendPool' + properties: { + backendAddresses: [ + { + ipAddress: '10.0.0.4' + } + ] + } + } + ] + backendHttpSettingsCollection: [ + { + name: 'appServiceBackendHttpsSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: true + port: 443 + protocol: 'Https' + requestTimeout: 30 + } + } + { + name: 'privateVmHttpSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: false + port: 80 + probe: { + id: '${appGWExpectedResourceID}/probes/privateVmHttpSettingProbe' + } + protocol: 'Http' + requestTimeout: 30 + } + } + ] + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + enableHttp2: true + privateLinkConfigurations: [ + { + name: 'pvtlink01' + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01' + properties: { + ipConfigurations: [ + { + name: 'privateLinkIpConfig1' + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01/ipConfigurations/privateLinkIpConfig1' + properties: { + privateIPAllocationMethod: 'Dynamic' + primary: false + subnet: { + id: nestedDependencies.outputs.privateLinkSubnetResourceId + } + } + } + ] + } + } + ] + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + service: 'public' + subnetResourceId: nestedDependencies.outputs.privateLinkSubnetResourceId + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + frontendIPConfigurations: [ + { + name: 'private' + properties: { + privateIPAddress: '10.0.0.20' + privateIPAllocationMethod: 'Static' + subnet: { + id: nestedDependencies.outputs.defaultSubnetResourceId + } + } + } + { + name: 'public' + properties: { + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: nestedDependencies.outputs.publicIPResourceId + } + privateLinkConfiguration: { + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01' + } + } + } + ] + frontendPorts: [ + { + name: 'port443' + properties: { + port: 443 + } + } + { + name: 'port4433' + properties: { + port: 4433 + } + } + { + name: 'port80' + properties: { + port: 80 + } + } + { + name: 'port8080' + properties: { + port: 8080 + } + } + ] + gatewayIPConfigurations: [ + { + name: 'apw-ip-configuration' + properties: { + subnet: { + id: nestedDependencies.outputs.defaultSubnetResourceId + } + } + } + ] + httpListeners: [ + { + name: 'public443' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/public' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port443' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '${appGWExpectedResourceID}/sslCertificates/${namePrefix}-az-apgw-x-001-ssl-certificate' + } + } + } + { + name: 'private4433' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/private' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port4433' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '${appGWExpectedResourceID}/sslCertificates/${namePrefix}-az-apgw-x-001-ssl-certificate' + } + } + } + { + name: 'httpRedirect80' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/public' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port80' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + { + name: 'httpRedirect8080' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/private' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port8080' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + probes: [ + { + name: 'privateVmHttpSettingProbe' + properties: { + host: '10.0.0.4' + interval: 60 + match: { + statusCodes: [ + '200' + '401' + ] + } + minServers: 3 + path: '/' + pickHostNameFromBackendHttpSettings: false + protocol: 'Http' + timeout: 15 + unhealthyThreshold: 5 + } + } + ] + redirectConfigurations: [ + { + name: 'httpRedirect80' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '${appGWExpectedResourceID}/requestRoutingRules/httpRedirect80-public443' + } + ] + targetListener: { + id: '${appGWExpectedResourceID}/httpListeners/public443' + } + } + } + { + name: 'httpRedirect8080' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '${appGWExpectedResourceID}/requestRoutingRules/httpRedirect8080-private4433' + } + ] + targetListener: { + id: '${appGWExpectedResourceID}/httpListeners/private4433' + } + } + } + ] + requestRoutingRules: [ + { + name: 'public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting' + properties: { + backendAddressPool: { + id: '${appGWExpectedResourceID}/backendAddressPools/appServiceBackendPool' + } + backendHttpSettings: { + id: '${appGWExpectedResourceID}/backendHttpSettingsCollection/appServiceBackendHttpsSetting' + } + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/public443' + } + priority: 200 + ruleType: 'Basic' + } + } + { + name: 'private4433-privateVmHttpSetting-privateVmHttpSetting' + properties: { + backendAddressPool: { + id: '${appGWExpectedResourceID}/backendAddressPools/privateVmBackendPool' + } + backendHttpSettings: { + id: '${appGWExpectedResourceID}/backendHttpSettingsCollection/privateVmHttpSetting' + } + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/private4433' + } + priority: 250 + ruleType: 'Basic' + } + } + { + name: 'httpRedirect80-public443' + properties: { + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/httpRedirect80' + } + priority: 300 + redirectConfiguration: { + id: '${appGWExpectedResourceID}/redirectConfigurations/httpRedirect80' + } + ruleType: 'Basic' + } + } + { + name: 'httpRedirect8080-private4433' + properties: { + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/httpRedirect8080' + } + priority: 350 + redirectConfiguration: { + id: '${appGWExpectedResourceID}/redirectConfigurations/httpRedirect8080' + } + ruleType: 'Basic' + rewriteRuleSet: { + id: '${appGWExpectedResourceID}/rewriteRuleSets/customRewrite' + } + } + } + ] + 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: 'WAF_v2' + sslCertificates: [ + { + name: '${namePrefix}-az-apgw-x-001-ssl-certificate' + properties: { + keyVaultSecretId: nestedDependencies.outputs.certificateSecretUrl + } + } + ] + managedIdentities: { + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + rewriteRuleSets: [ + { + name: 'customRewrite' + id: '${appGWExpectedResourceID}/rewriteRuleSets/customRewrite' + properties: { + rewriteRules: [ + { + ruleSequence: 100 + conditions: [] + name: 'NewRewrite' + actionSet: { + requestHeaderConfigurations: [ + { + headerName: 'Content-Type' + headerValue: 'JSON' + } + { + headerName: 'someheader' + } + ] + responseHeaderConfigurations: [] + } + } + ] + } + } + ] + webApplicationFirewallConfiguration: { + enabled: true + fileUploadLimitInMb: 100 + firewallMode: 'Detection' + maxRequestBodySizeInKb: 128 + requestBodyCheck: true + ruleSetType: 'OWASP' + ruleSetVersion: '3.0' + disabledRuleGroups: [ + { + ruleGroupName: 'Known-CVEs' + } + { + ruleGroupName: 'REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION' + } + { + ruleGroupName: 'REQUEST-941-APPLICATION-ATTACK-XSS' + } + ] + exclusions: [ + { + matchVariable: 'RequestHeaderNames' + selectorMatchOperator: 'StartsWith' + selector: 'hola' + } + ] + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +] diff --git a/avm/res/network/application-gateway/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/network/application-gateway/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..2a9c2b0fae --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,154 @@ +@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 Public IP to create.') +param publicIPName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Deployment Script to create for the Certificate generation.') +param certDeploymentScriptName 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, 24, 0) + } + } + { + name: 'privateLinkSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 1) + privateLinkServiceNetworkPolicies: 'Disabled' + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.appgateway.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource publicIP 'Microsoft.Network/publicIPAddresses@2023-04-01' = { + name: publicIPName + location: location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAllocationMethod: 'Static' + } + zones: [ + '1' + '2' + '3' + ] +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${managedIdentity.name}-KeyVault-Admin-RoleAssignment') + scope: keyVault + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '00482a5a-887f-4fb3-b363-3b7fe8e74483' + ) // Key Vault Administrator + principalType: 'ServicePrincipal' + } +} + +resource certDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: certDeploymentScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-KeyVaultName "${keyVault.name}" -CertName "applicationGatewaySslCertificate"' + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1') + } +} + +@description('The resource ID of the created Virtual Network default subnet.') +output defaultSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Virtual Network private link subnet.') +output privateLinkSubnetResourceId string = virtualNetwork.properties.subnets[1].id + +@description('The resource ID of the created Public IP.') +output publicIPResourceId string = publicIP.id + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The URL of the created certificate.') +output certificateSecretUrl string = certDeploymentScript.properties.outputs.secretUrl + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/avm/res/network/application-gateway/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/application-gateway/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..c6f9dede4f --- /dev/null +++ b/avm/res/network/application-gateway/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,486 @@ +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}-network.applicationgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'nagwaf' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + publicIPName: 'dep-${namePrefix}-pip-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + certDeploymentScriptName: 'dep-${namePrefix}-ds-${serviceShort}' + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var appGWName = '${namePrefix}${serviceShort}001' +var appGWExpectedResourceID = '${resourceGroup.id}/providers/Microsoft.Network/applicationGateways/${appGWName}' +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + enableTelemetry: enableTelemetry + name: appGWName + zones: [ + '1' + '2' + '3' + ] + backendAddressPools: [ + { + name: 'appServiceBackendPool' + properties: { + backendAddresses: [ + { + fqdn: 'aghapp.azurewebsites.net' + } + ] + } + } + { + name: 'privateVmBackendPool' + properties: { + backendAddresses: [ + { + ipAddress: '10.0.0.4' + } + ] + } + } + ] + backendHttpSettingsCollection: [ + { + name: 'appServiceBackendHttpsSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: true + port: 443 + protocol: 'Https' + requestTimeout: 30 + } + } + { + name: 'privateVmHttpSetting' + properties: { + cookieBasedAffinity: 'Disabled' + pickHostNameFromBackendAddress: false + port: 80 + probe: { + id: '${appGWExpectedResourceID}/probes/privateVmHttpSettingProbe' + } + protocol: 'Http' + requestTimeout: 30 + } + } + ] + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + enableHttp2: true + privateLinkConfigurations: [ + { + name: 'pvtlink01' + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01' + properties: { + ipConfigurations: [ + { + name: 'privateLinkIpConfig1' + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01/ipConfigurations/privateLinkIpConfig1' + properties: { + privateIPAllocationMethod: 'Dynamic' + primary: false + subnet: { + id: nestedDependencies.outputs.privateLinkSubnetResourceId + } + } + } + ] + } + } + ] + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + service: 'public' + subnetResourceId: nestedDependencies.outputs.privateLinkSubnetResourceId + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + frontendIPConfigurations: [ + { + name: 'private' + properties: { + privateIPAddress: '10.0.0.20' + privateIPAllocationMethod: 'Static' + subnet: { + id: nestedDependencies.outputs.defaultSubnetResourceId + } + } + } + { + name: 'public' + properties: { + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: nestedDependencies.outputs.publicIPResourceId + } + privateLinkConfiguration: { + id: '${appGWExpectedResourceID}/privateLinkConfigurations/pvtlink01' + } + } + } + ] + frontendPorts: [ + { + name: 'port443' + properties: { + port: 443 + } + } + { + name: 'port4433' + properties: { + port: 4433 + } + } + { + name: 'port80' + properties: { + port: 80 + } + } + { + name: 'port8080' + properties: { + port: 8080 + } + } + ] + gatewayIPConfigurations: [ + { + name: 'apw-ip-configuration' + properties: { + subnet: { + id: nestedDependencies.outputs.defaultSubnetResourceId + } + } + } + ] + httpListeners: [ + { + name: 'public443' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/public' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port443' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '${appGWExpectedResourceID}/sslCertificates/${namePrefix}-az-apgw-x-001-ssl-certificate' + } + } + } + { + name: 'private4433' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/private' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port4433' + } + hostNames: [] + protocol: 'https' + requireServerNameIndication: false + sslCertificate: { + id: '${appGWExpectedResourceID}/sslCertificates/${namePrefix}-az-apgw-x-001-ssl-certificate' + } + } + } + { + name: 'httpRedirect80' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/public' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port80' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + { + name: 'httpRedirect8080' + properties: { + frontendIPConfiguration: { + id: '${appGWExpectedResourceID}/frontendIPConfigurations/private' + } + frontendPort: { + id: '${appGWExpectedResourceID}/frontendPorts/port8080' + } + hostNames: [] + protocol: 'Http' + requireServerNameIndication: false + } + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + probes: [ + { + name: 'privateVmHttpSettingProbe' + properties: { + host: '10.0.0.4' + interval: 60 + match: { + statusCodes: [ + '200' + '401' + ] + } + minServers: 3 + path: '/' + pickHostNameFromBackendHttpSettings: false + protocol: 'Http' + timeout: 15 + unhealthyThreshold: 5 + } + } + ] + redirectConfigurations: [ + { + name: 'httpRedirect80' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '${appGWExpectedResourceID}/requestRoutingRules/httpRedirect80-public443' + } + ] + targetListener: { + id: '${appGWExpectedResourceID}/httpListeners/public443' + } + } + } + { + name: 'httpRedirect8080' + properties: { + includePath: true + includeQueryString: true + redirectType: 'Permanent' + requestRoutingRules: [ + { + id: '${appGWExpectedResourceID}/requestRoutingRules/httpRedirect8080-private4433' + } + ] + targetListener: { + id: '${appGWExpectedResourceID}/httpListeners/private4433' + } + } + } + ] + requestRoutingRules: [ + { + name: 'public443-appServiceBackendHttpsSetting-appServiceBackendHttpsSetting' + properties: { + backendAddressPool: { + id: '${appGWExpectedResourceID}/backendAddressPools/appServiceBackendPool' + } + backendHttpSettings: { + id: '${appGWExpectedResourceID}/backendHttpSettingsCollection/appServiceBackendHttpsSetting' + } + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/public443' + } + priority: 200 + ruleType: 'Basic' + } + } + { + name: 'private4433-privateVmHttpSetting-privateVmHttpSetting' + properties: { + backendAddressPool: { + id: '${appGWExpectedResourceID}/backendAddressPools/privateVmBackendPool' + } + backendHttpSettings: { + id: '${appGWExpectedResourceID}/backendHttpSettingsCollection/privateVmHttpSetting' + } + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/private4433' + } + priority: 250 + ruleType: 'Basic' + } + } + { + name: 'httpRedirect80-public443' + properties: { + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/httpRedirect80' + } + priority: 300 + redirectConfiguration: { + id: '${appGWExpectedResourceID}/redirectConfigurations/httpRedirect80' + } + ruleType: 'Basic' + } + } + { + name: 'httpRedirect8080-private4433' + properties: { + httpListener: { + id: '${appGWExpectedResourceID}/httpListeners/httpRedirect8080' + } + priority: 350 + redirectConfiguration: { + id: '${appGWExpectedResourceID}/redirectConfigurations/httpRedirect8080' + } + ruleType: 'Basic' + rewriteRuleSet: { + id: '${appGWExpectedResourceID}/rewriteRuleSets/customRewrite' + } + } + } + ] + sku: 'WAF_v2' + sslCertificates: [ + { + name: '${namePrefix}-az-apgw-x-001-ssl-certificate' + properties: { + keyVaultSecretId: nestedDependencies.outputs.certificateSecretUrl + } + } + ] + managedIdentities: { + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + rewriteRuleSets: [ + { + name: 'customRewrite' + id: '${appGWExpectedResourceID}/rewriteRuleSets/customRewrite' + properties: { + rewriteRules: [ + { + ruleSequence: 100 + conditions: [] + name: 'NewRewrite' + actionSet: { + requestHeaderConfigurations: [ + { + headerName: 'Content-Type' + headerValue: 'JSON' + } + { + headerName: 'someheader' + } + ] + responseHeaderConfigurations: [] + } + } + ] + } + } + ] + webApplicationFirewallConfiguration: { + enabled: true + fileUploadLimitInMb: 100 + firewallMode: 'Prevention' + maxRequestBodySizeInKb: 128 + requestBodyCheck: true + ruleSetType: 'OWASP' + ruleSetVersion: '3.0' + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +] diff --git a/avm/res/network/application-gateway/version.json b/avm/res/network/application-gateway/version.json new file mode 100644 index 0000000000..7fa401bdf7 --- /dev/null +++ b/avm/res/network/application-gateway/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 4d1e57a4d242ad35088e3ab37526d276dbc4bb7d Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Fri, 19 Apr 2024 09:54:43 -0600 Subject: [PATCH 24/32] feat: `avm/res/sql/managed-instance` (#1618) Migration of SQL Managed Instance module from CARML to AVM ## Pipeline Reference [![avm.res.sql.managed-instance](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml/badge.svg?branch=test%2Fsqlmi)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml) ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: Alexander Sehr --- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 2 +- .../avm.res.sql.managed-instance.yml | 85 + avm/res/sql/managed-instance/README.md | 1485 ++++++++++++ .../managed-instance/administrator/README.md | 84 + .../managed-instance/administrator/main.bicep | 39 + .../managed-instance/administrator/main.json | 77 + .../sql/managed-instance/database/README.md | 349 +++ .../README.md | 111 + .../main.bicep | 52 + .../main.json | 98 + .../README.md | 84 + .../main.bicep | 40 + .../main.json | 74 + .../sql/managed-instance/database/main.bicep | 198 ++ .../sql/managed-instance/database/main.json | 596 +++++ .../encryption-protector/README.md | 92 + .../encryption-protector/main.bicep | 42 + .../encryption-protector/main.json | 81 + avm/res/sql/managed-instance/key/README.md | 92 + avm/res/sql/managed-instance/key/main.bicep | 47 + avm/res/sql/managed-instance/key/main.json | 84 + avm/res/sql/managed-instance/main.bicep | 460 ++++ avm/res/sql/managed-instance/main.json | 1986 +++++++++++++++++ .../security-alert-policy/README.md | 92 + .../security-alert-policy/main.bicep | 41 + .../security-alert-policy/main.json | 80 + .../tests/e2e/defaults/dependencies.bicep | 288 +++ .../tests/e2e/defaults/main.test.bicep | 64 + .../tests/e2e/max/dependencies.bicep | 326 +++ .../tests/e2e/max/main.test.bicep | 189 ++ .../tests/e2e/vulnAssm/dependencies.bicep | 386 ++++ .../tests/e2e/vulnAssm/main.test.bicep | 90 + .../tests/e2e/waf-aligned/dependencies.bicep | 326 +++ .../tests/e2e/waf-aligned/main.test.bicep | 172 ++ avm/res/sql/managed-instance/version.json | 7 + .../vulnerability-assessment/README.md | 121 + .../vulnerability-assessment/main.bicep | 64 + .../vulnerability-assessment/main.json | 167 ++ .../nested_storageRoleAssignment.bicep | 20 + 40 files changed, 8691 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/avm.res.sql.managed-instance.yml create mode 100644 avm/res/sql/managed-instance/README.md create mode 100644 avm/res/sql/managed-instance/administrator/README.md create mode 100644 avm/res/sql/managed-instance/administrator/main.bicep create mode 100644 avm/res/sql/managed-instance/administrator/main.json create mode 100644 avm/res/sql/managed-instance/database/README.md create mode 100644 avm/res/sql/managed-instance/database/backup-long-term-retention-policy/README.md create mode 100644 avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.bicep create mode 100644 avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.json create mode 100644 avm/res/sql/managed-instance/database/backup-short-term-retention-policy/README.md create mode 100644 avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.bicep create mode 100644 avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.json create mode 100644 avm/res/sql/managed-instance/database/main.bicep create mode 100644 avm/res/sql/managed-instance/database/main.json create mode 100644 avm/res/sql/managed-instance/encryption-protector/README.md create mode 100644 avm/res/sql/managed-instance/encryption-protector/main.bicep create mode 100644 avm/res/sql/managed-instance/encryption-protector/main.json create mode 100644 avm/res/sql/managed-instance/key/README.md create mode 100644 avm/res/sql/managed-instance/key/main.bicep create mode 100644 avm/res/sql/managed-instance/key/main.json create mode 100644 avm/res/sql/managed-instance/main.bicep create mode 100644 avm/res/sql/managed-instance/main.json create mode 100644 avm/res/sql/managed-instance/security-alert-policy/README.md create mode 100644 avm/res/sql/managed-instance/security-alert-policy/main.bicep create mode 100644 avm/res/sql/managed-instance/security-alert-policy/main.json create mode 100644 avm/res/sql/managed-instance/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/vulnAssm/dependencies.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/sql/managed-instance/version.json create mode 100644 avm/res/sql/managed-instance/vulnerability-assessment/README.md create mode 100644 avm/res/sql/managed-instance/vulnerability-assessment/main.bicep create mode 100644 avm/res/sql/managed-instance/vulnerability-assessment/main.json create mode 100644 avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e04de640ac..0734b0fb91 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -131,7 +131,7 @@ /avm/res/service-fabric/cluster/ @Azure/avm-res-servicefabric-cluster-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/signal-r-service/signal-r/ @Azure/avm-res-signalrservice-signalr-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/signal-r-service/web-pub-sub/ @Azure/avm-res-signalrservice-webpubsub-module-owners-bicep @Azure/avm-core-team-technical-bicep -#/avm/res/sql/managed-instance/ @Azure/avm-res-sql-managedinstance-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/sql/managed-instance/ @Azure/avm-res-sql-managedinstance-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/sql/server/ @Azure/avm-res-sql-server-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/storage/storage-account/ @Azure/avm-res-storage-storageaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/synapse/private-link-hub/ @Azure/avm-res-synapse-privatelinkhub-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 40c763986f..8ad0c07acd 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -164,7 +164,7 @@ body: - "avm/res/service-fabric/cluster" - "avm/res/signal-r-service/signal-r" - "avm/res/signal-r-service/web-pub-sub" - # - "avm/res/sql/managed-instance" + - "avm/res/sql/managed-instance" - "avm/res/sql/server" - "avm/res/storage/storage-account" - "avm/res/synapse/private-link-hub" diff --git a/.github/workflows/avm.res.sql.managed-instance.yml b/.github/workflows/avm.res.sql.managed-instance.yml new file mode 100644 index 0000000000..54bb6be439 --- /dev/null +++ b/.github/workflows/avm.res.sql.managed-instance.yml @@ -0,0 +1,85 @@ +name: "avm.res.sql.managed-instance" + +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.sql.managed-instance.yml" + - "avm/res/sql/managed-instance/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/sql/managed-instance" + workflowPath: ".github/workflows/avm.res.sql.managed-instance.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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/sql/managed-instance/README.md b/avm/res/sql/managed-instance/README.md new file mode 100644 index 0000000000..1d06c5a612 --- /dev/null +++ b/avm/res/sql/managed-instance/README.md @@ -0,0 +1,1485 @@ +# SQL Managed Instances `[Microsoft.Sql/managedInstances]` + +This module deploys a SQL Managed Instance. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## 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.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Sql/managedInstances` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances) | +| `Microsoft.Sql/managedInstances/administrators` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/administrators) | +| `Microsoft.Sql/managedInstances/databases` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/databases) | +| `Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/managedInstances/databases/backupLongTermRetentionPolicies) | +| `Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies) | +| `Microsoft.Sql/managedInstances/encryptionProtector` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/managedInstances/encryptionProtector) | +| `Microsoft.Sql/managedInstances/keys` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/keys) | +| `Microsoft.Sql/managedInstances/securityAlertPolicies` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/securityAlertPolicies) | +| `Microsoft.Sql/managedInstances/vulnerabilityAssessments` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/vulnerabilityAssessments) | + +## 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/sql/managed-instance:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [With vulnerability assessment](#example-3-with-vulnerability-assessment) +- [WAF-aligned](#example-4-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module managedInstance 'br/public:avm/res/sql/managed-instance:' = { + name: 'managedInstanceDeployment' + params: { + // Required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + name: 'sqlmimin' + subnetResourceId: '' + // 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 + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "name": { + "value": "sqlmimin" + }, + "subnetResourceId": { + "value": "" + }, + // 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 managedInstance 'br/public:avm/res/sql/managed-instance:' = { + name: 'managedInstanceDeployment' + params: { + // Required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + name: 'sqlmimax' + subnetResourceId: '' + // Non-required parameters + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' + } + backupShortTermRetentionPolicies: { + name: 'default' + } + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + name: 'sqlmimax-db-001' + } + ] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '' + serverKeyType: 'AzureKeyVault' + } + hardwareFamily: 'Gen5' + keys: [ + { + name: '' + serverKeyType: 'AzureKeyVault' + uri: '' + } + ] + licenseType: 'LicenseIncluded' + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + primaryUserAssignedIdentityId: '' + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + ] + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + 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": { + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "name": { + "value": "sqlmimax" + }, + "subnetResourceId": { + "value": "" + }, + "collation": { + "value": "SQL_Latin1_General_CP1_CI_AS" + }, + "databases": { + "value": [ + { + "backupLongTermRetentionPolicies": { + "name": "default" + }, + "backupShortTermRetentionPolicies": { + "name": "default" + }, + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "name": "sqlmimax-db-001" + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "dnsZonePartner": { + "value": "" + }, + "encryptionProtectorObj": { + "value": { + "serverKeyName": "", + "serverKeyType": "AzureKeyVault" + } + }, + "hardwareFamily": { + "value": "Gen5" + }, + "keys": { + "value": [ + { + "name": "", + "serverKeyType": "AzureKeyVault", + "uri": "" + } + ] + }, + "licenseType": { + "value": "LicenseIncluded" + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "primaryUserAssignedIdentityId": { + "value": "" + }, + "proxyOverride": { + "value": "Proxy" + }, + "publicDataEndpointEnabled": { + "value": false + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + } + ] + }, + "securityAlertPoliciesObj": { + "value": { + "emailAccountAdmins": true, + "name": "default", + "state": "Enabled" + } + }, + "servicePrincipal": { + "value": "SystemAssigned" + }, + "skuName": { + "value": "GP_Gen5" + }, + "skuTier": { + "value": "GeneralPurpose" + }, + "storageSizeInGB": { + "value": 32 + }, + "timezoneId": { + "value": "UTC" + }, + "vCores": { + "value": 4 + }, + "vulnerabilityAssessmentsObj": { + "value": { + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } + } +} +``` + +
+

+ +### Example 3: _With vulnerability assessment_ + +This instance deploys the module with a vulnerability assessment. + + +

+ +via Bicep module + +```bicep +module managedInstance 'br/public:avm/res/sql/managed-instance:' = { + name: 'managedInstanceDeployment' + params: { + // Required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + name: 'sqlmivln' + subnetResourceId: '' + // Non-required parameters + location: '' + managedIdentities: { + systemAssigned: true + } + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + vulnerabilityAssessmentsObj: { + createStorageRoleAssignment: true + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + useStorageAccountAccessKey: false + } + } +} +``` + +
+

+ +

+ +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 + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "name": { + "value": "sqlmivln" + }, + "subnetResourceId": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, + "securityAlertPoliciesObj": { + "value": { + "emailAccountAdmins": true, + "name": "default", + "state": "Enabled" + } + }, + "vulnerabilityAssessmentsObj": { + "value": { + "createStorageRoleAssignment": true, + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + }, + "useStorageAccountAccessKey": false + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module managedInstance 'br/public:avm/res/sql/managed-instance:' = { + name: 'managedInstanceDeployment' + params: { + // Required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + name: 'sqlmiwaf' + subnetResourceId: '' + // Non-required parameters + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' + } + backupShortTermRetentionPolicies: { + name: 'default' + } + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + name: 'sqlmiwaf-db-001' + } + ] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '' + serverKeyType: 'AzureKeyVault' + } + hardwareFamily: 'Gen5' + keys: [ + { + name: '' + serverKeyType: 'AzureKeyVault' + uri: '' + } + ] + licenseType: 'LicenseIncluded' + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + primaryUserAssignedIdentityId: '' + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + 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": { + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "name": { + "value": "sqlmiwaf" + }, + "subnetResourceId": { + "value": "" + }, + "collation": { + "value": "SQL_Latin1_General_CP1_CI_AS" + }, + "databases": { + "value": [ + { + "backupLongTermRetentionPolicies": { + "name": "default" + }, + "backupShortTermRetentionPolicies": { + "name": "default" + }, + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "name": "sqlmiwaf-db-001" + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "dnsZonePartner": { + "value": "" + }, + "encryptionProtectorObj": { + "value": { + "serverKeyName": "", + "serverKeyType": "AzureKeyVault" + } + }, + "hardwareFamily": { + "value": "Gen5" + }, + "keys": { + "value": [ + { + "name": "", + "serverKeyType": "AzureKeyVault", + "uri": "" + } + ] + }, + "licenseType": { + "value": "LicenseIncluded" + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "primaryUserAssignedIdentityId": { + "value": "" + }, + "proxyOverride": { + "value": "Proxy" + }, + "publicDataEndpointEnabled": { + "value": false + }, + "securityAlertPoliciesObj": { + "value": { + "emailAccountAdmins": true, + "name": "default", + "state": "Enabled" + } + }, + "servicePrincipal": { + "value": "SystemAssigned" + }, + "skuName": { + "value": "GP_Gen5" + }, + "skuTier": { + "value": "GeneralPurpose" + }, + "storageSizeInGB": { + "value": 32 + }, + "timezoneId": { + "value": "UTC" + }, + "vCores": { + "value": 4 + }, + "vulnerabilityAssessmentsObj": { + "value": { + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`administratorLogin`](#parameter-administratorlogin) | string | The username used to establish jumpbox VMs. | +| [`administratorLoginPassword`](#parameter-administratorloginpassword) | securestring | The password given to the admin user. | +| [`name`](#parameter-name) | string | The name of the SQL managed instance. | +| [`subnetResourceId`](#parameter-subnetresourceid) | string | The fully qualified resource ID of the subnet on which the SQL managed instance will be placed. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`primaryUserAssignedIdentityId`](#parameter-primaryuserassignedidentityid) | string | The resource ID of a user assigned identity to be used by default. Required if "userAssignedIdentities" is not empty. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`administratorsObj`](#parameter-administratorsobj) | object | The administrator configuration. | +| [`collation`](#parameter-collation) | string | Collation of the managed instance. | +| [`databases`](#parameter-databases) | array | Databases to create in this server. | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`dnsZonePartner`](#parameter-dnszonepartner) | string | The resource ID of another managed instance whose DNS zone this managed instance will share after creation. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`encryptionProtectorObj`](#parameter-encryptionprotectorobj) | object | The encryption protection configuration. | +| [`hardwareFamily`](#parameter-hardwarefamily) | string | If the service has different generations of hardware, for the same SKU, then that can be captured here. | +| [`instancePoolResourceId`](#parameter-instancepoolresourceid) | string | The resource ID of the instance pool this managed server belongs to. | +| [`keys`](#parameter-keys) | array | The keys to configure. | +| [`licenseType`](#parameter-licensetype) | string | The license type. Possible values are 'LicenseIncluded' (regular price inclusive of a new SQL license) and 'BasePrice' (discounted AHB price for bringing your own SQL licenses). | +| [`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. | +| [`managedInstanceCreateMode`](#parameter-managedinstancecreatemode) | string | Specifies the mode of database creation. Default: Regular instance creation. Restore: Creates an instance by restoring a set of backups to specific point in time. RestorePointInTime and SourceManagedInstanceId must be specified. | +| [`minimalTlsVersion`](#parameter-minimaltlsversion) | string | Minimal TLS version allowed. | +| [`proxyOverride`](#parameter-proxyoverride) | string | Connection type used for connecting to the instance. | +| [`publicDataEndpointEnabled`](#parameter-publicdataendpointenabled) | bool | Whether or not the public data endpoint is enabled. | +| [`requestedBackupStorageRedundancy`](#parameter-requestedbackupstorageredundancy) | string | The storage account type used to store backups for this database. | +| [`restorePointInTime`](#parameter-restorepointintime) | string | Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`securityAlertPoliciesObj`](#parameter-securityalertpoliciesobj) | object | The security alert policy configuration. | +| [`servicePrincipal`](#parameter-serviceprincipal) | string | Service principal type. If using AD Authentication and applying Admin, must be set to `SystemAssigned`. Then Global Admin must allow Reader access to Azure AD for the Service Principal. | +| [`skuName`](#parameter-skuname) | string | The name of the SKU, typically, a letter + Number code, e.g. P3. | +| [`skuTier`](#parameter-skutier) | string | The tier or edition of the particular SKU, e.g. Basic, Premium. | +| [`sourceManagedInstanceId`](#parameter-sourcemanagedinstanceid) | string | The resource identifier of the source managed instance associated with create operation of this instance. | +| [`storageSizeInGB`](#parameter-storagesizeingb) | int | Storage size in GB. Minimum value: 32. Maximum value: 8192. Increments of 32 GB allowed only. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`timezoneId`](#parameter-timezoneid) | string | ID of the timezone. Allowed values are timezones supported by Windows. | +| [`vCores`](#parameter-vcores) | int | The number of vCores. Allowed values: 8, 16, 24, 32, 40, 64, 80. | +| [`vulnerabilityAssessmentsObj`](#parameter-vulnerabilityassessmentsobj) | object | The vulnerability assessment configuration. | +| [`zoneRedundant`](#parameter-zoneredundant) | bool | Whether or not multi-az is enabled. | + +### Parameter: `administratorLogin` + +The username used to establish jumpbox VMs. + +- Required: Yes +- Type: string + +### Parameter: `administratorLoginPassword` + +The password given to the admin user. + +- Required: Yes +- Type: securestring + +### Parameter: `name` + +The name of the SQL managed instance. + +- Required: Yes +- Type: string + +### Parameter: `subnetResourceId` + +The fully qualified resource ID of the subnet on which the SQL managed instance will be placed. + +- Required: Yes +- Type: string + +### Parameter: `primaryUserAssignedIdentityId` + +The resource ID of a user assigned identity to be used by default. Required if "userAssignedIdentities" is not empty. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `administratorsObj` + +The administrator configuration. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `collation` + +Collation of the managed instance. + +- Required: No +- Type: string +- Default: `'SQL_Latin1_General_CP1_CI_AS'` + +### Parameter: `databases` + +Databases to create in this server. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | string | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| [`eventHubName`](#parameter-diagnosticsettingseventhubname) | string | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | string | A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. | +| [`logCategoriesAndGroups`](#parameter-diagnosticsettingslogcategoriesandgroups) | array | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. | +| [`marketplacePartnerResourceId`](#parameter-diagnosticsettingsmarketplacepartnerresourceid) | string | The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | array | The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. | +| [`name`](#parameter-diagnosticsettingsname) | string | The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | string | Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | string | Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | string | Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. | +| [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. | +| [`enabled`](#parameter-diagnosticsettingslogcategoriesandgroupsenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `dnsZonePartner` + +The resource ID of another managed instance whose DNS zone this managed instance will share after creation. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `encryptionProtectorObj` + +The encryption protection configuration. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `hardwareFamily` + +If the service has different generations of hardware, for the same SKU, then that can be captured here. + +- Required: No +- Type: string +- Default: `'Gen5'` + +### Parameter: `instancePoolResourceId` + +The resource ID of the instance pool this managed server belongs to. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `keys` + +The keys to configure. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `licenseType` + +The license type. Possible values are 'LicenseIncluded' (regular price inclusive of a new SQL license) and 'BasePrice' (discounted AHB price for bringing your own SQL licenses). + +- Required: No +- Type: string +- Default: `'LicenseIncluded'` +- Allowed: + ```Bicep + [ + 'BasePrice' + 'LicenseIncluded' + ] + ``` + +### 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: `managedInstanceCreateMode` + +Specifies the mode of database creation. Default: Regular instance creation. Restore: Creates an instance by restoring a set of backups to specific point in time. RestorePointInTime and SourceManagedInstanceId must be specified. + +- Required: No +- Type: string +- Default: `'Default'` +- Allowed: + ```Bicep + [ + 'Default' + 'PointInTimeRestore' + ] + ``` + +### Parameter: `minimalTlsVersion` + +Minimal TLS version allowed. + +- Required: No +- Type: string +- Default: `'1.2'` +- Allowed: + ```Bicep + [ + '1.0' + '1.1' + '1.2' + 'None' + ] + ``` + +### Parameter: `proxyOverride` + +Connection type used for connecting to the instance. + +- Required: No +- Type: string +- Default: `'Proxy'` +- Allowed: + ```Bicep + [ + 'Default' + 'Proxy' + 'Redirect' + ] + ``` + +### Parameter: `publicDataEndpointEnabled` + +Whether or not the public data endpoint is enabled. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `requestedBackupStorageRedundancy` + +The storage account type used to store backups for this database. + +- Required: No +- Type: string +- Default: `'Geo'` +- Allowed: + ```Bicep + [ + 'Geo' + 'GeoZone' + 'Local' + 'Zone' + ] + ``` + +### Parameter: `restorePointInTime` + +Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. + +- Required: No +- Type: string +- Default: `''` + +### 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: `securityAlertPoliciesObj` + +The security alert policy configuration. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `servicePrincipal` + +Service principal type. If using AD Authentication and applying Admin, must be set to `SystemAssigned`. Then Global Admin must allow Reader access to Azure AD for the Service Principal. + +- Required: No +- Type: string +- Default: `'None'` +- Allowed: + ```Bicep + [ + 'None' + 'SystemAssigned' + ] + ``` + +### Parameter: `skuName` + +The name of the SKU, typically, a letter + Number code, e.g. P3. + +- Required: No +- Type: string +- Default: `'GP_Gen5'` + +### Parameter: `skuTier` + +The tier or edition of the particular SKU, e.g. Basic, Premium. + +- Required: No +- Type: string +- Default: `'GeneralPurpose'` + +### Parameter: `sourceManagedInstanceId` + +The resource identifier of the source managed instance associated with create operation of this instance. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `storageSizeInGB` + +Storage size in GB. Minimum value: 32. Maximum value: 8192. Increments of 32 GB allowed only. + +- Required: No +- Type: int +- Default: `32` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + +### Parameter: `timezoneId` + +ID of the timezone. Allowed values are timezones supported by Windows. + +- Required: No +- Type: string +- Default: `'UTC'` + +### Parameter: `vCores` + +The number of vCores. Allowed values: 8, 16, 24, 32, 40, 64, 80. + +- Required: No +- Type: int +- Default: `4` + +### Parameter: `vulnerabilityAssessmentsObj` + +The vulnerability assessment configuration. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `zoneRedundant` + +Whether or not multi-az is enabled. + +- Required: No +- Type: bool +- Default: `False` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployed managed instance. | +| `resourceGroupName` | string | The resource group of the deployed managed instance. | +| `resourceId` | string | The resource ID of the deployed managed instance. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/administrator/README.md b/avm/res/sql/managed-instance/administrator/README.md new file mode 100644 index 0000000000..87b2ba4cd3 --- /dev/null +++ b/avm/res/sql/managed-instance/administrator/README.md @@ -0,0 +1,84 @@ +# SQL Managed Instances Administrator `[Microsoft.Sql/managedInstances/administrators]` + +This module deploys a SQL Managed Instance Administrator. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/administrators` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/administrators) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`login`](#parameter-login) | string | Login name of the managed instance administrator. | +| [`sid`](#parameter-sid) | string | SID (object ID) of the managed instance administrator. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`tenantId`](#parameter-tenantid) | string | Tenant ID of the managed instance administrator. | + +### Parameter: `login` + +Login name of the managed instance administrator. + +- Required: Yes +- Type: string + +### Parameter: `sid` + +SID (object ID) of the managed instance administrator. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `tenantId` + +Tenant ID of the managed instance administrator. + +- Required: No +- Type: string +- Default: `''` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed managed instance administrator. | +| `resourceGroupName` | string | The resource group of the deployed managed instance administrator. | +| `resourceId` | string | The resource ID of the deployed managed instance administrator. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/administrator/main.bicep b/avm/res/sql/managed-instance/administrator/main.bicep new file mode 100644 index 0000000000..2b6fcc3275 --- /dev/null +++ b/avm/res/sql/managed-instance/administrator/main.bicep @@ -0,0 +1,39 @@ +metadata name = 'SQL Managed Instances Administrator' +metadata description = 'This module deploys a SQL Managed Instance Administrator.' +metadata owner = 'Azure/module-maintainers' + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Required. Login name of the managed instance administrator.') +param login string + +@description('Required. SID (object ID) of the managed instance administrator.') +param sid string + +@description('Optional. Tenant ID of the managed instance administrator.') +param tenantId string = '' + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +resource administrator 'Microsoft.Sql/managedInstances/administrators@2023-08-01-preview' = { + name: 'ActiveDirectory' + parent: managedInstance + properties: { + administratorType: 'ActiveDirectory' + login: login + sid: sid + tenantId: tenantId + } +} + +@description('The name of the deployed managed instance administrator.') +output name string = administrator.name + +@description('The resource ID of the deployed managed instance administrator.') +output resourceId string = administrator.id + +@description('The resource group of the deployed managed instance administrator.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/administrator/main.json b/avm/res/sql/managed-instance/administrator/main.json new file mode 100644 index 0000000000..bda17b0156 --- /dev/null +++ b/avm/res/sql/managed-instance/administrator/main.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "14195697460339552085" + }, + "name": "SQL Managed Instances Administrator", + "description": "This module deploys a SQL Managed Instance Administrator.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "login": { + "type": "string", + "metadata": { + "description": "Required. Login name of the managed instance administrator." + } + }, + "sid": { + "type": "string", + "metadata": { + "description": "Required. SID (object ID) of the managed instance administrator." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Tenant ID of the managed instance administrator." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/administrators", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), 'ActiveDirectory')]", + "properties": { + "administratorType": "ActiveDirectory", + "login": "[parameters('login')]", + "sid": "[parameters('sid')]", + "tenantId": "[parameters('tenantId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance administrator." + }, + "value": "ActiveDirectory" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance administrator." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/administrators', parameters('managedInstanceName'), 'ActiveDirectory')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance administrator." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/database/README.md b/avm/res/sql/managed-instance/database/README.md new file mode 100644 index 0000000000..fcf9c4ed6c --- /dev/null +++ b/avm/res/sql/managed-instance/database/README.md @@ -0,0 +1,349 @@ +# SQL Managed Instance Databases `[Microsoft.Sql/managedInstances/databases]` + +This module deploys a SQL Managed Instance Database. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## 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.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Sql/managedInstances/databases` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/databases) | +| `Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/managedInstances/databases/backupLongTermRetentionPolicies) | +| `Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the SQL managed instance database. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`longTermRetentionBackupResourceId`](#parameter-longtermretentionbackupresourceid) | string | The resource ID of the Long Term Retention backup to be used for restore of this managed database. Required if createMode is RestoreLongTermRetentionBackup. | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | +| [`recoverableDatabaseId`](#parameter-recoverabledatabaseid) | string | The resource identifier of the recoverable database associated with create operation of this database. Required if createMode is Recovery. | +| [`restorePointInTime`](#parameter-restorepointintime) | string | Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. Required if createMode is PointInTimeRestore. | +| [`sourceDatabaseId`](#parameter-sourcedatabaseid) | string | The resource identifier of the source database associated with create operation of this database. Required if createMode is PointInTimeRestore. | +| [`storageContainerSasToken`](#parameter-storagecontainersastoken) | securestring | Specifies the storage container sas token. Required if createMode is RestoreExternalBackup. | +| [`storageContainerUri`](#parameter-storagecontaineruri) | string | Specifies the uri of the storage container where backups for this restore are stored. Required if createMode is RestoreExternalBackup. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`backupLongTermRetentionPoliciesObj`](#parameter-backuplongtermretentionpoliciesobj) | object | The configuration for the backup long term retention policy definition. | +| [`backupShortTermRetentionPoliciesObj`](#parameter-backupshorttermretentionpoliciesobj) | object | The configuration for the backup short term retention policy definition. | +| [`catalogCollation`](#parameter-catalogcollation) | string | Collation of the managed instance. | +| [`collation`](#parameter-collation) | string | Collation of the managed instance database. | +| [`createMode`](#parameter-createmode) | string | Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required). | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`restorableDroppedDatabaseId`](#parameter-restorabledroppeddatabaseid) | string | The restorable dropped database resource ID to restore when creating this database. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | + +### Parameter: `name` + +The name of the SQL managed instance database. + +- Required: Yes +- Type: string + +### Parameter: `longTermRetentionBackupResourceId` + +The resource ID of the Long Term Retention backup to be used for restore of this managed database. Required if createMode is RestoreLongTermRetentionBackup. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `recoverableDatabaseId` + +The resource identifier of the recoverable database associated with create operation of this database. Required if createMode is Recovery. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `restorePointInTime` + +Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. Required if createMode is PointInTimeRestore. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `sourceDatabaseId` + +The resource identifier of the source database associated with create operation of this database. Required if createMode is PointInTimeRestore. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `storageContainerSasToken` + +Specifies the storage container sas token. Required if createMode is RestoreExternalBackup. + +- Required: No +- Type: securestring +- Default: `''` + +### Parameter: `storageContainerUri` + +Specifies the uri of the storage container where backups for this restore are stored. Required if createMode is RestoreExternalBackup. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `backupLongTermRetentionPoliciesObj` + +The configuration for the backup long term retention policy definition. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `backupShortTermRetentionPoliciesObj` + +The configuration for the backup short term retention policy definition. + +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `catalogCollation` + +Collation of the managed instance. + +- Required: No +- Type: string +- Default: `'SQL_Latin1_General_CP1_CI_AS'` + +### Parameter: `collation` + +Collation of the managed instance database. + +- Required: No +- Type: string +- Default: `'SQL_Latin1_General_CP1_CI_AS'` + +### Parameter: `createMode` + +Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required). + +- Required: No +- Type: string +- Default: `'Default'` +- Allowed: + ```Bicep + [ + 'Default' + 'PointInTimeRestore' + 'Recovery' + 'RestoreExternalBackup' + 'RestoreLongTermRetentionBackup' + ] + ``` + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | string | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| [`eventHubName`](#parameter-diagnosticsettingseventhubname) | string | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | string | A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. | +| [`logCategoriesAndGroups`](#parameter-diagnosticsettingslogcategoriesandgroups) | array | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to '' to disable log collection. | +| [`marketplacePartnerResourceId`](#parameter-diagnosticsettingsmarketplacepartnerresourceid) | string | The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`name`](#parameter-diagnosticsettingsname) | string | The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | string | Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | string | Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to '' to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | string | Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. | +| [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to 'AllLogs' to collect all logs. | + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to 'AllLogs' to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### 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: `restorableDroppedDatabaseId` + +The restorable dropped database resource ID to restore when creating this database. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployed database. | +| `resourceGroupName` | string | The resource group the database was deployed into. | +| `resourceId` | string | The resource ID of the deployed database. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/README.md b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/README.md new file mode 100644 index 0000000000..9bd34cadb3 --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/README.md @@ -0,0 +1,111 @@ +# SQL Managed Instance Database Backup Long-Term Retention Policies `[Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies]` + +This module deploys a SQL Managed Instance Database Backup Long-Term Retention Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/managedInstances/databases/backupLongTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Long Term Retention backup policy. For example "default". | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`databaseName`](#parameter-databasename) | string | The name of the parent managed instance database. Required if the template is used in a standalone deployment. | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`monthlyRetention`](#parameter-monthlyretention) | string | The monthly retention policy for an LTR backup in an ISO 8601 format. | +| [`weeklyRetention`](#parameter-weeklyretention) | string | The weekly retention policy for an LTR backup in an ISO 8601 format. | +| [`weekOfYear`](#parameter-weekofyear) | int | The week of year to take the yearly backup in an ISO 8601 format. | +| [`yearlyRetention`](#parameter-yearlyretention) | string | The yearly retention policy for an LTR backup in an ISO 8601 format. | + +### Parameter: `name` + +The name of the Long Term Retention backup policy. For example "default". + +- Required: Yes +- Type: string + +### Parameter: `databaseName` + +The name of the parent managed instance database. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `monthlyRetention` + +The monthly retention policy for an LTR backup in an ISO 8601 format. + +- Required: No +- Type: string +- Default: `'P1Y'` + +### Parameter: `weeklyRetention` + +The weekly retention policy for an LTR backup in an ISO 8601 format. + +- Required: No +- Type: string +- Default: `'P1M'` + +### Parameter: `weekOfYear` + +The week of year to take the yearly backup in an ISO 8601 format. + +- Required: No +- Type: int +- Default: `5` + +### Parameter: `yearlyRetention` + +The yearly retention policy for an LTR backup in an ISO 8601 format. + +- Required: No +- Type: string +- Default: `'P5Y'` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed database backup long-term retention policy. | +| `resourceGroupName` | string | The resource group of the deployed database backup long-term retention policy. | +| `resourceId` | string | The resource ID of the deployed database backup long-term retention policy. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.bicep b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.bicep new file mode 100644 index 0000000000..52ce6d2b23 --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.bicep @@ -0,0 +1,52 @@ +metadata name = 'SQL Managed Instance Database Backup Long-Term Retention Policies' +metadata description = 'This module deploys a SQL Managed Instance Database Backup Long-Term Retention Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Long Term Retention backup policy. For example "default".') +param name string + +@description('Conditional. The name of the parent managed instance database. Required if the template is used in a standalone deployment.') +param databaseName string + +@description('Conditional. The name of the parent managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. The week of year to take the yearly backup in an ISO 8601 format.') +param weekOfYear int = 5 + +@description('Optional. The weekly retention policy for an LTR backup in an ISO 8601 format.') +param weeklyRetention string = 'P1M' + +@description('Optional. The monthly retention policy for an LTR backup in an ISO 8601 format.') +param monthlyRetention string = 'P1Y' + +@description('Optional. The yearly retention policy for an LTR backup in an ISO 8601 format.') +param yearlyRetention string = 'P5Y' + +resource managedInstance 'Microsoft.Sql/managedInstances@2022-05-01-preview' existing = { + name: managedInstanceName + + resource managedInstaceDatabase 'databases@2022-05-01-preview' existing = { + name: databaseName + } +} + +resource backupLongTermRetentionPolicy 'Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies@2022-05-01-preview' = { + name: name + parent: managedInstance::managedInstaceDatabase + properties: { + monthlyRetention: monthlyRetention + weeklyRetention: weeklyRetention + weekOfYear: weekOfYear + yearlyRetention: yearlyRetention + } +} + +@description('The name of the deployed database backup long-term retention policy.') +output name string = backupLongTermRetentionPolicy.name + +@description('The resource ID of the deployed database backup long-term retention policy.') +output resourceId string = backupLongTermRetentionPolicy.id + +@description('The resource group of the deployed database backup long-term retention policy.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.json b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.json new file mode 100644 index 0000000000..3edd496f59 --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-long-term-retention-policy/main.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "2315344113299493188" + }, + "name": "SQL Managed Instance Database Backup Long-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Long-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Long Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance. Required if the template is used in a standalone deployment." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 5, + "metadata": { + "description": "Optional. The week of year to take the yearly backup in an ISO 8601 format." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "P1M", + "metadata": { + "description": "Optional. The weekly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "P1Y", + "metadata": { + "description": "Optional. The monthly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "P5Y", + "metadata": { + "description": "Optional. The yearly retention policy for an LTR backup in an ISO 8601 format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup long-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup long-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup long-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/README.md b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/README.md new file mode 100644 index 0000000000..623130d636 --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/README.md @@ -0,0 +1,84 @@ +# SQL Managed Instance Database Backup Short-Term Retention Policies `[Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies]` + +This module deploys a SQL Managed Instance Database Backup Short-Term Retention Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Short Term Retention backup policy. For example "default". | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`databaseName`](#parameter-databasename) | string | The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment. | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`retentionDays`](#parameter-retentiondays) | int | The backup retention period in days. This is how many days Point-in-Time Restore will be supported. | + +### Parameter: `name` + +The name of the Short Term Retention backup policy. For example "default". + +- Required: Yes +- Type: string + +### Parameter: `databaseName` + +The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `retentionDays` + +The backup retention period in days. This is how many days Point-in-Time Restore will be supported. + +- Required: No +- Type: int +- Default: `35` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed database backup short-term retention policy. | +| `resourceGroupName` | string | The resource group of the deployed database backup short-term retention policy. | +| `resourceId` | string | The resource ID of the deployed database backup short-term retention policy. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.bicep b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.bicep new file mode 100644 index 0000000000..207d0d314e --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.bicep @@ -0,0 +1,40 @@ +metadata name = 'SQL Managed Instance Database Backup Short-Term Retention Policies' +metadata description = 'This module deploys a SQL Managed Instance Database Backup Short-Term Retention Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Short Term Retention backup policy. For example "default".') +param name string + +@description('Conditional. The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment.') +param databaseName string + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. The backup retention period in days. This is how many days Point-in-Time Restore will be supported.') +param retentionDays int = 35 + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName + + resource managedInstaceDatabase 'databases@2023-08-01-preview' existing = { + name: databaseName + } +} + +resource backupShortTermRetentionPolicy 'Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies@2023-08-01-preview' = { + name: name + parent: managedInstance::managedInstaceDatabase + properties: { + retentionDays: retentionDays + } +} + +@description('The name of the deployed database backup short-term retention policy.') +output name string = backupShortTermRetentionPolicy.name + +@description('The resource ID of the deployed database backup short-term retention policy.') +output resourceId string = backupShortTermRetentionPolicy.id + +@description('The resource group of the deployed database backup short-term retention policy.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.json b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.json new file mode 100644 index 0000000000..4cee96ffa9 --- /dev/null +++ b/avm/res/sql/managed-instance/database/backup-short-term-retention-policy/main.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "4816864047763535590" + }, + "name": "SQL Managed Instance Database Backup Short-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Short-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Short Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 35, + "metadata": { + "description": "Optional. The backup retention period in days. This is how many days Point-in-Time Restore will be supported." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup short-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup short-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup short-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/database/main.bicep b/avm/res/sql/managed-instance/database/main.bicep new file mode 100644 index 0000000000..a5180004e9 --- /dev/null +++ b/avm/res/sql/managed-instance/database/main.bicep @@ -0,0 +1,198 @@ +metadata name = 'SQL Managed Instance Databases' +metadata description = 'This module deploys a SQL Managed Instance Database.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the SQL managed instance database.') +param name string + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Collation of the managed instance database.') +param collation string = 'SQL_Latin1_General_CP1_CI_AS' + +@description('Optional. Collation of the managed instance.') +param catalogCollation string = 'SQL_Latin1_General_CP1_CI_AS' + +@description('Optional. Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required).') +@allowed([ + 'Default' + 'RestoreExternalBackup' + 'PointInTimeRestore' + 'Recovery' + 'RestoreLongTermRetentionBackup' +]) +param createMode string = 'Default' + +@description('Conditional. The resource identifier of the source database associated with create operation of this database. Required if createMode is PointInTimeRestore.') +param sourceDatabaseId string = '' + +@description('Conditional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. Required if createMode is PointInTimeRestore.') +param restorePointInTime string = '' + +@description('Optional. The restorable dropped database resource ID to restore when creating this database.') +param restorableDroppedDatabaseId string = '' + +@description('Conditional. Specifies the uri of the storage container where backups for this restore are stored. Required if createMode is RestoreExternalBackup.') +param storageContainerUri string = '' + +@description('Conditional. Specifies the storage container sas token. Required if createMode is RestoreExternalBackup.') +@secure() +param storageContainerSasToken string = '' + +@description('Conditional. The resource identifier of the recoverable database associated with create operation of this database. Required if createMode is Recovery.') +param recoverableDatabaseId string = '' + +@description('Conditional. The resource ID of the Long Term Retention backup to be used for restore of this managed database. Required if createMode is RestoreLongTermRetentionBackup.') +param longTermRetentionBackupResourceId string = '' + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. The configuration for the backup short term retention policy definition.') +param backupShortTermRetentionPoliciesObj object = {} + +@description('Optional. The configuration for the backup long term retention policy definition.') +param backupLongTermRetentionPoliciesObj object = {} + +@description('Optional. Tags of the resource.') +param tags object? + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +resource database 'Microsoft.Sql/managedInstances/databases@2023-08-01-preview' = { + name: name + parent: managedInstance + location: location + tags: tags + properties: { + collation: empty(collation) ? null : collation + restorePointInTime: empty(restorePointInTime) ? null : restorePointInTime + catalogCollation: empty(catalogCollation) ? null : catalogCollation + createMode: empty(createMode) ? null : createMode + storageContainerUri: empty(storageContainerUri) ? null : storageContainerUri + sourceDatabaseId: empty(sourceDatabaseId) ? null : sourceDatabaseId + restorableDroppedDatabaseId: empty(restorableDroppedDatabaseId) ? null : restorableDroppedDatabaseId + storageContainerSasToken: empty(storageContainerSasToken) ? null : storageContainerSasToken + recoverableDatabaseId: empty(recoverableDatabaseId) ? null : recoverableDatabaseId + longTermRetentionBackupResourceId: empty(longTermRetentionBackupResourceId) ? null : longTermRetentionBackupResourceId + } +} + +resource database_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: database +} + +resource database_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' } ]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: database + } +] + +module database_backupShortTermRetentionPolicy 'backup-short-term-retention-policy/main.bicep' = if (!empty(backupShortTermRetentionPoliciesObj)) { + name: '${deployment().name}-BackupShortTRetPol' + params: { + managedInstanceName: managedInstanceName + databaseName: last(split(database.name, '/'))! + name: backupShortTermRetentionPoliciesObj.name + retentionDays: contains(backupShortTermRetentionPoliciesObj, 'retentionDays') ? backupShortTermRetentionPoliciesObj.retentionDays : 35 + } +} + +module database_backupLongTermRetentionPolicy 'backup-long-term-retention-policy/main.bicep' = if (!empty(backupLongTermRetentionPoliciesObj)) { + name: '${deployment().name}-BackupLongTRetPol' + params: { + managedInstanceName: managedInstanceName + databaseName: last(split(database.name, '/'))! + name: backupLongTermRetentionPoliciesObj.name + weekOfYear: contains(backupLongTermRetentionPoliciesObj, 'weekOfYear') ? backupLongTermRetentionPoliciesObj.weekOfYear : 5 + weeklyRetention: contains(backupLongTermRetentionPoliciesObj, 'weeklyRetention') ? backupLongTermRetentionPoliciesObj.weeklyRetention : 'P1M' + monthlyRetention: contains(backupLongTermRetentionPoliciesObj, 'monthlyRetention') ? backupLongTermRetentionPoliciesObj.monthlyRetention : 'P1Y' + yearlyRetention: contains(backupLongTermRetentionPoliciesObj, 'yearlyRetention') ? backupLongTermRetentionPoliciesObj.yearlyRetention : 'P5Y' + } +} + +@description('The name of the deployed database.') +output name string = database.name + +@description('The resource ID of the deployed database.') +output resourceId string = database.id + +@description('The resource group the database was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = database.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/avm/res/sql/managed-instance/database/main.json b/avm/res/sql/managed-instance/database/main.json new file mode 100644 index 0000000000..8ae32849aa --- /dev/null +++ b/avm/res/sql/managed-instance/database/main.json @@ -0,0 +1,596 @@ +{ + "$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.26.170.59819", + "templateHash": "15831945430809011826" + }, + "name": "SQL Managed Instance Databases", + "description": "This module deploys a SQL Managed Instance Database.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "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 + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to 'AllLogs' to collect all logs." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to '' to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SQL managed instance database." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. Collation of the managed instance database." + } + }, + "catalogCollation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. Collation of the managed instance." + } + }, + "createMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "RestoreExternalBackup", + "PointInTimeRestore", + "Recovery", + "RestoreLongTermRetentionBackup" + ], + "metadata": { + "description": "Optional. Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required)." + } + }, + "sourceDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource identifier of the source database associated with create operation of this database. Required if createMode is PointInTimeRestore." + } + }, + "restorePointInTime": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. Required if createMode is PointInTimeRestore." + } + }, + "restorableDroppedDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The restorable dropped database resource ID to restore when creating this database." + } + }, + "storageContainerUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the uri of the storage container where backups for this restore are stored. Required if createMode is RestoreExternalBackup." + } + }, + "storageContainerSasToken": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the storage container sas token. Required if createMode is RestoreExternalBackup." + } + }, + "recoverableDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource identifier of the recoverable database associated with create operation of this database. Required if createMode is Recovery." + } + }, + "longTermRetentionBackupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of the Long Term Retention backup to be used for restore of this managed database. Required if createMode is RestoreLongTermRetentionBackup." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "backupShortTermRetentionPoliciesObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the backup short term retention policy definition." + } + }, + "backupLongTermRetentionPoliciesObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the backup long term retention policy definition." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "managedInstance": { + "existing": true, + "type": "Microsoft.Sql/managedInstances", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('managedInstanceName')]" + }, + "database": { + "type": "Microsoft.Sql/managedInstances/databases", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "collation": "[if(empty(parameters('collation')), null(), parameters('collation'))]", + "restorePointInTime": "[if(empty(parameters('restorePointInTime')), null(), parameters('restorePointInTime'))]", + "catalogCollation": "[if(empty(parameters('catalogCollation')), null(), parameters('catalogCollation'))]", + "createMode": "[if(empty(parameters('createMode')), null(), parameters('createMode'))]", + "storageContainerUri": "[if(empty(parameters('storageContainerUri')), null(), parameters('storageContainerUri'))]", + "sourceDatabaseId": "[if(empty(parameters('sourceDatabaseId')), null(), parameters('sourceDatabaseId'))]", + "restorableDroppedDatabaseId": "[if(empty(parameters('restorableDroppedDatabaseId')), null(), parameters('restorableDroppedDatabaseId'))]", + "storageContainerSasToken": "[if(empty(parameters('storageContainerSasToken')), null(), parameters('storageContainerSasToken'))]", + "recoverableDatabaseId": "[if(empty(parameters('recoverableDatabaseId')), null(), parameters('recoverableDatabaseId'))]", + "longTermRetentionBackupResourceId": "[if(empty(parameters('longTermRetentionBackupResourceId')), null(), parameters('longTermRetentionBackupResourceId'))]" + }, + "dependsOn": [ + "managedInstance" + ] + }, + "database_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.Sql/managedInstances/{0}/databases/{1}', parameters('managedInstanceName'), 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": [ + "database" + ] + }, + "database_diagnosticSettings": { + "copy": { + "name": "database_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Sql/managedInstances/{0}/databases/{1}', parameters('managedInstanceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "database" + ] + }, + "database_backupShortTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupShortTermRetentionPoliciesObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-BackupShortTRetPol', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('managedInstanceName')]" + }, + "databaseName": { + "value": "[last(split(parameters('name'), '/'))]" + }, + "name": { + "value": "[parameters('backupShortTermRetentionPoliciesObj').name]" + }, + "retentionDays": "[if(contains(parameters('backupShortTermRetentionPoliciesObj'), 'retentionDays'), createObject('value', parameters('backupShortTermRetentionPoliciesObj').retentionDays), createObject('value', 35))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "4816864047763535590" + }, + "name": "SQL Managed Instance Database Backup Short-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Short-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Short Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 35, + "metadata": { + "description": "Optional. The backup retention period in days. This is how many days Point-in-Time Restore will be supported." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup short-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup short-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup short-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "database" + ] + }, + "database_backupLongTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupLongTermRetentionPoliciesObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-BackupLongTRetPol', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('managedInstanceName')]" + }, + "databaseName": { + "value": "[last(split(parameters('name'), '/'))]" + }, + "name": { + "value": "[parameters('backupLongTermRetentionPoliciesObj').name]" + }, + "weekOfYear": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'weekOfYear'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').weekOfYear), createObject('value', 5))]", + "weeklyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'weeklyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').weeklyRetention), createObject('value', 'P1M'))]", + "monthlyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'monthlyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').monthlyRetention), createObject('value', 'P1Y'))]", + "yearlyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'yearlyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').yearlyRetention), createObject('value', 'P5Y'))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "2315344113299493188" + }, + "name": "SQL Managed Instance Database Backup Long-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Long-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Long Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance. Required if the template is used in a standalone deployment." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 5, + "metadata": { + "description": "Optional. The week of year to take the yearly backup in an ISO 8601 format." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "P1M", + "metadata": { + "description": "Optional. The weekly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "P1Y", + "metadata": { + "description": "Optional. The monthly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "P5Y", + "metadata": { + "description": "Optional. The yearly retention policy for an LTR backup in an ISO 8601 format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup long-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup long-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup long-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "database" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the database was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('database', '2023-08-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/encryption-protector/README.md b/avm/res/sql/managed-instance/encryption-protector/README.md new file mode 100644 index 0000000000..18c0a5d1f8 --- /dev/null +++ b/avm/res/sql/managed-instance/encryption-protector/README.md @@ -0,0 +1,92 @@ +# SQL Managed Instance Encryption Protector `[Microsoft.Sql/managedInstances/encryptionProtector]` + +This module deploys a SQL Managed Instance Encryption Protector. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/encryptionProtector` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/managedInstances/encryptionProtector) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverKeyName`](#parameter-serverkeyname) | string | The name of the SQL managed instance key. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`autoRotationEnabled`](#parameter-autorotationenabled) | bool | Key auto rotation opt-in flag. | +| [`serverKeyType`](#parameter-serverkeytype) | string | The encryption protector type like "ServiceManaged", "AzureKeyVault". | + +### Parameter: `serverKeyName` + +The name of the SQL managed instance key. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `autoRotationEnabled` + +Key auto rotation opt-in flag. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `serverKeyType` + +The encryption protector type like "ServiceManaged", "AzureKeyVault". + +- Required: No +- Type: string +- Default: `'ServiceManaged'` +- Allowed: + ```Bicep + [ + 'AzureKeyVault' + 'ServiceManaged' + ] + ``` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed managed instance encryption protector. | +| `resourceGroupName` | string | The resource group of the deployed managed instance encryption protector. | +| `resourceId` | string | The resource ID of the deployed managed instance encryption protector. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/encryption-protector/main.bicep b/avm/res/sql/managed-instance/encryption-protector/main.bicep new file mode 100644 index 0000000000..d3b89a6f22 --- /dev/null +++ b/avm/res/sql/managed-instance/encryption-protector/main.bicep @@ -0,0 +1,42 @@ +metadata name = 'SQL Managed Instance Encryption Protector' +metadata description = 'This module deploys a SQL Managed Instance Encryption Protector.' +metadata owner = 'Azure/module-maintainers' + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Required. The name of the SQL managed instance key.') +param serverKeyName string + +@description('Optional. The encryption protector type like "ServiceManaged", "AzureKeyVault".') +@allowed([ + 'AzureKeyVault' + 'ServiceManaged' +]) +param serverKeyType string = 'ServiceManaged' + +@description('Optional. Key auto rotation opt-in flag.') +param autoRotationEnabled bool = false + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +resource encryptionProtector 'Microsoft.Sql/managedInstances/encryptionProtector@2022-05-01-preview' = { + name: 'current' + parent: managedInstance + properties: { + autoRotationEnabled: autoRotationEnabled + serverKeyName: serverKeyName + serverKeyType: serverKeyType + } +} + +@description('The name of the deployed managed instance encryption protector.') +output name string = encryptionProtector.name + +@description('The resource ID of the deployed managed instance encryption protector.') +output resourceId string = encryptionProtector.id + +@description('The resource group of the deployed managed instance encryption protector.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/encryption-protector/main.json b/avm/res/sql/managed-instance/encryption-protector/main.json new file mode 100644 index 0000000000..54edff7908 --- /dev/null +++ b/avm/res/sql/managed-instance/encryption-protector/main.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "13463643567330956322" + }, + "name": "SQL Managed Instance Encryption Protector", + "description": "This module deploys a SQL Managed Instance Encryption Protector.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "serverKeyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the SQL managed instance key." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type like \"ServiceManaged\", \"AzureKeyVault\"." + } + }, + "autoRotationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Key auto rotation opt-in flag." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/encryptionProtector", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), 'current')]", + "properties": { + "autoRotationEnabled": "[parameters('autoRotationEnabled')]", + "serverKeyName": "[parameters('serverKeyName')]", + "serverKeyType": "[parameters('serverKeyType')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance encryption protector." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance encryption protector." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/encryptionProtector', parameters('managedInstanceName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance encryption protector." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/key/README.md b/avm/res/sql/managed-instance/key/README.md new file mode 100644 index 0000000000..3cc7f5cf6e --- /dev/null +++ b/avm/res/sql/managed-instance/key/README.md @@ -0,0 +1,92 @@ +# SQL Managed Instance Keys `[Microsoft.Sql/managedInstances/keys]` + +This module deploys a SQL Managed Instance Key. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/keys` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/keys) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the key. Must follow the [__] pattern. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverKeyType`](#parameter-serverkeytype) | string | The encryption protector type like "ServiceManaged", "AzureKeyVault". | +| [`uri`](#parameter-uri) | string | The URI of the key. If the ServerKeyType is AzureKeyVault, then either the URI or the keyVaultName/keyName combination is required. | + +### Parameter: `name` + +The name of the key. Must follow the [__] pattern. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `serverKeyType` + +The encryption protector type like "ServiceManaged", "AzureKeyVault". + +- Required: No +- Type: string +- Default: `'ServiceManaged'` +- Allowed: + ```Bicep + [ + 'AzureKeyVault' + 'ServiceManaged' + ] + ``` + +### Parameter: `uri` + +The URI of the key. If the ServerKeyType is AzureKeyVault, then either the URI or the keyVaultName/keyName combination is required. + +- Required: No +- Type: string +- Default: `''` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed managed instance key. | +| `resourceGroupName` | string | The resource group of the deployed managed instance key. | +| `resourceId` | string | The resource ID of the deployed managed instance key. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/key/main.bicep b/avm/res/sql/managed-instance/key/main.bicep new file mode 100644 index 0000000000..d95ac84f4b --- /dev/null +++ b/avm/res/sql/managed-instance/key/main.bicep @@ -0,0 +1,47 @@ +metadata name = 'SQL Managed Instance Keys' +metadata description = 'This module deploys a SQL Managed Instance Key.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the key. Must follow the [__] pattern.') +param name string + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. The encryption protector type like "ServiceManaged", "AzureKeyVault".') +@allowed([ + 'AzureKeyVault' + 'ServiceManaged' +]) +param serverKeyType string = 'ServiceManaged' + +@description('Optional. The URI of the key. If the ServerKeyType is AzureKeyVault, then either the URI or the keyVaultName/keyName combination is required.') +param uri string = '' + +var splittedKeyUri = split(uri, '/') + +// if serverManaged, use serverManaged, if uri provided use concated uri value +// MUST match the pattern '__' +var serverKeyName = empty(uri) ? 'ServiceManaged' : '${split(splittedKeyUri[2], '.')[0]}_${splittedKeyUri[4]}_${splittedKeyUri[5]}' + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +resource key 'Microsoft.Sql/managedInstances/keys@2023-08-01-preview' = { + name: !empty(name) ? name : serverKeyName + parent: managedInstance + properties: { + serverKeyType: serverKeyType + uri: uri + } +} + +@description('The name of the deployed managed instance key.') +output name string = key.name + +@description('The resource ID of the deployed managed instance key.') +output resourceId string = key.id + +@description('The resource group of the deployed managed instance key.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/key/main.json b/avm/res/sql/managed-instance/key/main.json new file mode 100644 index 0000000000..403f395866 --- /dev/null +++ b/avm/res/sql/managed-instance/key/main.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "101283708334468532" + }, + "name": "SQL Managed Instance Keys", + "description": "This module deploys a SQL Managed Instance Key.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key. Must follow the [__] pattern." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type like \"ServiceManaged\", \"AzureKeyVault\"." + } + }, + "uri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The URI of the key. If the ServerKeyType is AzureKeyVault, then either the URI or the keyVaultName/keyName combination is required." + } + } + }, + "variables": { + "splittedKeyUri": "[split(parameters('uri'), '/')]", + "serverKeyName": "[if(empty(parameters('uri')), 'ServiceManaged', format('{0}_{1}_{2}', split(variables('splittedKeyUri')[2], '.')[0], variables('splittedKeyUri')[4], variables('splittedKeyUri')[5]))]" + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/keys", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance key." + }, + "value": "[if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance key." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/keys', parameters('managedInstanceName'), if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance key." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/main.bicep b/avm/res/sql/managed-instance/main.bicep new file mode 100644 index 0000000000..c180440c4d --- /dev/null +++ b/avm/res/sql/managed-instance/main.bicep @@ -0,0 +1,460 @@ +metadata name = 'SQL Managed Instances' +metadata description = 'This module deploys a SQL Managed Instance.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the SQL managed instance.') +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Required. The username used to establish jumpbox VMs.') +param administratorLogin string + +@description('Required. The password given to the admin user.') +@secure() +param administratorLoginPassword string + +@description('Required. The fully qualified resource ID of the subnet on which the SQL managed instance will be placed.') +param subnetResourceId string + +@description('Optional. The name of the SKU, typically, a letter + Number code, e.g. P3.') +param skuName string = 'GP_Gen5' + +@description('Optional. The tier or edition of the particular SKU, e.g. Basic, Premium.') +param skuTier string = 'GeneralPurpose' + +@description('Optional. Storage size in GB. Minimum value: 32. Maximum value: 8192. Increments of 32 GB allowed only.') +param storageSizeInGB int = 32 + +@description('Optional. The number of vCores. Allowed values: 8, 16, 24, 32, 40, 64, 80.') +param vCores int = 4 + +@description('Optional. The license type. Possible values are \'LicenseIncluded\' (regular price inclusive of a new SQL license) and \'BasePrice\' (discounted AHB price for bringing your own SQL licenses).') +@allowed([ + 'LicenseIncluded' + 'BasePrice' +]) +param licenseType string = 'LicenseIncluded' + +@description('Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here.') +param hardwareFamily string = 'Gen5' + +@description('Optional. Whether or not multi-az is enabled.') +param zoneRedundant bool = false + +@description('Optional. Service principal type. If using AD Authentication and applying Admin, must be set to `SystemAssigned`. Then Global Admin must allow Reader access to Azure AD for the Service Principal.') +@allowed([ + 'None' + 'SystemAssigned' +]) +param servicePrincipal string = 'None' + +@description('Optional. Specifies the mode of database creation. Default: Regular instance creation. Restore: Creates an instance by restoring a set of backups to specific point in time. RestorePointInTime and SourceManagedInstanceId must be specified.') +@allowed([ + 'Default' + 'PointInTimeRestore' +]) +param managedInstanceCreateMode string = 'Default' + +@description('Optional. The resource ID of another managed instance whose DNS zone this managed instance will share after creation.') +param dnsZonePartner string = '' + +@description('Optional. Collation of the managed instance.') +param collation string = 'SQL_Latin1_General_CP1_CI_AS' + +@description('Optional. Connection type used for connecting to the instance.') +@allowed([ + 'Proxy' + 'Redirect' + 'Default' +]) +param proxyOverride string = 'Proxy' + +@description('Optional. Whether or not the public data endpoint is enabled.') +param publicDataEndpointEnabled bool = false + +@description('Optional. ID of the timezone. Allowed values are timezones supported by Windows.') +param timezoneId string = 'UTC' + +@description('Optional. The resource ID of the instance pool this managed server belongs to.') +param instancePoolResourceId string = '' + +@description('Optional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database.') +param restorePointInTime string = '' + +@description('Optional. The resource identifier of the source managed instance associated with create operation of this instance.') +param sourceManagedInstanceId string = '' + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentitiesType + +@description('Conditional. The resource ID of a user assigned identity to be used by default. Required if "userAssignedIdentities" is not empty.') +param primaryUserAssignedIdentityId string = '' + +@description('Optional. Databases to create in this server.') +param databases array = [] + +@description('Optional. The vulnerability assessment configuration.') +param vulnerabilityAssessmentsObj object = {} + +@description('Optional. The security alert policy configuration.') +param securityAlertPoliciesObj object = {} + +@description('Optional. The keys to configure.') +param keys array = [] + +@description('Optional. The encryption protection configuration.') +param encryptionProtectorObj object = {} + +@description('Optional. The administrator configuration.') +param administratorsObj object = {} + +@description('Optional. Minimal TLS version allowed.') +@allowed([ + 'None' + '1.0' + '1.1' + '1.2' +]) +param minimalTlsVersion string = '1.2' + +@description('Optional. The storage account type used to store backups for this database.') +@allowed([ + 'Geo' + 'GeoZone' + 'Local' + 'Zone' +]) +param requestedBackupStorageRedundancy string = 'Geo' + +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') + 'Reservation Purchaser': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'SQL DB Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec') + 'SQL Managed Instance Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d') + 'SQL Security Manager': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3') + 'SQL Server Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437') + 'SqlDb Migration Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '189207d4-bb67-4208-a635-b06afe8b2c57') + 'SqlMI Migration Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1d335eef-eee1-47fe-a9e0-53214eba8872') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.sql-managedinstance.${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 managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' = { + name: name + location: location + tags: tags + identity: identity + sku: { + name: skuName + tier: skuTier + family: hardwareFamily + } + + properties: { + managedInstanceCreateMode: managedInstanceCreateMode + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + subnetId: subnetResourceId + licenseType: licenseType + vCores: vCores + storageSizeInGB: storageSizeInGB + collation: collation + dnsZonePartner: !empty(dnsZonePartner) ? dnsZonePartner : null + publicDataEndpointEnabled: publicDataEndpointEnabled + sourceManagedInstanceId: !empty(sourceManagedInstanceId) ? sourceManagedInstanceId : null + restorePointInTime: !empty(restorePointInTime) ? restorePointInTime : null + proxyOverride: proxyOverride + timezoneId: timezoneId + instancePoolId: !empty(instancePoolResourceId) ? instancePoolResourceId : null + primaryUserAssignedIdentityId: !empty(primaryUserAssignedIdentityId) ? primaryUserAssignedIdentityId : null + requestedBackupStorageRedundancy: requestedBackupStorageRedundancy + zoneRedundant: zoneRedundant + servicePrincipal: { + type: servicePrincipal + } + minimalTlsVersion: minimalTlsVersion + } +} + +resource managedInstance_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: managedInstance +} + +resource managedInstance_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: managedInstance + } +] + +resource managedInstance_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(managedInstance.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: managedInstance + } +] + +module managedInstance_databases 'database/main.bicep' = [for (database, index) in databases: { + name: '${uniqueString(deployment().name, location)}-SqlMi-DB-${index}' + params: { + name: database.name + managedInstanceName: managedInstance.name + catalogCollation: contains(database, 'catalogCollation') ? database.catalogCollation : 'SQL_Latin1_General_CP1_CI_AS' + collation: contains(database, 'collation') ? database.collation : 'SQL_Latin1_General_CP1_CI_AS' + createMode: contains(database, 'createMode') ? database.createMode : 'Default' + diagnosticSettings: database.?diagnosticSettings + location: contains(database, 'location') ? database.location : managedInstance.location + lock: database.?lock ?? lock + longTermRetentionBackupResourceId: contains(database, 'longTermRetentionBackupResourceId') ? database.longTermRetentionBackupResourceId : '' + recoverableDatabaseId: contains(database, 'recoverableDatabaseId') ? database.recoverableDatabaseId : '' + restorableDroppedDatabaseId: contains(database, 'restorableDroppedDatabaseId') ? database.restorableDroppedDatabaseId : '' + restorePointInTime: contains(database, 'restorePointInTime') ? database.restorePointInTime : '' + sourceDatabaseId: contains(database, 'sourceDatabaseId') ? database.sourceDatabaseId : '' + storageContainerSasToken: contains(database, 'storageContainerSasToken') ? database.storageContainerSasToken : '' + storageContainerUri: contains(database, 'storageContainerUri') ? database.storageContainerUri : '' + tags: database.?tags ?? tags + backupShortTermRetentionPoliciesObj: contains(database, 'backupShortTermRetentionPolicies') ? database.backupShortTermRetentionPolicies : {} + backupLongTermRetentionPoliciesObj: contains(database, 'backupLongTermRetentionPolicies') ? database.backupLongTermRetentionPolicies : {} + } +}] + +module managedInstance_securityAlertPolicy 'security-alert-policy/main.bicep' = if (!empty(securityAlertPoliciesObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-SecAlertPol' + params: { + managedInstanceName: managedInstance.name + name: securityAlertPoliciesObj.name + emailAccountAdmins: contains(securityAlertPoliciesObj, 'emailAccountAdmins') ? securityAlertPoliciesObj.emailAccountAdmins : false + state: contains(securityAlertPoliciesObj, 'state') ? securityAlertPoliciesObj.state : 'Disabled' + } +} + +module managedInstance_vulnerabilityAssessment 'vulnerability-assessment/main.bicep' = if (!empty(vulnerabilityAssessmentsObj) && (managedIdentities.?systemAssigned ?? false)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-VulnAssessm' + params: { + managedInstanceName: managedInstance.name + name: vulnerabilityAssessmentsObj.name + recurringScansEmails: contains(vulnerabilityAssessmentsObj, 'recurringScansEmails') ? vulnerabilityAssessmentsObj.recurringScansEmails : [] + recurringScansEmailSubscriptionAdmins: contains(vulnerabilityAssessmentsObj, 'recurringScansEmailSubscriptionAdmins') ? vulnerabilityAssessmentsObj.recurringScansEmailSubscriptionAdmins : false + recurringScansIsEnabled: contains(vulnerabilityAssessmentsObj, 'recurringScansIsEnabled') ? vulnerabilityAssessmentsObj.recurringScansIsEnabled : false + storageAccountResourceId: vulnerabilityAssessmentsObj.storageAccountResourceId + useStorageAccountAccessKey: contains(vulnerabilityAssessmentsObj, 'useStorageAccountAccessKey') ? vulnerabilityAssessmentsObj.useStorageAccountAccessKey : false + createStorageRoleAssignment: contains(vulnerabilityAssessmentsObj, 'createStorageRoleAssignment') ? vulnerabilityAssessmentsObj.createStorageRoleAssignment : true + } + dependsOn: [ + managedInstance_securityAlertPolicy + ] +} + +module managedInstance_keys 'key/main.bicep' = [for (key, index) in keys: { + name: '${uniqueString(deployment().name, location)}-SqlMi-Key-${index}' + params: { + name: key.name + managedInstanceName: managedInstance.name + serverKeyType: contains(key, 'serverKeyType') ? key.serverKeyType : 'ServiceManaged' + uri: contains(key, 'uri') ? key.uri : '' + } +}] + +module managedInstance_encryptionProtector 'encryption-protector/main.bicep' = if (!empty(encryptionProtectorObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-EncryProtector' + params: { + managedInstanceName: managedInstance.name + serverKeyName: encryptionProtectorObj.serverKeyName + serverKeyType: contains(encryptionProtectorObj, 'serverKeyType') ? encryptionProtectorObj.serverKeyType : 'ServiceManaged' + autoRotationEnabled: contains(encryptionProtectorObj, 'autoRotationEnabled') ? encryptionProtectorObj.autoRotationEnabled : true + } + dependsOn: [ + managedInstance_keys + ] +} + +module managedInstance_administrator 'administrator/main.bicep' = if (!empty(administratorsObj)) { + name: '${uniqueString(deployment().name, location)}-SqlMi-Admin' + params: { + managedInstanceName: managedInstance.name + login: administratorsObj.name + sid: administratorsObj.sid + tenantId: contains(administratorsObj, 'tenantId') ? administratorsObj.tenantId : '' + } +} + +@description('The name of the deployed managed instance.') +output name string = managedInstance.name + +@description('The resource ID of the deployed managed instance.') +output resourceId string = managedInstance.id + +@description('The resource group of the deployed managed instance.') +output resourceGroupName string = resourceGroup().name + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string = managedInstance.?identity.?principalId ?? '' + +@description('The location the resource was deployed into.') +output location string = managedInstance.location + +// =============== // +// Definitions // +// =============== // + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + 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 diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs.') + categoryGroup: string? + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics.') + category: string + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? diff --git a/avm/res/sql/managed-instance/main.json b/avm/res/sql/managed-instance/main.json new file mode 100644 index 0000000000..1786333be1 --- /dev/null +++ b/avm/res/sql/managed-instance/main.json @@ -0,0 +1,1986 @@ +{ + "$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.26.170.59819", + "templateHash": "9634979761136417752" + }, + "name": "SQL Managed Instances", + "description": "This module deploys a SQL Managed Instance.", + "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 + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SQL managed instance." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "administratorLogin": { + "type": "string", + "metadata": { + "description": "Required. The username used to establish jumpbox VMs." + } + }, + "administratorLoginPassword": { + "type": "securestring", + "metadata": { + "description": "Required. The password given to the admin user." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The fully qualified resource ID of the subnet on which the SQL managed instance will be placed." + } + }, + "skuName": { + "type": "string", + "defaultValue": "GP_Gen5", + "metadata": { + "description": "Optional. The name of the SKU, typically, a letter + Number code, e.g. P3." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "GeneralPurpose", + "metadata": { + "description": "Optional. The tier or edition of the particular SKU, e.g. Basic, Premium." + } + }, + "storageSizeInGB": { + "type": "int", + "defaultValue": 32, + "metadata": { + "description": "Optional. Storage size in GB. Minimum value: 32. Maximum value: 8192. Increments of 32 GB allowed only." + } + }, + "vCores": { + "type": "int", + "defaultValue": 4, + "metadata": { + "description": "Optional. The number of vCores. Allowed values: 8, 16, 24, 32, 40, 64, 80." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "LicenseIncluded", + "allowedValues": [ + "LicenseIncluded", + "BasePrice" + ], + "metadata": { + "description": "Optional. The license type. Possible values are 'LicenseIncluded' (regular price inclusive of a new SQL license) and 'BasePrice' (discounted AHB price for bringing your own SQL licenses)." + } + }, + "hardwareFamily": { + "type": "string", + "defaultValue": "Gen5", + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not multi-az is enabled." + } + }, + "servicePrincipal": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "None", + "SystemAssigned" + ], + "metadata": { + "description": "Optional. Service principal type. If using AD Authentication and applying Admin, must be set to `SystemAssigned`. Then Global Admin must allow Reader access to Azure AD for the Service Principal." + } + }, + "managedInstanceCreateMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "PointInTimeRestore" + ], + "metadata": { + "description": "Optional. Specifies the mode of database creation. Default: Regular instance creation. Restore: Creates an instance by restoring a set of backups to specific point in time. RestorePointInTime and SourceManagedInstanceId must be specified." + } + }, + "dnsZonePartner": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of another managed instance whose DNS zone this managed instance will share after creation." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. Collation of the managed instance." + } + }, + "proxyOverride": { + "type": "string", + "defaultValue": "Proxy", + "allowedValues": [ + "Proxy", + "Redirect", + "Default" + ], + "metadata": { + "description": "Optional. Connection type used for connecting to the instance." + } + }, + "publicDataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not the public data endpoint is enabled." + } + }, + "timezoneId": { + "type": "string", + "defaultValue": "UTC", + "metadata": { + "description": "Optional. ID of the timezone. Allowed values are timezones supported by Windows." + } + }, + "instancePoolResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the instance pool this managed server belongs to." + } + }, + "restorePointInTime": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database." + } + }, + "sourceManagedInstanceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource identifier of the source managed instance associated with create operation of this instance." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "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 of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "primaryUserAssignedIdentityId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of a user assigned identity to be used by default. Required if \"userAssignedIdentities\" is not empty." + } + }, + "databases": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Databases to create in this server." + } + }, + "vulnerabilityAssessmentsObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The vulnerability assessment configuration." + } + }, + "securityAlertPoliciesObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The security alert policy configuration." + } + }, + "keys": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The keys to configure." + } + }, + "encryptionProtectorObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The encryption protection configuration." + } + }, + "administratorsObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The administrator configuration." + } + }, + "minimalTlsVersion": { + "type": "string", + "defaultValue": "1.2", + "allowedValues": [ + "None", + "1.0", + "1.1", + "1.2" + ], + "metadata": { + "description": "Optional. Minimal TLS version allowed." + } + }, + "requestedBackupStorageRedundancy": { + "type": "string", + "defaultValue": "Geo", + "allowedValues": [ + "Geo", + "GeoZone", + "Local", + "Zone" + ], + "metadata": { + "description": "Optional. The storage account type used to store backups for this database." + } + } + }, + "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')]", + "Reservation Purchaser": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "SQL DB Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec')]", + "SQL Managed Instance Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d')]", + "SQL Security Manager": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3')]", + "SQL Server Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437')]", + "SqlDb Migration Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '189207d4-bb67-4208-a635-b06afe8b2c57')]", + "SqlMI Migration Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1d335eef-eee1-47fe-a9e0-53214eba8872')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.sql-managedinstance.{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" + } + } + } + } + }, + "managedInstance": { + "type": "Microsoft.Sql/managedInstances", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "sku": { + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]", + "family": "[parameters('hardwareFamily')]" + }, + "properties": { + "managedInstanceCreateMode": "[parameters('managedInstanceCreateMode')]", + "administratorLogin": "[parameters('administratorLogin')]", + "administratorLoginPassword": "[parameters('administratorLoginPassword')]", + "subnetId": "[parameters('subnetResourceId')]", + "licenseType": "[parameters('licenseType')]", + "vCores": "[parameters('vCores')]", + "storageSizeInGB": "[parameters('storageSizeInGB')]", + "collation": "[parameters('collation')]", + "dnsZonePartner": "[if(not(empty(parameters('dnsZonePartner'))), parameters('dnsZonePartner'), null())]", + "publicDataEndpointEnabled": "[parameters('publicDataEndpointEnabled')]", + "sourceManagedInstanceId": "[if(not(empty(parameters('sourceManagedInstanceId'))), parameters('sourceManagedInstanceId'), null())]", + "restorePointInTime": "[if(not(empty(parameters('restorePointInTime'))), parameters('restorePointInTime'), null())]", + "proxyOverride": "[parameters('proxyOverride')]", + "timezoneId": "[parameters('timezoneId')]", + "instancePoolId": "[if(not(empty(parameters('instancePoolResourceId'))), parameters('instancePoolResourceId'), null())]", + "primaryUserAssignedIdentityId": "[if(not(empty(parameters('primaryUserAssignedIdentityId'))), parameters('primaryUserAssignedIdentityId'), null())]", + "requestedBackupStorageRedundancy": "[parameters('requestedBackupStorageRedundancy')]", + "zoneRedundant": "[parameters('zoneRedundant')]", + "servicePrincipal": { + "type": "[parameters('servicePrincipal')]" + }, + "minimalTlsVersion": "[parameters('minimalTlsVersion')]" + } + }, + "managedInstance_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.Sql/managedInstances/{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": [ + "managedInstance" + ] + }, + "managedInstance_diagnosticSettings": { + "copy": { + "name": "managedInstance_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Sql/managedInstances/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "managedInstance" + ] + }, + "managedInstance_roleAssignments": { + "copy": { + "name": "managedInstance_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Sql/managedInstances/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Sql/managedInstances', 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": [ + "managedInstance" + ] + }, + "managedInstance_databases": { + "copy": { + "name": "managedInstance_databases", + "count": "[length(parameters('databases'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-DB-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('databases')[copyIndex()].name]" + }, + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "catalogCollation": "[if(contains(parameters('databases')[copyIndex()], 'catalogCollation'), createObject('value', parameters('databases')[copyIndex()].catalogCollation), createObject('value', 'SQL_Latin1_General_CP1_CI_AS'))]", + "collation": "[if(contains(parameters('databases')[copyIndex()], 'collation'), createObject('value', parameters('databases')[copyIndex()].collation), createObject('value', 'SQL_Latin1_General_CP1_CI_AS'))]", + "createMode": "[if(contains(parameters('databases')[copyIndex()], 'createMode'), createObject('value', parameters('databases')[copyIndex()].createMode), createObject('value', 'Default'))]", + "diagnosticSettings": { + "value": "[tryGet(parameters('databases')[copyIndex()], 'diagnosticSettings')]" + }, + "location": "[if(contains(parameters('databases')[copyIndex()], 'location'), createObject('value', parameters('databases')[copyIndex()].location), createObject('value', reference('managedInstance', '2023-08-01-preview', 'full').location))]", + "lock": { + "value": "[coalesce(tryGet(parameters('databases')[copyIndex()], 'lock'), parameters('lock'))]" + }, + "longTermRetentionBackupResourceId": "[if(contains(parameters('databases')[copyIndex()], 'longTermRetentionBackupResourceId'), createObject('value', parameters('databases')[copyIndex()].longTermRetentionBackupResourceId), createObject('value', ''))]", + "recoverableDatabaseId": "[if(contains(parameters('databases')[copyIndex()], 'recoverableDatabaseId'), createObject('value', parameters('databases')[copyIndex()].recoverableDatabaseId), createObject('value', ''))]", + "restorableDroppedDatabaseId": "[if(contains(parameters('databases')[copyIndex()], 'restorableDroppedDatabaseId'), createObject('value', parameters('databases')[copyIndex()].restorableDroppedDatabaseId), createObject('value', ''))]", + "restorePointInTime": "[if(contains(parameters('databases')[copyIndex()], 'restorePointInTime'), createObject('value', parameters('databases')[copyIndex()].restorePointInTime), createObject('value', ''))]", + "sourceDatabaseId": "[if(contains(parameters('databases')[copyIndex()], 'sourceDatabaseId'), createObject('value', parameters('databases')[copyIndex()].sourceDatabaseId), createObject('value', ''))]", + "storageContainerSasToken": "[if(contains(parameters('databases')[copyIndex()], 'storageContainerSasToken'), createObject('value', parameters('databases')[copyIndex()].storageContainerSasToken), createObject('value', ''))]", + "storageContainerUri": "[if(contains(parameters('databases')[copyIndex()], 'storageContainerUri'), createObject('value', parameters('databases')[copyIndex()].storageContainerUri), createObject('value', ''))]", + "tags": { + "value": "[coalesce(tryGet(parameters('databases')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "backupShortTermRetentionPoliciesObj": "[if(contains(parameters('databases')[copyIndex()], 'backupShortTermRetentionPolicies'), createObject('value', parameters('databases')[copyIndex()].backupShortTermRetentionPolicies), createObject('value', createObject()))]", + "backupLongTermRetentionPoliciesObj": "[if(contains(parameters('databases')[copyIndex()], 'backupLongTermRetentionPolicies'), createObject('value', parameters('databases')[copyIndex()].backupLongTermRetentionPolicies), createObject('value', createObject()))]" + }, + "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.26.170.59819", + "templateHash": "15831945430809011826" + }, + "name": "SQL Managed Instance Databases", + "description": "This module deploys a SQL Managed Instance Database.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "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 + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to 'AllLogs' to collect all logs." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to '' to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SQL managed instance database." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. Collation of the managed instance database." + } + }, + "catalogCollation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. Collation of the managed instance." + } + }, + "createMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "RestoreExternalBackup", + "PointInTimeRestore", + "Recovery", + "RestoreLongTermRetentionBackup" + ], + "metadata": { + "description": "Optional. Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required)." + } + }, + "sourceDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource identifier of the source database associated with create operation of this database. Required if createMode is PointInTimeRestore." + } + }, + "restorePointInTime": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. Required if createMode is PointInTimeRestore." + } + }, + "restorableDroppedDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The restorable dropped database resource ID to restore when creating this database." + } + }, + "storageContainerUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the uri of the storage container where backups for this restore are stored. Required if createMode is RestoreExternalBackup." + } + }, + "storageContainerSasToken": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Conditional. Specifies the storage container sas token. Required if createMode is RestoreExternalBackup." + } + }, + "recoverableDatabaseId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource identifier of the recoverable database associated with create operation of this database. Required if createMode is Recovery." + } + }, + "longTermRetentionBackupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of the Long Term Retention backup to be used for restore of this managed database. Required if createMode is RestoreLongTermRetentionBackup." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "backupShortTermRetentionPoliciesObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the backup short term retention policy definition." + } + }, + "backupLongTermRetentionPoliciesObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the backup long term retention policy definition." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "managedInstance": { + "existing": true, + "type": "Microsoft.Sql/managedInstances", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('managedInstanceName')]" + }, + "database": { + "type": "Microsoft.Sql/managedInstances/databases", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "collation": "[if(empty(parameters('collation')), null(), parameters('collation'))]", + "restorePointInTime": "[if(empty(parameters('restorePointInTime')), null(), parameters('restorePointInTime'))]", + "catalogCollation": "[if(empty(parameters('catalogCollation')), null(), parameters('catalogCollation'))]", + "createMode": "[if(empty(parameters('createMode')), null(), parameters('createMode'))]", + "storageContainerUri": "[if(empty(parameters('storageContainerUri')), null(), parameters('storageContainerUri'))]", + "sourceDatabaseId": "[if(empty(parameters('sourceDatabaseId')), null(), parameters('sourceDatabaseId'))]", + "restorableDroppedDatabaseId": "[if(empty(parameters('restorableDroppedDatabaseId')), null(), parameters('restorableDroppedDatabaseId'))]", + "storageContainerSasToken": "[if(empty(parameters('storageContainerSasToken')), null(), parameters('storageContainerSasToken'))]", + "recoverableDatabaseId": "[if(empty(parameters('recoverableDatabaseId')), null(), parameters('recoverableDatabaseId'))]", + "longTermRetentionBackupResourceId": "[if(empty(parameters('longTermRetentionBackupResourceId')), null(), parameters('longTermRetentionBackupResourceId'))]" + }, + "dependsOn": [ + "managedInstance" + ] + }, + "database_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.Sql/managedInstances/{0}/databases/{1}', parameters('managedInstanceName'), 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": [ + "database" + ] + }, + "database_diagnosticSettings": { + "copy": { + "name": "database_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Sql/managedInstances/{0}/databases/{1}', parameters('managedInstanceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "database" + ] + }, + "database_backupShortTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupShortTermRetentionPoliciesObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-BackupShortTRetPol', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('managedInstanceName')]" + }, + "databaseName": { + "value": "[last(split(parameters('name'), '/'))]" + }, + "name": { + "value": "[parameters('backupShortTermRetentionPoliciesObj').name]" + }, + "retentionDays": "[if(contains(parameters('backupShortTermRetentionPoliciesObj'), 'retentionDays'), createObject('value', parameters('backupShortTermRetentionPoliciesObj').retentionDays), createObject('value', 35))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "4816864047763535590" + }, + "name": "SQL Managed Instance Database Backup Short-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Short-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Short Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 35, + "metadata": { + "description": "Optional. The backup retention period in days. This is how many days Point-in-Time Restore will be supported." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup short-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup short-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupShortTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup short-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "database" + ] + }, + "database_backupLongTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupLongTermRetentionPoliciesObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-BackupLongTRetPol', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('managedInstanceName')]" + }, + "databaseName": { + "value": "[last(split(parameters('name'), '/'))]" + }, + "name": { + "value": "[parameters('backupLongTermRetentionPoliciesObj').name]" + }, + "weekOfYear": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'weekOfYear'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').weekOfYear), createObject('value', 5))]", + "weeklyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'weeklyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').weeklyRetention), createObject('value', 'P1M'))]", + "monthlyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'monthlyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').monthlyRetention), createObject('value', 'P1Y'))]", + "yearlyRetention": "[if(contains(parameters('backupLongTermRetentionPoliciesObj'), 'yearlyRetention'), createObject('value', parameters('backupLongTermRetentionPoliciesObj').yearlyRetention), createObject('value', 'P5Y'))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "2315344113299493188" + }, + "name": "SQL Managed Instance Database Backup Long-Term Retention Policies", + "description": "This module deploys a SQL Managed Instance Database Backup Long-Term Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Long Term Retention backup policy. For example \"default\"." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance database. Required if the template is used in a standalone deployment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent managed instance. Required if the template is used in a standalone deployment." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 5, + "metadata": { + "description": "Optional. The week of year to take the yearly backup in an ISO 8601 format." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "P1M", + "metadata": { + "description": "Optional. The weekly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "P1Y", + "metadata": { + "description": "Optional. The monthly retention policy for an LTR backup in an ISO 8601 format." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "P5Y", + "metadata": { + "description": "Optional. The yearly retention policy for an LTR backup in an ISO 8601 format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database backup long-term retention policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database backup long-term retention policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases/backupLongTermRetentionPolicies', parameters('managedInstanceName'), parameters('databaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database backup long-term retention policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "database" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/databases', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the database was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('database', '2023-08-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "managedInstance" + ] + }, + "managedInstance_securityAlertPolicy": { + "condition": "[not(empty(parameters('securityAlertPoliciesObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-SecAlertPol', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('securityAlertPoliciesObj').name]" + }, + "emailAccountAdmins": "[if(contains(parameters('securityAlertPoliciesObj'), 'emailAccountAdmins'), createObject('value', parameters('securityAlertPoliciesObj').emailAccountAdmins), createObject('value', false()))]", + "state": "[if(contains(parameters('securityAlertPoliciesObj'), 'state'), createObject('value', parameters('securityAlertPoliciesObj').state), createObject('value', 'Disabled'))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "16936228680401372562" + }, + "name": "SQL Managed Instance Security Alert Policies", + "description": "This module deploys a SQL Managed Instance Security Alert Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security alert policy." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "state": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Enables advanced data security features, like recuring vulnerability assesment scans and ATP. If enabled, storage account must be provided." + } + }, + "emailAccountAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/securityAlertPolicies", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "properties": { + "state": "[parameters('state')]", + "emailAccountAdmins": "[parameters('emailAccountAdmins')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed security alert policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed security alert policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/securityAlertPolicies', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed security alert policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedInstance" + ] + }, + "managedInstance_vulnerabilityAssessment": { + "condition": "[and(not(empty(parameters('vulnerabilityAssessmentsObj'))), coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-VulnAssessm', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('vulnerabilityAssessmentsObj').name]" + }, + "recurringScansEmails": "[if(contains(parameters('vulnerabilityAssessmentsObj'), 'recurringScansEmails'), createObject('value', parameters('vulnerabilityAssessmentsObj').recurringScansEmails), createObject('value', createArray()))]", + "recurringScansEmailSubscriptionAdmins": "[if(contains(parameters('vulnerabilityAssessmentsObj'), 'recurringScansEmailSubscriptionAdmins'), createObject('value', parameters('vulnerabilityAssessmentsObj').recurringScansEmailSubscriptionAdmins), createObject('value', false()))]", + "recurringScansIsEnabled": "[if(contains(parameters('vulnerabilityAssessmentsObj'), 'recurringScansIsEnabled'), createObject('value', parameters('vulnerabilityAssessmentsObj').recurringScansIsEnabled), createObject('value', false()))]", + "storageAccountResourceId": { + "value": "[parameters('vulnerabilityAssessmentsObj').storageAccountResourceId]" + }, + "useStorageAccountAccessKey": "[if(contains(parameters('vulnerabilityAssessmentsObj'), 'useStorageAccountAccessKey'), createObject('value', parameters('vulnerabilityAssessmentsObj').useStorageAccountAccessKey), createObject('value', false()))]", + "createStorageRoleAssignment": "[if(contains(parameters('vulnerabilityAssessmentsObj'), 'createStorageRoleAssignment'), createObject('value', parameters('vulnerabilityAssessmentsObj').createStorageRoleAssignment), createObject('value', true()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "15455405847587423486" + }, + "name": "SQL Managed Instance Vulnerability Assessments", + "description": "This module deploys a SQL Managed Instance Vulnerability Assessment.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "recurringScansIsEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Recurring scans state." + } + }, + "recurringScansEmailSubscriptionAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators." + } + }, + "recurringScansEmails": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the scan notification is sent." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. A blob storage to hold the scan results." + } + }, + "useStorageAccountAccessKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL MI system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account." + } + }, + "createStorageRoleAssignment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/vulnerabilityAssessments", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "properties": { + "storageContainerPath": "[format('https://{0}.blob.{1}/vulnerability-assessment/', last(split(parameters('storageAccountResourceId'), '/')), environment().suffixes.storage)]", + "storageAccountAccessKey": "[if(parameters('useStorageAccountAccessKey'), listKeys(parameters('storageAccountResourceId'), '2019-06-01').keys[0].value, null())]", + "recurringScans": { + "isEnabled": "[parameters('recurringScansIsEnabled')]", + "emailSubscriptionAdmins": "[parameters('recurringScansEmailSubscriptionAdmins')]", + "emails": "[parameters('recurringScansEmails')]" + } + } + }, + { + "condition": "[and(not(parameters('useStorageAccountAccessKey')), parameters('createStorageRoleAssignment'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sbdc-rbac', parameters('managedInstanceName'))]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedInstanceIdentityPrincipalId": { + "value": "[reference(resourceId('Microsoft.Sql/managedInstances', parameters('managedInstanceName')), '2023-08-01-preview', 'full').identity.principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "18021215853157074333" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Storage account name." + } + }, + "managedInstanceIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. Managed Identity Principal ID." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(format('{0}-{1}-Storage-Blob-Data-Contributor', resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('managedInstanceIdentityPrincipalId')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[parameters('managedInstanceIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + } + } + ] + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed vulnerability assessment." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed vulnerability assessment." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/vulnerabilityAssessments', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed vulnerability assessment." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedInstance", + "managedInstance_securityAlertPolicy" + ] + }, + "managedInstance_keys": { + "copy": { + "name": "managedInstance_keys", + "count": "[length(parameters('keys'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('keys')[copyIndex()].name]" + }, + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "serverKeyType": "[if(contains(parameters('keys')[copyIndex()], 'serverKeyType'), createObject('value', parameters('keys')[copyIndex()].serverKeyType), createObject('value', 'ServiceManaged'))]", + "uri": "[if(contains(parameters('keys')[copyIndex()], 'uri'), createObject('value', parameters('keys')[copyIndex()].uri), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "101283708334468532" + }, + "name": "SQL Managed Instance Keys", + "description": "This module deploys a SQL Managed Instance Key.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key. Must follow the [__] pattern." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type like \"ServiceManaged\", \"AzureKeyVault\"." + } + }, + "uri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The URI of the key. If the ServerKeyType is AzureKeyVault, then either the URI or the keyVaultName/keyName combination is required." + } + } + }, + "variables": { + "splittedKeyUri": "[split(parameters('uri'), '/')]", + "serverKeyName": "[if(empty(parameters('uri')), 'ServiceManaged', format('{0}_{1}_{2}', split(variables('splittedKeyUri')[2], '.')[0], variables('splittedKeyUri')[4], variables('splittedKeyUri')[5]))]" + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/keys", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance key." + }, + "value": "[if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance key." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/keys', parameters('managedInstanceName'), if(not(empty(parameters('name'))), parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance key." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedInstance" + ] + }, + "managedInstance_encryptionProtector": { + "condition": "[not(empty(parameters('encryptionProtectorObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-EncryProtector', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "serverKeyName": { + "value": "[parameters('encryptionProtectorObj').serverKeyName]" + }, + "serverKeyType": "[if(contains(parameters('encryptionProtectorObj'), 'serverKeyType'), createObject('value', parameters('encryptionProtectorObj').serverKeyType), createObject('value', 'ServiceManaged'))]", + "autoRotationEnabled": "[if(contains(parameters('encryptionProtectorObj'), 'autoRotationEnabled'), createObject('value', parameters('encryptionProtectorObj').autoRotationEnabled), createObject('value', true()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "13463643567330956322" + }, + "name": "SQL Managed Instance Encryption Protector", + "description": "This module deploys a SQL Managed Instance Encryption Protector.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "serverKeyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the SQL managed instance key." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type like \"ServiceManaged\", \"AzureKeyVault\"." + } + }, + "autoRotationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Key auto rotation opt-in flag." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/encryptionProtector", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), 'current')]", + "properties": { + "autoRotationEnabled": "[parameters('autoRotationEnabled')]", + "serverKeyName": "[parameters('serverKeyName')]", + "serverKeyType": "[parameters('serverKeyType')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance encryption protector." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance encryption protector." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/encryptionProtector', parameters('managedInstanceName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance encryption protector." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedInstance", + "managedInstance_keys" + ] + }, + "managedInstance_administrator": { + "condition": "[not(empty(parameters('administratorsObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SqlMi-Admin', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "managedInstanceName": { + "value": "[parameters('name')]" + }, + "login": { + "value": "[parameters('administratorsObj').name]" + }, + "sid": { + "value": "[parameters('administratorsObj').sid]" + }, + "tenantId": "[if(contains(parameters('administratorsObj'), 'tenantId'), createObject('value', parameters('administratorsObj').tenantId), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "14195697460339552085" + }, + "name": "SQL Managed Instances Administrator", + "description": "This module deploys a SQL Managed Instance Administrator.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "login": { + "type": "string", + "metadata": { + "description": "Required. Login name of the managed instance administrator." + } + }, + "sid": { + "type": "string", + "metadata": { + "description": "Required. SID (object ID) of the managed instance administrator." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Tenant ID of the managed instance administrator." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/administrators", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), 'ActiveDirectory')]", + "properties": { + "administratorType": "ActiveDirectory", + "login": "[parameters('login')]", + "sid": "[parameters('sid')]", + "tenantId": "[parameters('tenantId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance administrator." + }, + "value": "ActiveDirectory" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance administrator." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/administrators', parameters('managedInstanceName'), 'ActiveDirectory')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance administrator." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedInstance" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed managed instance." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed managed instance." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed managed instance." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[coalesce(tryGet(tryGet(reference('managedInstance', '2023-08-01-preview', 'full'), 'identity'), 'principalId'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('managedInstance', '2023-08-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/security-alert-policy/README.md b/avm/res/sql/managed-instance/security-alert-policy/README.md new file mode 100644 index 0000000000..29b532c3a8 --- /dev/null +++ b/avm/res/sql/managed-instance/security-alert-policy/README.md @@ -0,0 +1,92 @@ +# SQL Managed Instance Security Alert Policies `[Microsoft.Sql/managedInstances/securityAlertPolicies]` + +This module deploys a SQL Managed Instance Security Alert Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/managedInstances/securityAlertPolicies` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/securityAlertPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the security alert policy. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`emailAccountAdmins`](#parameter-emailaccountadmins) | bool | Specifies that the schedule scan notification will be is sent to the subscription administrators. | +| [`state`](#parameter-state) | string | Enables advanced data security features, like recuring vulnerability assesment scans and ATP. If enabled, storage account must be provided. | + +### Parameter: `name` + +The name of the security alert policy. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `emailAccountAdmins` + +Specifies that the schedule scan notification will be is sent to the subscription administrators. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `state` + +Enables advanced data security features, like recuring vulnerability assesment scans and ATP. If enabled, storage account must be provided. + +- Required: No +- Type: string +- Default: `'Disabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed security alert policy. | +| `resourceGroupName` | string | The resource group of the deployed security alert policy. | +| `resourceId` | string | The resource ID of the deployed security alert policy. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/security-alert-policy/main.bicep b/avm/res/sql/managed-instance/security-alert-policy/main.bicep new file mode 100644 index 0000000000..1928a343fe --- /dev/null +++ b/avm/res/sql/managed-instance/security-alert-policy/main.bicep @@ -0,0 +1,41 @@ +metadata name = 'SQL Managed Instance Security Alert Policies' +metadata description = 'This module deploys a SQL Managed Instance Security Alert Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the security alert policy.') +param name string + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. Enables advanced data security features, like recuring vulnerability assesment scans and ATP. If enabled, storage account must be provided.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param state string = 'Disabled' + +@description('Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators.') +param emailAccountAdmins bool = false + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +resource securityAlertPolicy 'Microsoft.Sql/managedInstances/securityAlertPolicies@2023-08-01-preview' = { + name: name + parent: managedInstance + properties: { + state: state + emailAccountAdmins: emailAccountAdmins + } +} + +@description('The name of the deployed security alert policy.') +output name string = securityAlertPolicy.name + +@description('The resource ID of the deployed security alert policy.') +output resourceId string = securityAlertPolicy.id + +@description('The resource group of the deployed security alert policy.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/security-alert-policy/main.json b/avm/res/sql/managed-instance/security-alert-policy/main.json new file mode 100644 index 0000000000..021521bfa3 --- /dev/null +++ b/avm/res/sql/managed-instance/security-alert-policy/main.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "16936228680401372562" + }, + "name": "SQL Managed Instance Security Alert Policies", + "description": "This module deploys a SQL Managed Instance Security Alert Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security alert policy." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "state": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Enables advanced data security features, like recuring vulnerability assesment scans and ATP. If enabled, storage account must be provided." + } + }, + "emailAccountAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/securityAlertPolicies", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "properties": { + "state": "[parameters('state')]", + "emailAccountAdmins": "[parameters('emailAccountAdmins')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed security alert policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed security alert policy." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/securityAlertPolicies', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed security alert policy." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/tests/e2e/defaults/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..b1f97e3ddf --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,288 @@ +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Network Security Group to create.') +param networkSecurityGroupName string + +@description('Required. The name of the Route Table to create.') +param routeTableName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '10.0.0.0/16' +var addressPrefixString = replace(replace(addressPrefix, '.', '-'), '/', '-') + +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-04-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-sqlmgmt-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI provisioning Control Plane Deployment and Authentication Service' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'SqlManagement' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1438' + '1440' + '1452' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corpsaw-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetSaw' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1440' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corppublic-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability through Corpnet ranges' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetPublic' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 102 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-healthprobe-in-${addressPrefixString}-v10' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 103 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 104 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-services-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI services outbound traffic over https' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + destinationPortRanges: [ + '443' + '12000' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_subnet-${addressPrefixString}-to-vnetlocal' + properties: { + addressPrefix: addressPrefix + nextHopType: 'VnetLocal' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage' + properties: { + addressPrefix: 'Storage' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-SqlManagement' + properties: { + addressPrefix: 'SqlManagement' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureMonitor' + properties: { + addressPrefix: 'AzureMonitor' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetSaw' + properties: { + addressPrefix: 'CorpNetSaw' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetPublic' + properties: { + addressPrefix: 'CorpNetPublic' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureActiveDirectory' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.westeurope' + properties: { + addressPrefix: 'AzureCloud.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.northeurope' + properties: { + addressPrefix: 'AzureCloud.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.westeurope' + properties: { + addressPrefix: 'Storage.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.northeurope' + properties: { + addressPrefix: 'Storage.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.westeurope' + properties: { + addressPrefix: 'EventHub.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.northeurope' + properties: { + addressPrefix: 'EventHub.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'ManagedInstance' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + routeTable: { + id: routeTable.id + } + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'managedInstanceDelegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + ] + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id diff --git a/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..e6eda07e9e --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,64 @@ +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}-sql.managedinstances-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'sqlmimin' + +@description('Optional. The password to leverage for the login.') +@secure() +param password string = newGuid() + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + networkSecurityGroupName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}-${serviceShort}' + location: resourceLocation + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + } +}] diff --git a/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..20398fd372 --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/max/dependencies.bicep @@ -0,0 +1,326 @@ +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Network Security Group to create.') +param networkSecurityGroupName string + +@description('Required. The name of the Route Table to create.') +param routeTableName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '10.0.0.0/16' +var addressPrefixString = replace(replace(addressPrefix, '.', '-'), '/', '-') + +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-04-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-sqlmgmt-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI provisioning Control Plane Deployment and Authentication Service' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'SqlManagement' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1438' + '1440' + '1452' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corpsaw-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetSaw' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1440' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corppublic-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability through Corpnet ranges' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetPublic' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 102 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-healthprobe-in-${addressPrefixString}-v10' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 103 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 104 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-services-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI services outbound traffic over https' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + destinationPortRanges: [ + '443' + '12000' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_subnet-${addressPrefixString}-to-vnetlocal' + properties: { + addressPrefix: addressPrefix + nextHopType: 'VnetLocal' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage' + properties: { + addressPrefix: 'Storage' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-SqlManagement' + properties: { + addressPrefix: 'SqlManagement' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureMonitor' + properties: { + addressPrefix: 'AzureMonitor' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetSaw' + properties: { + addressPrefix: 'CorpNetSaw' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetPublic' + properties: { + addressPrefix: 'CorpNetPublic' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureActiveDirectory' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.${location}' + properties: { + addressPrefix: 'AzureCloud.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.${location}' + properties: { + addressPrefix: 'Storage.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.${location}' + properties: { + addressPrefix: 'EventHub.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'ManagedInstance' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + routeTable: { + id: routeTable.id + } + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'managedInstanceDelegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + ] + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: true + softDeleteRetentionInDays: 7 + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + + resource key 'keys@2022-07-01' = { + name: 'keyEncryptionKey' + properties: { + kty: 'RSA' + } + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault::key.id}-${location}-${managedIdentity.id}-Key-Reader-RoleAssignment') + scope: keyVault::key + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + principalType: 'ServicePrincipal' + } +} + +@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 URL of the created Key Vault Encryption Key.') +output keyVaultEncryptionKeyUrl string = keyVault::key.properties.keyUriWithVersion + +@description('The name of the created Key Vault Encryption Key.') +output keyVaultKeyName string = keyVault::key.name + +@description('The name of the created Key Vault.') +output keyVaultName string = keyVault.name diff --git a/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..b45ce3cbb7 --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/max/main.test.bicep @@ -0,0 +1,189 @@ +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}-sql.managedinstances-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'sqlmimax' + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + +@description('Optional. The password to leverage for the login.') +@secure() +param password string = newGuid() + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + // Adding base time to make the name unique as purge protection must be enabled (but may not be longer than 24 characters total) + keyVaultName: 'dep${namePrefix}kv${serviceShort}${substring(uniqueString(baseTime), 0, 3)}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + networkSecurityGroupName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + location: resourceLocation + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: toLower('dep${namePrefix}x${serviceShort}01') + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' + } + backupShortTermRetentionPolicies: { + name: 'default' + } + name: '${namePrefix}-${serviceShort}-db-001' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + } + ] + diagnosticSettings: [ + { + name: 'customSetting' + logCategoriesAndGroups:[ + { + categoryGroup: 'allLogs' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + } + hardwareFamily: 'Gen5' + keys: [ + { + name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + licenseType: 'LicenseIncluded' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + 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' + } + ] + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +}] diff --git a/avm/res/sql/managed-instance/tests/e2e/vulnAssm/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/dependencies.bicep new file mode 100644 index 0000000000..d06ccfa76e --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/dependencies.bicep @@ -0,0 +1,386 @@ +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Network Security Group to create.') +param networkSecurityGroupName string + +@description('Required. The name of the Route Table to create.') +param routeTableName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Storage Account to create.') +param storageAccountName string + +var addressPrefix = '10.0.0.0/16' +var addressPrefixString = replace(replace(addressPrefix, '.', '-'), '/', '-') + +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-04-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-sqlmgmt-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI provisioning Control Plane Deployment and Authentication Service' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'SqlManagement' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1438' + '1440' + '1452' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corpsaw-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetSaw' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1440' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corppublic-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability through Corpnet ranges' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetPublic' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 102 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-healthprobe-in-${addressPrefixString}-v10' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 103 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 104 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-aad-out-${addressPrefixString}-v11' + properties: { + description: 'Allow communication with Azure Active Directory over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'AzureActiveDirectory' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-onedsc-out-${addressPrefixString}-v11' + properties: { + description: 'Allow communication with the One DS Collector over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'OneDsCollector' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-services-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI services outbound traffic over https' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + destinationPortRanges: [ + '443' + '12000' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'mi-strg-p-out-${addressPrefixString}-v11' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'Storage.eastus' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + { + name: 'mi-strg-s-out-${addressPrefixString}-v11' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'Storage.westus' + access: 'Allow' + priority: 105 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_subnet-${addressPrefixString}-to-vnetlocal' + properties: { + addressPrefix: addressPrefix + nextHopType: 'VnetLocal' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage' + properties: { + addressPrefix: 'Storage' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.eastus' + properties: { + addressPrefix: 'Storage.eastus' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.westus' + properties: { + addressPrefix: 'Storage.westus' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-SqlManagement' + properties: { + addressPrefix: 'SqlManagement' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureMonitor' + properties: { + addressPrefix: 'AzureMonitor' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetSaw' + properties: { + addressPrefix: 'CorpNetSaw' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetPublic' + properties: { + addressPrefix: 'CorpNetPublic' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureActiveDirectory' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-OneDsCollector' + properties: { + addressPrefix: 'OneDsCollector' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.westeurope' + properties: { + addressPrefix: 'AzureCloud.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.northeurope' + properties: { + addressPrefix: 'AzureCloud.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.westeurope' + properties: { + addressPrefix: 'Storage.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.northeurope' + properties: { + addressPrefix: 'Storage.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.westeurope' + properties: { + addressPrefix: 'EventHub.westeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.northeurope' + properties: { + addressPrefix: 'EventHub.northeurope' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'ManagedInstance' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + routeTable: { + id: routeTable.id + } + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'managedInstanceDelegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + ] + } +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: storageAccountName + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + } +} + +@description('The resource ID of the created Storage Account.') +output storageAccountResourceId string = storageAccount.id + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id diff --git a/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep new file mode 100644 index 0000000000..a51971ca80 --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/vulnAssm/main.test.bicep @@ -0,0 +1,90 @@ +targetScope = 'subscription' + +metadata name = 'With vulnerability assessment' +metadata description = 'This instance deploys the module with a vulnerability assessment.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-sql.managedinstances-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'sqlmivln' + +@description('Optional. The password to leverage for the login.') +@secure() +param password string = newGuid() + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + networkSecurityGroupName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + location: resourceLocation + storageAccountName: toLower('dep${namePrefix}v${serviceShort}01') + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + managedIdentities: { + systemAssigned: true + } + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId + useStorageAccountAccessKey: false + createStorageRoleAssignment: true + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +}] diff --git a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..20398fd372 --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,326 @@ +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Network Security Group to create.') +param networkSecurityGroupName string + +@description('Required. The name of the Route Table to create.') +param routeTableName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '10.0.0.0/16' +var addressPrefixString = replace(replace(addressPrefix, '.', '-'), '/', '-') + +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-04-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-sqlmgmt-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI provisioning Control Plane Deployment and Authentication Service' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'SqlManagement' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1438' + '1440' + '1452' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corpsaw-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetSaw' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + '1440' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-corppublic-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI Supportability through Corpnet ranges' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'CorpNetPublic' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 102 + direction: 'Inbound' + destinationPortRanges: [ + '9000' + '9003' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-healthprobe-in-${addressPrefixString}-v10' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 103 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-in-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 104 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-services-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI services outbound traffic over https' + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + destinationPortRanges: [ + '443' + '12000' + ] + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-internal-out-${addressPrefixString}-v10' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: addressPrefix + destinationAddressPrefix: addressPrefix + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'Microsoft.Sql-managedInstances_UseOnly_subnet-${addressPrefixString}-to-vnetlocal' + properties: { + addressPrefix: addressPrefix + nextHopType: 'VnetLocal' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage' + properties: { + addressPrefix: 'Storage' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-SqlManagement' + properties: { + addressPrefix: 'SqlManagement' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureMonitor' + properties: { + addressPrefix: 'AzureMonitor' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetSaw' + properties: { + addressPrefix: 'CorpNetSaw' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-CorpNetPublic' + properties: { + addressPrefix: 'CorpNetPublic' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureActiveDirectory' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-AzureCloud.${location}' + properties: { + addressPrefix: 'AzureCloud.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-Storage.${location}' + properties: { + addressPrefix: 'Storage.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + { + name: 'Microsoft.Sql-managedInstances_UseOnly_mi-EventHub.${location}' + properties: { + addressPrefix: 'EventHub.${location}' + nextHopType: 'Internet' + hasBgpOverride: false + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'ManagedInstance' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + routeTable: { + id: routeTable.id + } + networkSecurityGroup: { + id: networkSecurityGroup.id + } + delegations: [ + { + name: 'managedInstanceDelegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + ] + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: true + softDeleteRetentionInDays: 7 + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + + resource key 'keys@2022-07-01' = { + name: 'keyEncryptionKey' + properties: { + kty: 'RSA' + } + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault::key.id}-${location}-${managedIdentity.id}-Key-Reader-RoleAssignment') + scope: keyVault::key + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + principalType: 'ServicePrincipal' + } +} + +@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 URL of the created Key Vault Encryption Key.') +output keyVaultEncryptionKeyUrl string = keyVault::key.properties.keyUriWithVersion + +@description('The name of the created Key Vault Encryption Key.') +output keyVaultKeyName string = keyVault::key.name + +@description('The name of the created Key Vault.') +output keyVaultName string = keyVault.name diff --git a/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..7cbd5f19b5 --- /dev/null +++ b/avm/res/sql/managed-instance/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,172 @@ +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}-sql.managedinstances-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'sqlmiwaf' + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') + +@description('Optional. The password to leverage for the login.') +@secure() +param password string = newGuid() + +@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: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + // Adding base time to make the name unique as purge protection must be enabled (but may not be longer than 24 characters total) + keyVaultName: 'dep${namePrefix}kv${serviceShort}${substring(uniqueString(baseTime), 0, 3)}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + networkSecurityGroupName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + location: resourceLocation + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: toLower('dep${namePrefix}wf${serviceShort}01') + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}-${serviceShort}' + administratorLogin: 'adminUserName' + administratorLoginPassword: password + subnetResourceId: nestedDependencies.outputs.subnetResourceId + collation: 'SQL_Latin1_General_CP1_CI_AS' + databases: [ + { + backupLongTermRetentionPolicies: { + name: 'default' + } + backupShortTermRetentionPolicies: { + name: 'default' + } + name: '${namePrefix}-${serviceShort}-db-001' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + } + ] + diagnosticSettings: [ + { + name: 'customSetting' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + dnsZonePartner: '' + encryptionProtectorObj: { + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + } + hardwareFamily: 'Gen5' + keys: [ + { + name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + licenseType: 'LicenseIncluded' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + proxyOverride: 'Proxy' + publicDataEndpointEnabled: false + securityAlertPoliciesObj: { + emailAccountAdmins: true + name: 'default' + state: 'Enabled' + } + servicePrincipal: 'SystemAssigned' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + storageSizeInGB: 32 + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + timezoneId: 'UTC' + vCores: 4 + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +}] diff --git a/avm/res/sql/managed-instance/version.json b/avm/res/sql/managed-instance/version.json new file mode 100644 index 0000000000..7fa401bdf7 --- /dev/null +++ b/avm/res/sql/managed-instance/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/README.md b/avm/res/sql/managed-instance/vulnerability-assessment/README.md new file mode 100644 index 0000000000..62c9a59da2 --- /dev/null +++ b/avm/res/sql/managed-instance/vulnerability-assessment/README.md @@ -0,0 +1,121 @@ +# SQL Managed Instance Vulnerability Assessments `[Microsoft.Sql/managedInstances/vulnerabilityAssessments]` + +This module deploys a SQL Managed Instance Vulnerability Assessment. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Sql/managedInstances/vulnerabilityAssessments` | [2023-08-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/managedInstances/vulnerabilityAssessments) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the vulnerability assessment. | +| [`storageAccountResourceId`](#parameter-storageaccountresourceid) | string | A blob storage to hold the scan results. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`managedInstanceName`](#parameter-managedinstancename) | string | The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`createStorageRoleAssignment`](#parameter-createstorageroleassignment) | bool | Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account. | +| [`recurringScansEmails`](#parameter-recurringscansemails) | array | Specifies an array of email addresses to which the scan notification is sent. | +| [`recurringScansEmailSubscriptionAdmins`](#parameter-recurringscansemailsubscriptionadmins) | bool | Specifies that the schedule scan notification will be is sent to the subscription administrators. | +| [`recurringScansIsEnabled`](#parameter-recurringscansisenabled) | bool | Recurring scans state. | +| [`useStorageAccountAccessKey`](#parameter-usestorageaccountaccesskey) | bool | Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL MI system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account. | + +### Parameter: `name` + +The name of the vulnerability assessment. + +- Required: Yes +- Type: string + +### Parameter: `storageAccountResourceId` + +A blob storage to hold the scan results. + +- Required: Yes +- Type: string + +### Parameter: `managedInstanceName` + +The name of the parent SQL managed instance. Required if the template is used in a standalone deployment. + +- Required: Yes +- Type: string + +### Parameter: `createStorageRoleAssignment` + +Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `recurringScansEmails` + +Specifies an array of email addresses to which the scan notification is sent. + +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `recurringScansEmailSubscriptionAdmins` + +Specifies that the schedule scan notification will be is sent to the subscription administrators. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `recurringScansIsEnabled` + +Recurring scans state. + +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `useStorageAccountAccessKey` + +Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL MI system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account. + +- Required: No +- Type: bool +- Default: `False` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed vulnerability assessment. | +| `resourceGroupName` | string | The resource group of the deployed vulnerability assessment. | +| `resourceId` | string | The resource ID of the deployed vulnerability assessment. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep b/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep new file mode 100644 index 0000000000..4bac1770f6 --- /dev/null +++ b/avm/res/sql/managed-instance/vulnerability-assessment/main.bicep @@ -0,0 +1,64 @@ +metadata name = 'SQL Managed Instance Vulnerability Assessments' +metadata description = 'This module deploys a SQL Managed Instance Vulnerability Assessment.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the vulnerability assessment.') +param name string + +@description('Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment.') +param managedInstanceName string + +@description('Optional. Recurring scans state.') +param recurringScansIsEnabled bool = false + +@description('Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators.') +param recurringScansEmailSubscriptionAdmins bool = false + +@description('Optional. Specifies an array of email addresses to which the scan notification is sent.') +param recurringScansEmails array = [] + +@description('Required. A blob storage to hold the scan results.') +param storageAccountResourceId string + +@description('Optional. Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL MI system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account.') +param useStorageAccountAccessKey bool = false + +@description('Optional. Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account.') +param createStorageRoleAssignment bool = true + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' existing = { + name: managedInstanceName +} + +// Assign SQL MI MSI access to storage account +module storageAccount_sbdc_rbac 'modules/nested_storageRoleAssignment.bicep' = if (!useStorageAccountAccessKey && createStorageRoleAssignment) { + name: '${managedInstance.name}-sbdc-rbac' + scope: resourceGroup(split(storageAccountResourceId, '/')[4]) + params: { + storageAccountName: last(split(storageAccountResourceId, '/')) + managedInstanceIdentityPrincipalId: managedInstance.identity.principalId + } +} + +resource vulnerabilityAssessment 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2023-08-01-preview' = { + name: name + parent: managedInstance + properties: { + storageContainerPath: 'https://${last(split(storageAccountResourceId, '/'))}.blob.${environment().suffixes.storage}/vulnerability-assessment/' + storageAccountAccessKey: useStorageAccountAccessKey ? listKeys(storageAccountResourceId, '2019-06-01').keys[0].value : any(null) + recurringScans: { + isEnabled: recurringScansIsEnabled + emailSubscriptionAdmins: recurringScansEmailSubscriptionAdmins + emails: recurringScansEmails + } + } +} + +@description('The name of the deployed vulnerability assessment.') +output name string = vulnerabilityAssessment.name + +@description('The resource ID of the deployed vulnerability assessment.') +output resourceId string = vulnerabilityAssessment.id + +@description('The resource group of the deployed vulnerability assessment.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/main.json b/avm/res/sql/managed-instance/vulnerability-assessment/main.json new file mode 100644 index 0000000000..8d0fef695c --- /dev/null +++ b/avm/res/sql/managed-instance/vulnerability-assessment/main.json @@ -0,0 +1,167 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "15455405847587423486" + }, + "name": "SQL Managed Instance Vulnerability Assessments", + "description": "This module deploys a SQL Managed Instance Vulnerability Assessment.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "managedInstanceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL managed instance. Required if the template is used in a standalone deployment." + } + }, + "recurringScansIsEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Recurring scans state." + } + }, + "recurringScansEmailSubscriptionAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be is sent to the subscription administrators." + } + }, + "recurringScansEmails": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the scan notification is sent." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. A blob storage to hold the scan results." + } + }, + "useStorageAccountAccessKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL MI system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account." + } + }, + "createStorageRoleAssignment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/vulnerabilityAssessments", + "apiVersion": "2023-08-01-preview", + "name": "[format('{0}/{1}', parameters('managedInstanceName'), parameters('name'))]", + "properties": { + "storageContainerPath": "[format('https://{0}.blob.{1}/vulnerability-assessment/', last(split(parameters('storageAccountResourceId'), '/')), environment().suffixes.storage)]", + "storageAccountAccessKey": "[if(parameters('useStorageAccountAccessKey'), listKeys(parameters('storageAccountResourceId'), '2019-06-01').keys[0].value, null())]", + "recurringScans": { + "isEnabled": "[parameters('recurringScansIsEnabled')]", + "emailSubscriptionAdmins": "[parameters('recurringScansEmailSubscriptionAdmins')]", + "emails": "[parameters('recurringScansEmails')]" + } + } + }, + { + "condition": "[and(not(parameters('useStorageAccountAccessKey')), parameters('createStorageRoleAssignment'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sbdc-rbac', parameters('managedInstanceName'))]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedInstanceIdentityPrincipalId": { + "value": "[reference(resourceId('Microsoft.Sql/managedInstances', parameters('managedInstanceName')), '2023-08-01-preview', 'full').identity.principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "18021215853157074333" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Storage account name." + } + }, + "managedInstanceIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. Managed Identity Principal ID." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(format('{0}-{1}-Storage-Blob-Data-Contributor', resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('managedInstanceIdentityPrincipalId')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[parameters('managedInstanceIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + } + } + ] + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed vulnerability assessment." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed vulnerability assessment." + }, + "value": "[resourceId('Microsoft.Sql/managedInstances/vulnerabilityAssessments', parameters('managedInstanceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed vulnerability assessment." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep b/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep new file mode 100644 index 0000000000..7ef3f28a53 --- /dev/null +++ b/avm/res/sql/managed-instance/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep @@ -0,0 +1,20 @@ +@description('Required. Storage account name.') +param storageAccountName string + +@description('Required. Managed Identity Principal ID.') +param managedInstanceIdentityPrincipalId string + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = { + name: storageAccountName +} + +// Assign Storage Blob Data Contributor RBAC role +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${storageAccount.id}-${managedInstanceIdentityPrincipalId}-Storage-Blob-Data-Contributor') + scope: storageAccount + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + principalId: managedInstanceIdentityPrincipalId + principalType: 'ServicePrincipal' + } +} From 950223f8de83d690fc272ed5cbac812b17e5ad43 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Fri, 19 Apr 2024 22:05:40 +0200 Subject: [PATCH 25/32] fix: Recovered missing role assignment workflow (#1719) ## Description Recovered missing role assignment workflow --- .../avm.ptn.authorization.role-assignment.yml | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/avm.ptn.authorization.role-assignment.yml diff --git a/.github/workflows/avm.ptn.authorization.role-assignment.yml b/.github/workflows/avm.ptn.authorization.role-assignment.yml new file mode 100644 index 0000000000..c9ad828152 --- /dev/null +++ b/.github/workflows/avm.ptn.authorization.role-assignment.yml @@ -0,0 +1,86 @@ +name: "avm.ptn.authorization.role-assignment" + +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.ptn.authorization.role-assignment.yml" + - "avm/ptn/authorization/role-assignment/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/authorization/role-assignment" + workflowPath: ".github/workflows/avm.ptn.authorization.role-assignment.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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 From 7c25f74a8288ef15f8c1b158e48312e54e979af0 Mon Sep 17 00:00:00 2001 From: Richard Hooper Date: Fri, 19 Apr 2024 22:32:15 +0100 Subject: [PATCH 26/32] feat: added option to set backendpooltype. - `avm/res/container-service/managed-cluster` (#1709) ## Description This adds the option to change the backendpooltype. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.container-service.managed-cluster](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=feat-rh-backendpooltype)](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [X] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Signed-off-by: PixelRobots <22979170+PixelRobots@users.noreply.github.com> --- .../managed-cluster/README.md | 16 ++++++++++++ .../managed-cluster/main.bicep | 20 ++++++++++----- .../managed-cluster/main.json | 25 +++++++++++++++---- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/avm/res/container-service/managed-cluster/README.md b/avm/res/container-service/managed-cluster/README.md index 43e75d0fb7..f1f8091e4e 100644 --- a/avm/res/container-service/managed-cluster/README.md +++ b/avm/res/container-service/managed-cluster/README.md @@ -1501,6 +1501,7 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster: Date: Fri, 19 Apr 2024 23:48:50 +0100 Subject: [PATCH 27/32] docs: fixing issue #1064 - `avm/res/container-service/managed-cluster` (#1715) ## Description Closes #1064 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.container-service.managed-cluster](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=rh-container-service-docs-update)](https://github.com/PixelRobots/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings Signed-off-by: PixelRobots <22979170+PixelRobots@users.noreply.github.com> --- .../managed-cluster/README.md | 72 +++++++------------ .../managed-cluster/main.json | 2 +- .../tests/e2e/azure/main.test.bicep | 9 +-- .../tests/e2e/kubenet/main.test.bicep | 9 +-- .../tests/e2e/priv/main.test.bicep | 9 +-- .../tests/e2e/waf-aligned/main.test.bicep | 9 +-- 6 files changed, 37 insertions(+), 73 deletions(-) diff --git a/avm/res/container-service/managed-cluster/README.md b/avm/res/container-service/managed-cluster/README.md index f1f8091e4e..18b51cc75b 100644 --- a/avm/res/container-service/managed-cluster/README.md +++ b/avm/res/container-service/managed-cluster/README.md @@ -64,6 +64,9 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster:' @@ -112,9 +112,6 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster:", @@ -339,9 +336,6 @@ module managedCluster 'br/public:avm/res/container-service/managed-cluster: Date: Sun, 21 Apr 2024 22:15:19 +0200 Subject: [PATCH 28/32] feat: Added custom Azure storage replication rule and updated ps-rule (#1725) --- .../psrule/.ps-rule/custom-rules.Rule.yaml | 14 ++++++++++++++ .../pipelines/staticValidation/psrule/ps-rule.yaml | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml diff --git a/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml b/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml new file mode 100644 index 0000000000..c3bb28aae8 --- /dev/null +++ b/avm/utilities/pipelines/staticValidation/psrule/.ps-rule/custom-rules.Rule.yaml @@ -0,0 +1,14 @@ +# Synopsis: Use a zone or geo redundant storage account +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: 'Custom.Azure.Storage.UseReplication' +spec: + type: + - Microsoft.Storage/storageAccounts + condition: + anyOf: + - field: 'Sku.name' + contains: 'ZRS' + - field: 'Sku.name' + contains: 'GRS' diff --git a/avm/utilities/pipelines/staticValidation/psrule/ps-rule.yaml b/avm/utilities/pipelines/staticValidation/psrule/ps-rule.yaml index f5bc660b72..b241ac0b1a 100644 --- a/avm/utilities/pipelines/staticValidation/psrule/ps-rule.yaml +++ b/avm/utilities/pipelines/staticValidation/psrule/ps-rule.yaml @@ -74,8 +74,9 @@ configuration: rule: # Enable custom rules that don't exist in the baseline - includeLocal: false + includeLocal: true exclude: # Ignore the following rules for all resources - Azure.KeyVault.PurgeProtect - Azure.VM.UseHybridUseBenefit + - Azure.Storage.UseReplication From 557203abd1fe4993c82b0d4355aee1dbe09911c6 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 22 Apr 2024 23:47:05 +0200 Subject: [PATCH 29/32] fix: Update instance size options for API Management service - `avm/res/api-management/service` (#1629) Added '0' as an allowed value for instance size selection. Fixes #1628 ## Description ## Pipeline Reference | Pipeline | | -------- | | | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [ ] Azure Verified Module updates: - [X] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/api-management/service/README.md | 1 + avm/res/api-management/service/main.bicep | 1 + avm/res/api-management/service/main.json | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/avm/res/api-management/service/README.md b/avm/res/api-management/service/README.md index 4b29ffb6c3..b8ca4b6ec8 100644 --- a/avm/res/api-management/service/README.md +++ b/avm/res/api-management/service/README.md @@ -1464,6 +1464,7 @@ The instance size of this API Management service. - Allowed: ```Bicep [ + 0 1 2 ] diff --git a/avm/res/api-management/service/main.bicep b/avm/res/api-management/service/main.bicep index 6224b65b85..429f44edce 100644 --- a/avm/res/api-management/service/main.bicep +++ b/avm/res/api-management/service/main.bicep @@ -66,6 +66,7 @@ param sku string = 'Developer' @description('Optional. The instance size of this API Management service.') @allowed([ + 0 1 2 ]) diff --git a/avm/res/api-management/service/main.json b/avm/res/api-management/service/main.json index 9f44ef9ee9..3a2f0a0a9c 100644 --- a/avm/res/api-management/service/main.json +++ b/avm/res/api-management/service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "4652975451954294914" + "templateHash": "7048259745072727177" }, "name": "API Management Services", "description": "This module deploys an API Management Service.", @@ -381,6 +381,7 @@ "type": "int", "defaultValue": 1, "allowedValues": [ + 0, 1, 2 ], From 1dd8628c121e4bb2ba8fa6372bb3a202f2c6ad46 Mon Sep 17 00:00:00 2001 From: Buddy <38195643+tsc-buddy@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:20:19 +1200 Subject: [PATCH 30/32] fix: AppServicePlan - SKU selection improvements (#1736) ## Description Provides improvements to simplify the selection of SKUs for App Service Plans by relying on the RP to provide the values for 'tier', 'family' and 'size' properties based on the 'name' provided. Logic adjusted on ZR param to accommodate the change also. Closes #1506 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.web.serverfarm](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=fix%2F1506-sku-updates)](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/web/serverfarm/README.md | 84 ++++++++----------- avm/res/web/serverfarm/main.bicep | 25 +++--- avm/res/web/serverfarm/main.json | 23 +++-- .../tests/e2e/defaults/main.test.bicep | 9 +- .../serverfarm/tests/e2e/max/main.test.bicep | 9 +- .../tests/e2e/waf-aligned/main.test.bicep | 9 +- avm/res/web/serverfarm/version.json | 2 +- 7 files changed, 71 insertions(+), 90 deletions(-) diff --git a/avm/res/web/serverfarm/README.md b/avm/res/web/serverfarm/README.md index 1a3e585904..74b625e14a 100644 --- a/avm/res/web/serverfarm/README.md +++ b/avm/res/web/serverfarm/README.md @@ -47,13 +47,8 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfmin001' - sku: { - capacity: 3 - family: 'P' - name: 'P1v3' - size: 'P1v3' - tier: 'Premium' - } + skuCapacity: 2 + skuName: 'S1' // Non-required parameters location: '' } @@ -76,14 +71,11 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfmin001" }, - "sku": { - "value": { - "capacity": 3, - "family": "P", - "name": "P1v3", - "size": "P1v3", - "tier": "Premium" - } + "skuCapacity": { + "value": 2 + }, + "skuName": { + "value": "S1" }, // Non-required parameters "location": { @@ -111,13 +103,8 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfmax001' - sku: { - capacity: 1 - family: 'S' - name: 'S1' - size: 'S1' - tier: 'Standard' - } + skuCapacity: 1 + skuName: 'S1' // Non-required parameters diagnosticSettings: [ { @@ -183,14 +170,11 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfmax001" }, - "sku": { - "value": { - "capacity": 1, - "family": "S", - "name": "S1", - "size": "S1", - "tier": "Standard" - } + "skuCapacity": { + "value": 1 + }, + "skuName": { + "value": "S1" }, // Non-required parameters "diagnosticSettings": { @@ -275,13 +259,8 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfwaf001' - sku: { - capacity: 3 - family: 'P' - name: 'P1v3' - size: 'P1v3' - tier: 'Premium' - } + skuCapacity: 3 + skuName: 'P1v3' // Non-required parameters diagnosticSettings: [ { @@ -329,14 +308,11 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfwaf001" }, - "sku": { - "value": { - "capacity": 3, - "family": "P", - "name": "P1v3", - "size": "P1v3", - "tier": "Premium" - } + "skuCapacity": { + "value": 3 + }, + "skuName": { + "value": "P1v3" }, // Non-required parameters "diagnosticSettings": { @@ -392,7 +368,8 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { | Parameter | Type | Description | | :-- | :-- | :-- | | [`name`](#parameter-name) | string | Name of the app service plan. | -| [`sku`](#parameter-sku) | object | Defines the name, tier, size, family and capacity of the App Service Plan. | +| [`skuCapacity`](#parameter-skucapacity) | int | Number of workers associated with the App Service Plan. | +| [`skuName`](#parameter-skuname) | string | The name of the SKU will Determine the tier, size, family of the App Service Plan. | **Conditional parameters** @@ -426,12 +403,19 @@ Name of the app service plan. - Required: Yes - Type: string -### Parameter: `sku` +### Parameter: `skuCapacity` -Defines the name, tier, size, family and capacity of the App Service Plan. +Number of workers associated with the App Service Plan. - Required: Yes -- Type: object +- Type: int + +### Parameter: `skuName` + +The name of the SKU will Determine the tier, size, family of the App Service Plan. + +- Required: Yes +- Type: string ### Parameter: `reserved` @@ -778,7 +762,7 @@ Zone Redundancy can only be used on Premium or ElasticPremium SKU Tiers within Z - Required: No - Type: bool -- Default: `[if(or(equals(parameters('sku').tier, 'Premium'), equals(parameters('sku').tier, 'ElasticPremium')), true(), false())]` +- Default: `[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]` ## Outputs diff --git a/avm/res/web/serverfarm/main.bicep b/avm/res/web/serverfarm/main.bicep index 5deb3fe099..5eb7e567b6 100644 --- a/avm/res/web/serverfarm/main.bicep +++ b/avm/res/web/serverfarm/main.bicep @@ -7,19 +7,19 @@ metadata owner = 'Azure/module-maintainers' @maxLength(60) param name string -@description('Required. Defines the name, tier, size, family and capacity of the App Service Plan.') +@description('Required. The name of the SKU will Determine the tier, size, family of the App Service Plan.') @metadata({ example: ''' - { - name: 'P1v3' - tier: 'Premium' - size: 'P1v3' - family: 'P' - capacity: 3 - } + 'F1' + 'B1' + 'P1v3' + 'I1v2' ''' }) -param sku object +param skuName string + +@description('Required. Number of workers associated with the App Service Plan.') +param skuCapacity int @description('Optional. Location for all resources.') param location string = resourceGroup().location @@ -61,7 +61,7 @@ param targetWorkerCount int = 0 param targetWorkerSize int = 0 @description('Optional. Zone Redundancy can only be used on Premium or ElasticPremium SKU Tiers within ZRS Supported regions (https://learn.microsoft.com/en-us/azure/storage/common/redundancy-regions-zrs).') -param zoneRedundant bool = (sku.tier == 'Premium' || sku.tier == 'ElasticPremium') ? true : false +param zoneRedundant bool = startsWith(skuName, 'P') || startsWith(skuName, 'EP') ? true : false @description('Optional. The lock settings of the service.') param lock lockType @@ -124,7 +124,10 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = { kind: kind location: location tags: tags - sku: sku + sku: { + name: skuName + capacity: skuCapacity + } properties: { workerTierName: workerTierName hostingEnvironmentProfile: !empty(appServiceEnvironmentId) diff --git a/avm/res/web/serverfarm/main.json b/avm/res/web/serverfarm/main.json index 68999ed0f2..1e9678c755 100644 --- a/avm/res/web/serverfarm/main.json +++ b/avm/res/web/serverfarm/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.26.170.59819", - "templateHash": "16669238654401736455" + "templateHash": "5846778493081554072" }, "name": "App Service Plan", "description": "This module deploys an App Service Plan.", @@ -201,11 +201,17 @@ "description": "Required. Name of the app service plan." } }, - "sku": { - "type": "object", + "skuName": { + "type": "string", "metadata": { - "example": " {\n name: 'P1v3'\n tier: 'Premium'\n size: 'P1v3'\n family: 'P'\n capacity: 3\n }\n ", - "description": "Required. Defines the name, tier, size, family and capacity of the App Service Plan." + "example": " 'F1'\n 'B1'\n 'P1v3'\n 'I1v2'\n ", + "description": "Required. The name of the SKU will Determine the tier, size, family of the App Service Plan." + } + }, + "skuCapacity": { + "type": "int", + "metadata": { + "description": "Required. Number of workers associated with the App Service Plan." } }, "location": { @@ -285,7 +291,7 @@ }, "zoneRedundant": { "type": "bool", - "defaultValue": "[if(or(equals(parameters('sku').tier, 'Premium'), equals(parameters('sku').tier, 'ElasticPremium')), true(), false())]", + "defaultValue": "[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]", "metadata": { "description": "Optional. Zone Redundancy can only be used on Premium or ElasticPremium SKU Tiers within ZRS Supported regions (https://learn.microsoft.com/en-us/azure/storage/common/redundancy-regions-zrs)." } @@ -362,7 +368,10 @@ "kind": "[parameters('kind')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", - "sku": "[parameters('sku')]", + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]" + }, "properties": { "workerTierName": "[parameters('workerTierName')]", "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentId'))), createObject('id', parameters('appServiceEnvironmentId')), null())]", diff --git a/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep index 9f2ffe8e94..0183742502 100644 --- a/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep @@ -46,13 +46,8 @@ module testDeployment '../../../main.bicep' = [ params: { name: '${namePrefix}${serviceShort}001' location: tempLocation - sku: { - name: 'P1v3' - tier: 'Premium' - size: 'P1v3' - family: 'P' - capacity: 3 - } + skuName: 'S1' + skuCapacity: 2 } } ] diff --git a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep index f7e0fb3a88..b987bfb1ae 100644 --- a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep @@ -67,13 +67,8 @@ module testDeployment '../../../main.bicep' = [ params: { name: '${namePrefix}${serviceShort}001' location: tempLocation - sku: { - name: 'S1' - tier: 'Standard' - size: 'S1' - family: 'S' - capacity: 1 - } + skuName: 'S1' + skuCapacity: 1 perSiteScaling: true zoneRedundant: false kind: 'App' diff --git a/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep index 5cd23e57c9..45080f9d3b 100644 --- a/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep @@ -58,13 +58,8 @@ module testDeployment '../../../main.bicep' = [ params: { name: '${namePrefix}${serviceShort}001' location: tempLocation - sku: { - name: 'P1v3' - tier: 'Premium' - size: 'P1v3' - family: 'P' - capacity: 3 - } + skuName: 'P1v3' + skuCapacity: 3 zoneRedundant: true kind: 'App' lock: { diff --git a/avm/res/web/serverfarm/version.json b/avm/res/web/serverfarm/version.json index 8def869ede..daf1a794d9 100644 --- a/avm/res/web/serverfarm/version.json +++ b/avm/res/web/serverfarm/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.1", + "version": "0.2", "pathFilters": [ "./main.json" ] From 9fc50746ca6981becc7d2e6df9a611c835da685f Mon Sep 17 00:00:00 2001 From: Yas Date: Tue, 23 Apr 2024 12:28:57 +1000 Subject: [PATCH 31/32] feat: `avm/res/sql/instance-pool` (#1714) ## Description This PR adds the new Azure SQL Instance pool and its tests Closes [Module Proposal : avm/res/sql/instance-pool](#752) ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.sql.instance-pool](https://github.com/yashints/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml/badge.svg)](https://github.com/yashints/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml) | ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../workflows/avm.res.sql.instance-pool.yml | 86 ++++++ avm/res/sql/instance-pool/README.md | 286 ++++++++++++++++++ avm/res/sql/instance-pool/main.bicep | 102 +++++++ avm/res/sql/instance-pool/main.json | 176 +++++++++++ .../tests/e2e/defaults/dependencies.bicep | 286 ++++++++++++++++++ .../tests/e2e/defaults/main.test.bicep | 61 ++++ .../tests/e2e/waf-aligned/dependencies.bicep | 286 ++++++++++++++++++ .../tests/e2e/waf-aligned/main.test.bicep | 62 ++++ avm/res/sql/instance-pool/version.json | 7 + 11 files changed, 1354 insertions(+) create mode 100644 .github/workflows/avm.res.sql.instance-pool.yml create mode 100644 avm/res/sql/instance-pool/README.md create mode 100644 avm/res/sql/instance-pool/main.bicep create mode 100644 avm/res/sql/instance-pool/main.json create mode 100644 avm/res/sql/instance-pool/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/sql/instance-pool/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/sql/instance-pool/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/sql/instance-pool/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/sql/instance-pool/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0734b0fb91..68a24d86e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -131,6 +131,7 @@ /avm/res/service-fabric/cluster/ @Azure/avm-res-servicefabric-cluster-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/signal-r-service/signal-r/ @Azure/avm-res-signalrservice-signalr-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/signal-r-service/web-pub-sub/ @Azure/avm-res-signalrservice-webpubsub-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/sql/instance-pool/ @Azure/avm-res-sql-instancepool-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/sql/managed-instance/ @Azure/avm-res-sql-managedinstance-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/sql/server/ @Azure/avm-res-sql-server-module-owners-bicep @Azure/avm-core-team-technical-bicep /avm/res/storage/storage-account/ @Azure/avm-res-storage-storageaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 8ad0c07acd..9a7da432aa 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -164,6 +164,7 @@ body: - "avm/res/service-fabric/cluster" - "avm/res/signal-r-service/signal-r" - "avm/res/signal-r-service/web-pub-sub" + - "avm/res/sql/instance-pool" - "avm/res/sql/managed-instance" - "avm/res/sql/server" - "avm/res/storage/storage-account" diff --git a/.github/workflows/avm.res.sql.instance-pool.yml b/.github/workflows/avm.res.sql.instance-pool.yml new file mode 100644 index 0000000000..75f6103864 --- /dev/null +++ b/.github/workflows/avm.res.sql.instance-pool.yml @@ -0,0 +1,86 @@ +name: "avm.res.sql.instance-pool" + +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: false + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.sql.instance-pool.yml" + - "avm/res/sql/instance-pool/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/sql/instance-pool" + workflowPath: ".github/workflows/avm.res.sql.instance-pool.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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/sql/instance-pool/README.md b/avm/res/sql/instance-pool/README.md new file mode 100644 index 0000000000..68e73a8f95 --- /dev/null +++ b/avm/res/sql/instance-pool/README.md @@ -0,0 +1,286 @@ +# SQL Server Instance Pool `[Microsoft.Sql/instancePools]` + +This module deploys an Azure SQL Server Instance Pool. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/instancePools` | [2023-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2023-05-01-preview/instancePools) | + +## 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/sql/instance-pool:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [WAF-aligned](#example-2-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module instancePool 'br/public:avm/res/sql/instance-pool:' = { + name: 'instancePoolDeployment' + params: { + // Required parameters + name: '' + subnetResourceId: '' + // 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": "" + }, + "subnetResourceId": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module instancePool 'br/public:avm/res/sql/instance-pool:' = { + name: 'instancePoolDeployment' + params: { + // Required parameters + name: '' + subnetResourceId: '' + // Non-required parameters + location: '' + skuName: 'GP_Gen8IM' + } +} +``` + +
+

+ +

+ +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": "" + }, + "subnetResourceId": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + }, + "skuName": { + "value": "GP_Gen8IM" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the instance pool. | +| [`subnetResourceId`](#parameter-subnetresourceid) | string | The subnet resource ID for the instance pool. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`licenseType`](#parameter-licensetype) | string | The license type to apply for this database. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`skuFamily`](#parameter-skufamily) | string | If the service has different generations of hardware, for the same SKU, then that can be captured here. | +| [`skuName`](#parameter-skuname) | string | The SKU name for the instance pool. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`tier`](#parameter-tier) | string | The vCore service tier for the instance pool. | +| [`vCores`](#parameter-vcores) | int | The number of vCores for the instance pool. | + +### Parameter: `name` + +The name of the instance pool. + +- Required: Yes +- Type: string + +### Parameter: `subnetResourceId` + +The subnet resource ID for the instance pool. + +- Required: Yes +- Type: string + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `licenseType` + +The license type to apply for this database. + +- Required: No +- Type: string +- Default: `'BasePrice'` +- Allowed: + ```Bicep + [ + 'BasePrice' + 'LicenseIncluded' + ] + ``` + +### Parameter: `location` + +Location for all resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `skuFamily` + +If the service has different generations of hardware, for the same SKU, then that can be captured here. + +- Required: No +- Type: string +- Default: `'Gen5'` + +### Parameter: `skuName` + +The SKU name for the instance pool. + +- Required: No +- Type: string +- Default: `'GP_Gen5'` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + +### Parameter: `tier` + +The vCore service tier for the instance pool. + +- Required: No +- Type: string +- Default: `'GeneralPurpose'` +- Allowed: + ```Bicep + [ + 'GeneralPurpose' + ] + ``` + +### Parameter: `vCores` + +The number of vCores for the instance pool. + +- Required: No +- Type: int +- Default: `8` +- Allowed: + ```Bicep + [ + 8 + 16 + 24 + 32 + 40 + 64 + 80 + 96 + 128 + 160 + 192 + 224 + 256 + ] + ``` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `instancePoolLocation` | string | The location of the SQL instance pool. | +| `name` | string | The name of the SQL instance pool. | +| `resourceGroupName` | string | The resource group name of the SQL instance pool. | +| `resourceId` | string | The ID of the SQL instance pool. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/sql/instance-pool/main.bicep b/avm/res/sql/instance-pool/main.bicep new file mode 100644 index 0000000000..088cd87ada --- /dev/null +++ b/avm/res/sql/instance-pool/main.bicep @@ -0,0 +1,102 @@ +metadata name = 'SQL Server Instance Pool' +metadata description = 'This module deploys an Azure SQL Server Instance Pool.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the instance pool.') +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Required. The subnet resource ID for the instance pool.') +param subnetResourceId string + +@description('Optional. The license type to apply for this database.') +@allowed([ + 'BasePrice' + 'LicenseIncluded' +]) +param licenseType string = 'BasePrice' + +@description('Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here.') +param skuFamily string = 'Gen5' + +@description('Optional. The number of vCores for the instance pool.') +@allowed([ + 8 + 16 + 24 + 32 + 40 + 64 + 80 + 96 + 128 + 160 + 192 + 224 + 256 +]) +param vCores int = 8 + +@description('Optional. The vCore service tier for the instance pool.') +@allowed([ + 'GeneralPurpose' +]) +param tier string = 'GeneralPurpose' + +@description('Optional. The SKU name for the instance pool.') +param skuName string = 'GP_Gen5' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +resource instancePool 'Microsoft.Sql/instancePools@2023-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + family: skuFamily + name: skuName + tier: tier + } + properties: { + licenseType: licenseType + subnetId: subnetResourceId + vCores: vCores + } +} + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = + if (enableTelemetry) { + name: '46d3xbcp.res.sql-instancepool.${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' + } + } + } + } + } + +@description('The ID of the SQL instance pool.') +output resourceId string = instancePool.id + +@description('The name of the SQL instance pool.') +output name string = instancePool.name + +@description('The location of the SQL instance pool.') +output instancePoolLocation string = instancePool.location + +@description('The resource group name of the SQL instance pool.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/instance-pool/main.json b/avm/res/sql/instance-pool/main.json new file mode 100644 index 0000000000..684f086ed4 --- /dev/null +++ b/avm/res/sql/instance-pool/main.json @@ -0,0 +1,176 @@ +{ + "$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.26.170.59819", + "templateHash": "1210707271653756300" + }, + "name": "SQL Server Instance Pool", + "description": "This module deploys an Azure SQL Server Instance Pool.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the instance pool." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The subnet resource ID for the instance pool." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "BasePrice", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "metadata": { + "description": "Optional. The license type to apply for this database." + } + }, + "skuFamily": { + "type": "string", + "defaultValue": "Gen5", + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "vCores": { + "type": "int", + "defaultValue": 8, + "allowedValues": [ + 8, + 16, + 24, + 32, + 40, + 64, + 80, + 96, + 128, + 160, + 192, + 224, + 256 + ], + "metadata": { + "description": "Optional. The number of vCores for the instance pool." + } + }, + "tier": { + "type": "string", + "defaultValue": "GeneralPurpose", + "allowedValues": [ + "GeneralPurpose" + ], + "metadata": { + "description": "Optional. The vCore service tier for the instance pool." + } + }, + "skuName": { + "type": "string", + "defaultValue": "GP_Gen5", + "metadata": { + "description": "Optional. The SKU name for the instance pool." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "instancePool": { + "type": "Microsoft.Sql/instancePools", + "apiVersion": "2023-05-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "family": "[parameters('skuFamily')]", + "name": "[parameters('skuName')]", + "tier": "[parameters('tier')]" + }, + "properties": { + "licenseType": "[parameters('licenseType')]", + "subnetId": "[parameters('subnetResourceId')]", + "vCores": "[parameters('vCores')]" + } + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.sql-instancepool.{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" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The ID of the SQL instance pool." + }, + "value": "[resourceId('Microsoft.Sql/instancePools', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL instance pool." + }, + "value": "[parameters('name')]" + }, + "instancePoolLocation": { + "type": "string", + "metadata": { + "description": "The location of the SQL instance pool." + }, + "value": "[reference('instancePool', '2023-05-01-preview', 'full').location]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group name of the SQL instance pool." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/instance-pool/tests/e2e/defaults/dependencies.bicep b/avm/res/sql/instance-pool/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..772bd070fc --- /dev/null +++ b/avm/res/sql/instance-pool/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,286 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Deployment Script to create to get the paired region name.') +param pairedRegionScriptName string + +@description('Required. The name of the SQL Instance Pool.') +param sqlInstancePoolName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Optional. The name of the Subnet to create.') +param subnetName string = 'sql-instancepool-subnet' + +@description('Required. The name of the NSG to create.') +param nsgName string + +@description('Required. The name of the route table to create.') +param routeTableName string + +var addressPrefix = '10.0.0.0/16' +var subnetAddressPrefix = cidrSubnet(addressPrefix, 24, 0) + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: managedIdentityName + location: location +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${location}-${managedIdentity.id}-Reader-RoleAssignment') + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) // Reader + principalType: 'ServicePrincipal' + } +} + +resource getPairedRegionScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: pairedRegionScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-Location \\"${location}\\"' + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1') + } + dependsOn: [ + roleAssignment + ] +} + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: nsgName + location: location + properties: { + securityRules: [ + { + name: 'sqlmi-healthprobe-in' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'sqlmi-internal-in' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + } + } + { + name: 'sqlmi-aad-out' + properties: { + description: 'Allow communication with Azure Active Directory over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'AzureActiveDirectory' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'sqlmi-onedsc-out' + properties: { + description: 'Allow communication with the One DS Collector over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'OneDsCollector' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'sqlmi-internal-out' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'sqlmi-strg-p-out' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'Storage.${location}' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + { + name: 'sqlmi-strg-s-out' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'Storage.${getPairedRegionScript.properties.outputs.pairedRegionName}' + access: 'Allow' + priority: 105 + direction: 'Outbound' + } + } + { + name: 'sqlmi-optional-azure-out' + properties: { + description: 'Allow AzureCloud outbound https traffic' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2020-06-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'sqlmi_subnet-to-vnetlocal' + properties: { + addressPrefix: subnetAddressPrefix + nextHopType: 'VnetLocal' + } + } + { + name: 'sqlmi-aad' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-odscollector' + properties: { + addressPrefix: 'OneDsCollector' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-stg-p' + properties: { + addressPrefix: 'Storage.${location}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-stg-s' + properties: { + addressPrefix: 'Storage.${getPairedRegionScript.properties.outputs.pairedRegionName}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-optional-azure-p' + properties: { + addressPrefix: 'AzureCloud.${location}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-optional-azurecloud-s' + properties: { + addressPrefix: 'AzureCloud.${getPairedRegionScript.properties.outputs.pairedRegionName}' + nextHopType: 'Internet' + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + networkSecurityGroup: { + id: nsg.id + } + routeTable: { + id: routeTable.id + } + delegations: [ + { + name: 'ip-delegations-${sqlInstancePoolName}' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + } + ] + } +} + +@description('The subnetId required for the instance pool creation.') +output subnetId string = virtualNetwork.properties.subnets[0].id + +@description('The name of the sql instnce pool to be created.') +output sqlInstancePoolName string = sqlInstancePoolName diff --git a/avm/res/sql/instance-pool/tests/e2e/defaults/main.test.bicep b/avm/res/sql/instance-pool/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..677a80dbe1 --- /dev/null +++ b/avm/res/sql/instance-pool/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,61 @@ +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}-sql.instancepool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'sipmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + pairedRegionScriptName: 'dep-${namePrefix}-ds-${serviceShort}' + nsgName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + sqlInstancePoolName: '${namePrefix}${serviceShort}001' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: nestedDependencies.outputs.sqlInstancePoolName + location: resourceLocation + subnetResourceId: nestedDependencies.outputs.subnetId + } +} diff --git a/avm/res/sql/instance-pool/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/sql/instance-pool/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..772bd070fc --- /dev/null +++ b/avm/res/sql/instance-pool/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,286 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Deployment Script to create to get the paired region name.') +param pairedRegionScriptName string + +@description('Required. The name of the SQL Instance Pool.') +param sqlInstancePoolName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Optional. The name of the Subnet to create.') +param subnetName string = 'sql-instancepool-subnet' + +@description('Required. The name of the NSG to create.') +param nsgName string + +@description('Required. The name of the route table to create.') +param routeTableName string + +var addressPrefix = '10.0.0.0/16' +var subnetAddressPrefix = cidrSubnet(addressPrefix, 24, 0) + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: managedIdentityName + location: location +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${location}-${managedIdentity.id}-Reader-RoleAssignment') + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) // Reader + principalType: 'ServicePrincipal' + } +} + +resource getPairedRegionScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: pairedRegionScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-Location \\"${location}\\"' + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1') + } + dependsOn: [ + roleAssignment + ] +} + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: nsgName + location: location + properties: { + securityRules: [ + { + name: 'sqlmi-healthprobe-in' + properties: { + description: 'Allow Azure Load Balancer inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'sqlmi-internal-in' + properties: { + description: 'Allow MI internal inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 101 + direction: 'Inbound' + } + } + { + name: 'sqlmi-aad-out' + properties: { + description: 'Allow communication with Azure Active Directory over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'AzureActiveDirectory' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'sqlmi-onedsc-out' + properties: { + description: 'Allow communication with the One DS Collector over https' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'OneDsCollector' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'sqlmi-internal-out' + properties: { + description: 'Allow MI internal outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: subnetAddressPrefix + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'sqlmi-strg-p-out' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'Storage.${location}' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + { + name: 'sqlmi-strg-s-out' + properties: { + description: 'Allow outbound communication with storage over HTTPS' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'Storage.${getPairedRegionScript.properties.outputs.pairedRegionName}' + access: 'Allow' + priority: 105 + direction: 'Outbound' + } + } + { + name: 'sqlmi-optional-azure-out' + properties: { + description: 'Allow AzureCloud outbound https traffic' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: subnetAddressPrefix + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + ] + } +} + +resource routeTable 'Microsoft.Network/routeTables@2020-06-01' = { + name: routeTableName + location: location + properties: { + disableBgpRoutePropagation: false + routes: [ + { + name: 'sqlmi_subnet-to-vnetlocal' + properties: { + addressPrefix: subnetAddressPrefix + nextHopType: 'VnetLocal' + } + } + { + name: 'sqlmi-aad' + properties: { + addressPrefix: 'AzureActiveDirectory' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-odscollector' + properties: { + addressPrefix: 'OneDsCollector' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-stg-p' + properties: { + addressPrefix: 'Storage.${location}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-stg-s' + properties: { + addressPrefix: 'Storage.${getPairedRegionScript.properties.outputs.pairedRegionName}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-optional-azure-p' + properties: { + addressPrefix: 'AzureCloud.${location}' + nextHopType: 'Internet' + } + } + { + name: 'sqlmi-optional-azurecloud-s' + properties: { + addressPrefix: 'AzureCloud.${getPairedRegionScript.properties.outputs.pairedRegionName}' + nextHopType: 'Internet' + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + networkSecurityGroup: { + id: nsg.id + } + routeTable: { + id: routeTable.id + } + delegations: [ + { + name: 'ip-delegations-${sqlInstancePoolName}' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + } + ] + } +} + +@description('The subnetId required for the instance pool creation.') +output subnetId string = virtualNetwork.properties.subnets[0].id + +@description('The name of the sql instnce pool to be created.') +output sqlInstancePoolName string = sqlInstancePoolName diff --git a/avm/res/sql/instance-pool/tests/e2e/waf-aligned/main.test.bicep b/avm/res/sql/instance-pool/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..ffaa207271 --- /dev/null +++ b/avm/res/sql/instance-pool/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,62 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-sql.instancepool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'sipwaf' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + pairedRegionScriptName: 'dep-${namePrefix}-ds-${serviceShort}' + nsgName: 'dep-${namePrefix}-nsg-${serviceShort}' + routeTableName: 'dep-${namePrefix}-rt-${serviceShort}' + sqlInstancePoolName: '${namePrefix}${serviceShort}001' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + name: nestedDependencies.outputs.sqlInstancePoolName + location: resourceLocation + skuName: 'GP_Gen8IM' + subnetResourceId: nestedDependencies.outputs.subnetId + } +} diff --git a/avm/res/sql/instance-pool/version.json b/avm/res/sql/instance-pool/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/res/sql/instance-pool/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 31898eddd28967a6560f1d98d97f5c144935b652 Mon Sep 17 00:00:00 2001 From: Christoph Vollmann Date: Tue, 23 Apr 2024 04:58:27 +0200 Subject: [PATCH 32/32] fix: 1704 Web Site without Managed Identity (#1738) ## Description This PR changes the default value for Managed Identity Type from `null` to `'None'` to allow creation of sites without Managed Identity as per documentation: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites?pivots=deployment-language-bicep#managedserviceidentity. The pipeline run shows Bicep Linter warnings for the `identity` property in the pester tests: https://github.com/cloudchristoph/bicep-registry-modules/actions/runs/8783746073/job/24100560440#step:4:234 ``` /home/runner/work/bicep-registry-modules/bicep-registry-modules/avm/res/web/site/main.bicep(227,13) : Warning BCP036: The property "type" expected a value of type "'None' | 'SystemAssigned' | 'SystemAssigned, UserAssigned' | 'UserAssigned' | null" but the provided value in source declaration "identity" is of type "'None' | 'SystemAssigned' | 'SystemAssigned,UserAssigned' | 'UserAssigned'". If this is an inaccuracy in the documentation, please report it to the Bicep Team. [https://aka.ms/bicep-type-issues] ``` The warning also has existed before, but for the missing `'None'` value, so I think it's not an issue. Regarding the backwards compatibility: This change doesn't affect the actual deployment. Managed Identities are disabled no matter which of the two values is given. Fixes #1704 Closes #1704 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.web.site](https://github.com/cloudchristoph/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=1704_site_without_managed_identity)](https://github.com/cloudchristoph/bicep-registry-modules/actions/workflows/avm.res.web.site.yml)| ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings (see above) --- avm/res/web/site/main.bicep | 2 +- avm/res/web/site/main.json | 42 ++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/avm/res/web/site/main.bicep b/avm/res/web/site/main.bicep index 19fa05ed2d..9c2aefa0e4 100644 --- a/avm/res/web/site/main.bicep +++ b/avm/res/web/site/main.bicep @@ -169,7 +169,7 @@ var identity = !empty(managedIdentities) ? { type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') - : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) + : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None') userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null } : null diff --git a/avm/res/web/site/main.json b/avm/res/web/site/main.json index 3b31a486aa..607d3adbb5 100644 --- a/avm/res/web/site/main.json +++ b/avm/res/web/site/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "14301920664802681394" + "version": "0.26.170.59819", + "templateHash": "5830272725104890600" }, "name": "Web/Function Apps", "description": "This module deploys a Web or Function App.", @@ -747,7 +747,7 @@ }, "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())]", + "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', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", "builtInRoleNames": { "App Compliance Automation Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f37683f-2463-46b6-9ce7-9b788b988ba2')]", "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", @@ -929,8 +929,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "18064037455551234601" + "version": "0.26.170.59819", + "templateHash": "12051629915105082529" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting.", @@ -1080,8 +1080,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "9660352953607316036" + "version": "0.26.170.59819", + "templateHash": "14303407385986258247" }, "name": "Site Auth Settings V2 Config", "description": "This module deploys a Site Auth Settings V2 Configuration.", @@ -1300,8 +1300,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "7436278786647572492" + "version": "0.26.170.59819", + "templateHash": "5665258089530156840" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -2208,8 +2208,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12764984503745572183" + "version": "0.26.170.59819", + "templateHash": "16550727222833899283" }, "name": "Site Slot App Settings", "description": "This module deploys a Site Slot App Setting.", @@ -2378,8 +2378,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1333776366661137381" + "version": "0.26.170.59819", + "templateHash": "512112730780239094" }, "name": "Site Slot Auth Settings V2 Config", "description": "This module deploys a Site Auth Settings V2 Configuration.", @@ -2494,8 +2494,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "2236066853450471067" + "version": "0.26.170.59819", + "templateHash": "4923254671011353973" }, "name": "Web Site Slot Basic Publishing Credentials Policies", "description": "This module deploys a Web Site Slot Basic Publishing Credentials Policy.", @@ -2620,8 +2620,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "1058820827217282473" + "version": "0.26.170.59819", + "templateHash": "15033433727480709023" }, "name": "Web/Function Apps Slot Hybrid Connection Relay", "description": "This module deploys a Site Slot Hybrid Connection Namespace Relay.", @@ -3440,8 +3440,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12379291046700283915" + "version": "0.26.170.59819", + "templateHash": "1590652329081458395" }, "name": "Web Site Basic Publishing Credentials Policies", "description": "This module deploys a Web Site Basic Publishing Credentials Policy.", @@ -3556,8 +3556,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.26.54.24096", - "templateHash": "12607693486765150465" + "version": "0.26.170.59819", + "templateHash": "15287918657229788223" }, "name": "Web/Function Apps Hybrid Connection Relay", "description": "This module deploys a Site Hybrid Connection Namespace Relay.",