From 2a51977062b3fb319d528113cdfdd428d1bdff00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Barab=C3=A1s?= Date: Tue, 21 Nov 2023 08:18:56 +0100 Subject: [PATCH 1/7] chore: Update CODEOWNERS file (#659) --- .github/CODEOWNERS | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 382da116d2..62b334c5a3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,4 @@ +* @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /.github/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /scripts/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /avm @Azure/avm-core-team-technical-bicep @@ -47,7 +48,7 @@ #/avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep #/avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep #/avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep -#/avm/res/event-grid/domain/ @Azure/avm-res-eventgrid-domain-module-owners-bicep +/avm/res/event-grid/domain/ @Azure/avm-res-eventgrid-domain-module-owners-bicep #/avm/res/event-grid/system-topic/ @Azure/avm-res-eventgrid-systemtopic-module-owners-bicep #/avm/res/event-grid/topic/ @Azure/avm-res-eventgrid-topic-module-owners-bicep #/avm/res/event-hub/namespace/ @Azure/avm-res-eventhub-namespace-module-owners-bicep @@ -55,7 +56,7 @@ #/avm/res/healthcare-apis/workspace/ @Azure/avm-res-healthcareapis-workspace-module-owners-bicep /avm/res/insights/action-group/ @Azure/avm-res-insights-actiongroup-module-owners-bicep #/avm/res/insights/activity-log-alert/ @Azure/avm-res-insights-activitylogalert-module-owners-bicep -#/avm/res/insights/component/ @Azure/avm-res-insights-component-module-owners-bicep +/avm/res/insights/component/ @Azure/avm-res-insights-component-module-owners-bicep #/avm/res/insights/data-collection-endpoint/ @Azure/avm-res-insights-datacollectionendpoint-module-owners-bicep #/avm/res/insights/data-collection-rule/ @Azure/avm-res-insights-datacollectionrule-module-owners-bicep /avm/res/insights/diagnostic-setting/ @Azure/avm-res-insights-diagnosticsetting-module-owners-bicep @@ -114,7 +115,7 @@ /avm/res/operational-insights/workspace/ @Azure/avm-res-operationalinsights-workspace-module-owners-bicep /avm/res/operations-management/solution/ @Azure/avm-res-operationsmanagement-solution-module-owners-bicep #/avm/res/policy-insights/remediation/ @Azure/avm-res-policyinsights-remediation-module-owners-bicep -#/avm/res/power-bi-dedicated/capacity/ @Azure/avm-res-powerbidedicated-capacity-module-owners-bicep +/avm/res/power-bi-dedicated/capacity/ @Azure/avm-res-powerbidedicated-capacity-module-owners-bicep #/avm/res/purview/account/ @Azure/avm-res-purview-account-module-owners-bicep #/avm/res/recovery-services/vault/ @Azure/avm-res-recoveryservices-vault-module-owners-bicep #/avm/res/relay/namespace/ @Azure/avm-res-relay-namespace-module-owners-bicep @@ -122,7 +123,7 @@ #/avm/res/resources/deployment-script/ @Azure/avm-res-resources-deploymentscript-module-owners-bicep #/avm/res/resources/resource-group/ @Azure/avm-res-resources-resourcegroup-module-owners-bicep #/avm/res/resources/tags/ @Azure/avm-res-resources-tags-module-owners-bicep -#/avm/res/search/search-service/ @Azure/avm-res-search-searchservice-module-owners-bicep +/avm/res/search/search-service/ @Azure/avm-res-search-searchservice-module-owners-bicep #/avm/res/service-bus/namespace/ @Azure/avm-res-servicebus-namespace-module-owners-bicep #/avm/res/service-fabric/cluster/ @Azure/avm-res-servicefabric-cluster-module-owners-bicep #/avm/res/signal-r-service/signal-r/ @Azure/avm-res-signalrservice-signalr-module-owners-bicep From 1eb37c844393f267796655caa5d4bfdb3ed2efe8 Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:32:23 +0200 Subject: [PATCH 2/7] feat: New Module avm-res-resource-graph.query (#657) ## Description New Module migrated from CARML [![avm.res.resource-graph.query](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml/badge.svg?branch=avm-resource-graph-query)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml) --------- Co-authored-by: Alexander Sehr --- .../avm.res.resource-graph.query.yml | 81 ++++ avm/res/resource-graph/query/ORPHANED.md | 4 + avm/res/resource-graph/query/README.md | 429 ++++++++++++++++++ avm/res/resource-graph/query/main.bicep | 149 ++++++ avm/res/resource-graph/query/main.json | 270 +++++++++++ .../query/tests/e2e/defaults/main.test.bicep | 46 ++ .../query/tests/e2e/max/dependencies.bicep | 16 + .../query/tests/e2e/max/main.test.bicep | 71 +++ .../query/tests/e2e/max/query.kql | 3 + .../tests/e2e/waf-aligned/main.test.bicep | 59 +++ avm/res/resource-graph/query/version.json | 7 + 11 files changed, 1135 insertions(+) create mode 100644 .github/workflows/avm.res.resource-graph.query.yml create mode 100644 avm/res/resource-graph/query/ORPHANED.md create mode 100644 avm/res/resource-graph/query/README.md create mode 100644 avm/res/resource-graph/query/main.bicep create mode 100644 avm/res/resource-graph/query/main.json create mode 100644 avm/res/resource-graph/query/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/resource-graph/query/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/resource-graph/query/tests/e2e/max/main.test.bicep create mode 100644 avm/res/resource-graph/query/tests/e2e/max/query.kql create mode 100644 avm/res/resource-graph/query/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/resource-graph/query/version.json diff --git a/.github/workflows/avm.res.resource-graph.query.yml b/.github/workflows/avm.res.resource-graph.query.yml new file mode 100644 index 0000000000..356e0e2769 --- /dev/null +++ b/.github/workflows/avm.res.resource-graph.query.yml @@ -0,0 +1,81 @@ +name: "avm.res.resource-graph.query" + +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.resource-graph.query.yml" + - "avm/res/resource-graph.query/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/resource-graph/query" + workflowPath: ".github/workflows/avm.res.resource-graph.query.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get parameter 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 }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Module" + 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 }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/resource-graph/query/ORPHANED.md b/avm/res/resource-graph/query/ORPHANED.md new file mode 100644 index 0000000000..d76e7e1e4e --- /dev/null +++ b/avm/res/resource-graph/query/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)! diff --git a/avm/res/resource-graph/query/README.md b/avm/res/resource-graph/query/README.md new file mode 100644 index 0000000000..7178730ac5 --- /dev/null +++ b/avm/res/resource-graph/query/README.md @@ -0,0 +1,429 @@ +# Resource Graph Queries `[Microsoft.ResourceGraph/queries]` + +> ⚠️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 Resource Graph Query. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.ResourceGraph/queries` | [2018-09-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ResourceGraph/2018-09-01-preview/queries) | + +## 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/resource-graph/query:`. + +- [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 query 'br/public:avm/res/resource-graph/query:' = { + name: '${uniqueString(deployment().name, location)}-test-rdsmin' + params: { + // Required parameters + name: 'rdsmin001' + query: 'Resources | limit 10' + // 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": "rdsmin001" + }, + "query": { + "value": "Resources | limit 10" + }, + // 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 query 'br/public:avm/res/resource-graph/query:' = { + name: '${uniqueString(deployment().name, location)}-test-rdsmax' + params: { + // Required parameters + name: 'rdsmax001' + query: '' + // Non-required parameters + location: '' + lock: { + kind: 'None' + } + queryDescription: 'An example query to list first 5 subscriptions.' + 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": "rdsmax001" + }, + "query": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "None" + } + }, + "queryDescription": { + "value": "An example query to list first 5 subscriptions." + }, + "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: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module query 'br/public:avm/res/resource-graph/query:' = { + name: '${uniqueString(deployment().name, location)}-test-rdswaf' + params: { + // Required parameters + name: 'rdswaf001' + query: 'resourcecontainers| where type == \'microsoft.resources/subscriptions\' | take 5' + // Non-required parameters + enableTelemetry: '' + location: '' + lock: { + kind: 'None' + } + queryDescription: 'An example query to list first 5 subscriptions.' + 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": "rdswaf001" + }, + "query": { + "value": "resourcecontainers| where type == \"microsoft.resources/subscriptions\" | take 5" + }, + // Non-required parameters + "enableTelemetry": { + "value": "" + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "None" + } + }, + "queryDescription": { + "value": "An example query to list first 5 subscriptions." + }, + "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 Resource Graph Query. | +| [`query`](#parameter-query) | string | KQL query that will be graph. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`queryDescription`](#parameter-querydescription) | string | The description of a graph query. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| [`tags`](#parameter-tags) | object | Resource tags. | + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. +- Required: No +- Type: bool +- Default: `True` + +### 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 + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`kind`](#parameter-lockkind) | No | string | Optional. Specify the type of lock. | +| [`name`](#parameter-lockname) | No | string | Optional. Specify the name of lock. | + +### Parameter: `lock.kind` + +Optional. Specify the type of lock. + +- Required: No +- Type: string +- Allowed: `[CanNotDelete, None, ReadOnly]` + +### Parameter: `lock.name` + +Optional. Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `name` + +Name of the Resource Graph Query. +- Required: Yes +- Type: string + +### Parameter: `query` + +KQL query that will be graph. +- Required: Yes +- Type: string + +### Parameter: `queryDescription` + +The description of a graph query. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `roleAssignments` + +Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`condition`](#parameter-roleassignmentscondition) | No | string | 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`](#parameter-roleassignmentsconditionversion) | No | string | Optional. Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | No | string | Optional. The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | No | string | Optional. The description of the role assignment. | +| [`principalId`](#parameter-roleassignmentsprincipalid) | Yes | string | Required. The principal ID of the principal (user/group/identity) to assign the role to. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | No | string | Optional. The principal type of the assigned principal ID. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | Yes | string | Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. | + +### Parameter: `roleAssignments.condition` + +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" + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Optional. Version of the condition. + +- Required: No +- Type: string +- Allowed: `[2.0]` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +Optional. The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +Optional. The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalId` + +Required. The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.principalType` + +Optional. The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: `[Device, ForeignGroup, Group, ServicePrincipal, User]` + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. + +- Required: Yes +- Type: string + +### 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 query. | +| `resourceGroupName` | string | The resource group the query was deployed into. | +| `resourceId` | string | The resource ID of the query. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/resource-graph/query/main.bicep b/avm/res/resource-graph/query/main.bicep new file mode 100644 index 0000000000..b608669bf4 --- /dev/null +++ b/avm/res/resource-graph/query/main.bicep @@ -0,0 +1,149 @@ +metadata name = 'Resource Graph Queries' +metadata description = 'This module deploys a Resource Graph Query.' +metadata owner = 'Azure/module-maintainers' + +// ================ // +// Parameters // +// ================ // + +@description('Required. Name of the Resource Graph Query.') +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 assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments roleAssignmentType + +@description('Optional. Resource tags.') +param tags object? + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Required. KQL query that will be graph.') +param query string + +@description('Optional. The description of a graph query.') +param queryDescription string = '' + +// =========== // +// Variables // +// =========== // + +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 rgQuery_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: rgQuery +} + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.resourcegraph-query.${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 rgQuery 'Microsoft.ResourceGraph/queries@2018-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + query: query + description: queryDescription + } +} + +resource rgQuery_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(rgQuery.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: rgQuery +}] + +// ================ // +// Outputs // +// ================ // + +@description('The name of the query.') +output name string = rgQuery.name + +@description('The resource ID of the query.') +output resourceId string = rgQuery.id + +@description('The resource group the query was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = rgQuery.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @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? +}[]? diff --git a/avm/res/resource-graph/query/main.json b/avm/res/resource-graph/query/main.json new file mode 100644 index 0000000000..884921fa1c --- /dev/null +++ b/avm/res/resource-graph/query/main.json @@ -0,0 +1,270 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "15120002565372840884" + }, + "name": "Resource Graph Queries", + "description": "This module deploys a Resource Graph Query.", + "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 name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "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 + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Resource Graph Query." + } + }, + "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 assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. KQL query that will be graph." + } + }, + "queryDescription": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of a graph query." + } + } + }, + "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')]" + } + }, + "resources": { + "rgQuery_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.ResourceGraph/queries/{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": [ + "rgQuery" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.resourcegraph-query.{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" + } + } + } + } + }, + "rgQuery": { + "type": "Microsoft.ResourceGraph/queries", + "apiVersion": "2018-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "query": "[parameters('query')]", + "description": "[parameters('queryDescription')]" + } + }, + "rgQuery_roleAssignments": { + "copy": { + "name": "rgQuery_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ResourceGraph/queries/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.ResourceGraph/queries', 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], 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": [ + "rgQuery" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the query." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the query." + }, + "value": "[resourceId('Microsoft.ResourceGraph/queries', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the query was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('rgQuery', '2018-09-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/resource-graph/query/tests/e2e/defaults/main.test.bicep b/avm/res/resource-graph/query/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..778d04cd9f --- /dev/null +++ b/avm/res/resource-graph/query/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,46 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-resourcegraph.queries-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rdsmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + query: 'Resources | limit 10' + } +} diff --git a/avm/res/resource-graph/query/tests/e2e/max/dependencies.bicep b/avm/res/resource-graph/query/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..0b7f226c1a --- /dev/null +++ b/avm/res/resource-graph/query/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/resource-graph/query/tests/e2e/max/main.test.bicep b/avm/res/resource-graph/query/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..f36aa01b11 --- /dev/null +++ b/avm/res/resource-graph/query/tests/e2e/max/main.test.bicep @@ -0,0 +1,71 @@ +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}-resourcegraph.queries-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rdsmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + lock: { + kind: 'None' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + query: loadTextContent('./query.kql') + queryDescription: 'An example query to list first 5 subscriptions.' + } +} diff --git a/avm/res/resource-graph/query/tests/e2e/max/query.kql b/avm/res/resource-graph/query/tests/e2e/max/query.kql new file mode 100644 index 0000000000..6b16fa609a --- /dev/null +++ b/avm/res/resource-graph/query/tests/e2e/max/query.kql @@ -0,0 +1,3 @@ +resourcecontainers +| where type == 'microsoft.resources/subscriptions' +| take 5 diff --git a/avm/res/resource-graph/query/tests/e2e/waf-aligned/main.test.bicep b/avm/res/resource-graph/query/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..42f559b4f9 --- /dev/null +++ b/avm/res/resource-graph/query/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,59 @@ +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}-resourcegraph.queries-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'rdswaf' + +@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: location +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + enableTelemetry: enableTelemetry + lock: { + kind: 'None' + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + query: 'resourcecontainers| where type == \'microsoft.resources/subscriptions\' | take 5' + queryDescription: 'An example query to list first 5 subscriptions.' + } +} diff --git a/avm/res/resource-graph/query/version.json b/avm/res/resource-graph/query/version.json new file mode 100644 index 0000000000..7fa401bdf7 --- /dev/null +++ b/avm/res/resource-graph/query/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From e95330585cd1978afa6c583e5fc2007b42bbe5fe Mon Sep 17 00:00:00 2001 From: Kris Baranek Date: Tue, 21 Nov 2023 16:21:11 +0100 Subject: [PATCH 3/7] feat: New Module `avm/res/sql/server` (#637) ## Description New Module `avm/res/sql/server`, migrated from CARML. ## Adding a new module - [x] A proposal has been submitted and approved. - [ ] I have included "Closes #{module_proposal_issue_number}" in the PR description. - [ ] I have run `brm validate` locally to verify the module files. - [x] I have run deployment tests locally to ensure the module is deployable. ## Pipeline references | Pipeline | | - | | [![avm.res.sql.server](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml/badge.svg?branch=users%2Fkrbar%2FsqlModule)](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml) | --------- Co-authored-by: Alexander Sehr --- .github/CODEOWNERS | 2 +- .github/workflows/avm.res.sql.server.yml | 81 + avm/res/sql/server/README.md | 1649 +++++++++ avm/res/sql/server/database/README.md | 439 +++ .../README.md | 87 + .../main.bicep | 49 + .../main.json | 92 + .../README.md | 71 + .../main.bicep | 41 + .../main.json | 76 + avm/res/sql/server/database/main.bicep | 269 ++ avm/res/sql/server/database/main.json | 678 ++++ avm/res/sql/server/elastic-pool/README.md | 169 + avm/res/sql/server/elastic-pool/main.bicep | 92 + avm/res/sql/server/elastic-pool/main.json | 189 ++ .../sql/server/encryption-protector/README.md | 83 + .../server/encryption-protector/main.bicep | 42 + .../sql/server/encryption-protector/main.json | 81 + avm/res/sql/server/firewall-rule/README.md | 76 + avm/res/sql/server/firewall-rule/main.bicep | 37 + avm/res/sql/server/firewall-rule/main.json | 76 + avm/res/sql/server/key/README.md | 78 + avm/res/sql/server/key/main.bicep | 47 + avm/res/sql/server/key/main.json | 95 + avm/res/sql/server/main.bicep | 460 +++ avm/res/sql/server/main.json | 2968 +++++++++++++++++ .../server/security-alert-policy/README.md | 123 + .../server/security-alert-policy/main.bicep | 62 + .../server/security-alert-policy/main.json | 120 + .../server/tests/e2e/admin/dependencies.bicep | 13 + .../server/tests/e2e/admin/main.test.bicep | 60 + .../server/tests/e2e/defaults/main.test.bicep | 54 + .../server/tests/e2e/max/dependencies.bicep | 111 + .../sql/server/tests/e2e/max/main.test.bicep | 193 ++ .../server/tests/e2e/pe/dependencies.bicep | 50 + .../sql/server/tests/e2e/pe/main.test.bicep | 78 + .../tests/e2e/secondary/dependencies.bicep | 36 + .../tests/e2e/secondary/main.test.bicep | 75 + .../tests/e2e/vulnAssm/dependencies.bicep | 35 + .../server/tests/e2e/vulnAssm/main.test.bicep | 92 + .../tests/e2e/waf-aligned/dependencies.bicep | 111 + .../tests/e2e/waf-aligned/main.test.bicep | 175 + avm/res/sql/server/version.json | 7 + .../sql/server/virtual-network-rule/README.md | 75 + .../server/virtual-network-rule/main.bicep | 37 + .../sql/server/virtual-network-rule/main.json | 75 + .../server/vulnerability-assessment/README.md | 108 + .../vulnerability-assessment/main.bicep | 64 + .../server/vulnerability-assessment/main.json | 162 + .../nested_storageRoleAssignment.bicep | 17 + 50 files changed, 9959 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/avm.res.sql.server.yml create mode 100644 avm/res/sql/server/README.md create mode 100644 avm/res/sql/server/database/README.md create mode 100644 avm/res/sql/server/database/backup-long-term-retention-policy/README.md create mode 100644 avm/res/sql/server/database/backup-long-term-retention-policy/main.bicep create mode 100644 avm/res/sql/server/database/backup-long-term-retention-policy/main.json create mode 100644 avm/res/sql/server/database/backup-short-term-retention-policy/README.md create mode 100644 avm/res/sql/server/database/backup-short-term-retention-policy/main.bicep create mode 100644 avm/res/sql/server/database/backup-short-term-retention-policy/main.json create mode 100644 avm/res/sql/server/database/main.bicep create mode 100644 avm/res/sql/server/database/main.json create mode 100644 avm/res/sql/server/elastic-pool/README.md create mode 100644 avm/res/sql/server/elastic-pool/main.bicep create mode 100644 avm/res/sql/server/elastic-pool/main.json create mode 100644 avm/res/sql/server/encryption-protector/README.md create mode 100644 avm/res/sql/server/encryption-protector/main.bicep create mode 100644 avm/res/sql/server/encryption-protector/main.json create mode 100644 avm/res/sql/server/firewall-rule/README.md create mode 100644 avm/res/sql/server/firewall-rule/main.bicep create mode 100644 avm/res/sql/server/firewall-rule/main.json create mode 100644 avm/res/sql/server/key/README.md create mode 100644 avm/res/sql/server/key/main.bicep create mode 100644 avm/res/sql/server/key/main.json create mode 100644 avm/res/sql/server/main.bicep create mode 100644 avm/res/sql/server/main.json create mode 100644 avm/res/sql/server/security-alert-policy/README.md create mode 100644 avm/res/sql/server/security-alert-policy/main.bicep create mode 100644 avm/res/sql/server/security-alert-policy/main.json create mode 100644 avm/res/sql/server/tests/e2e/admin/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/admin/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/max/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/pe/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/pe/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/secondary/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/secondary/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/vulnAssm/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/vulnAssm/main.test.bicep create mode 100644 avm/res/sql/server/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/sql/server/version.json create mode 100644 avm/res/sql/server/virtual-network-rule/README.md create mode 100644 avm/res/sql/server/virtual-network-rule/main.bicep create mode 100644 avm/res/sql/server/virtual-network-rule/main.json create mode 100644 avm/res/sql/server/vulnerability-assessment/README.md create mode 100644 avm/res/sql/server/vulnerability-assessment/main.bicep create mode 100644 avm/res/sql/server/vulnerability-assessment/main.json create mode 100644 avm/res/sql/server/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 62b334c5a3..e0a9e83bb3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -129,7 +129,7 @@ #/avm/res/signal-r-service/signal-r/ @Azure/avm-res-signalrservice-signalr-module-owners-bicep #/avm/res/signal-r-service/web-pub-sub/ @Azure/avm-res-signalrservice-webpubsub-module-owners-bicep #/avm/res/sql/managed-instance/ @Azure/avm-res-sql-managedinstance-module-owners-bicep -#/avm/res/sql/server/ @Azure/avm-res-sql-server-module-owners-bicep +/avm/res/sql/server/ @Azure/avm-res-sql-server-module-owners-bicep #/avm/res/storage/storage-account/ @Azure/avm-res-storage-storageaccount-module-owners-bicep #/avm/res/synapse/private-link-hub/ @Azure/avm-res-synapse-privatelinkhub-module-owners-bicep #/avm/res/synapse/workspace/ @Azure/avm-res-synapse-workspace-module-owners-bicep diff --git a/.github/workflows/avm.res.sql.server.yml b/.github/workflows/avm.res.sql.server.yml new file mode 100644 index 0000000000..f2dad4ff0f --- /dev/null +++ b/.github/workflows/avm.res.sql.server.yml @@ -0,0 +1,81 @@ +name: "avm.res.sql.server" + +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.server.yml" + - "avm/res/sql/server/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/sql/server" + workflowPath: ".github/workflows/avm.res.sql.server.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get parameter 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 }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Module" + 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 }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/sql/server/README.md b/avm/res/sql/server/README.md new file mode 100644 index 0000000000..86287e7a35 --- /dev/null +++ b/avm/res/sql/server/README.md @@ -0,0 +1,1649 @@ +# Azure SQL Servers `[Microsoft.Sql/servers]` + +This module deploys an Azure SQL Server. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Notes](#Notes) + +## 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/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Sql/servers` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers) | +| `Microsoft.Sql/servers/databases` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases) | +| `Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupLongTermRetentionPolicies) | +| `Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupShortTermRetentionPolicies) | +| `Microsoft.Sql/servers/elasticPools` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/elasticPools) | +| `Microsoft.Sql/servers/encryptionProtector` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/encryptionProtector) | +| `Microsoft.Sql/servers/firewallRules` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/firewallRules) | +| `Microsoft.Sql/servers/keys` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/keys) | +| `Microsoft.Sql/servers/securityAlertPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/securityAlertPolicies) | +| `Microsoft.Sql/servers/virtualNetworkRules` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/virtualNetworkRules) | +| `Microsoft.Sql/servers/vulnerabilityAssessments` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/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/server:`. + +- [With an administrator](#example-1-with-an-administrator) +- [Using only defaults](#example-2-using-only-defaults) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [Using Private Endpoints](#example-4-using-private-endpoints) +- [With a secondary database](#example-5-with-a-secondary-database) +- [With vulnerability assessment](#example-6-with-vulnerability-assessment) +- [WAF-aligned](#example-7-waf-aligned) + +### Example 1: _With an administrator_ + +This instance deploys the module with a Microsoft Entra ID identity as SQL administrator. + + +

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlsadmin' + params: { + // Required parameters + name: 'sqlsadmin' + // Non-required parameters + administrators: { + azureADOnlyAuthentication: true + login: 'myspn' + principalType: 'Application' + sid: '' + } + 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": "sqlsadmin" + }, + // Non-required parameters + "administrators": { + "value": { + "azureADOnlyAuthentication": true, + "login": "myspn", + "principalType": "Application", + "sid": "" + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-ssmin' + params: { + // Required parameters + name: 'ssmin001' + // Non-required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + 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": "ssmin001" + }, + // Non-required parameters + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "location": { + "value": "" + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlsmax' + params: { + // Required parameters + name: 'sqlsmax' + // Non-required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + databases: [ + { + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + capacity: 0 + collation: 'SQL_Latin1_General_CP1_CI_AS' + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + elasticPoolId: '' + encryptionProtectorObj: { + serverKeyName: '' + serverKeyType: 'AzureKeyVault' + } + licenseType: 'LicenseIncluded' + maxSizeBytes: 34359738368 + name: 'sqlsmaxdb-001' + skuName: 'ElasticPool' + skuTier: 'GeneralPurpose' + } + ] + elasticPools: [ + { + maintenanceConfigurationId: '' + name: 'sqlsmax-ep-001' + skuCapacity: 10 + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + } + ] + firewallRules: [ + { + endIpAddress: '0.0.0.0' + name: 'AllowAllWindowsAzureIps' + startIpAddress: '0.0.0.0' + } + ] + keys: [ + { + name: '' + serverKeyType: 'AzureKeyVault' + uri: '' + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + primaryUserAssignedIdentityId: '' + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + service: 'sqlServer' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + ] + restrictOutboundNetworkAccess: 'Disabled' + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + securityAlertPolicies: [ + { + emailAccountAdmins: true + name: 'Default' + state: 'Enabled' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + virtualNetworkRules: [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetId: '' + } + ] + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "sqlsmax" + }, + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "databases": { + "value": [ + { + "backupLongTermRetentionPolicy": { + "monthlyRetention": "P6M" + }, + "backupShortTermRetentionPolicy": { + "retentionDays": 14 + }, + "capacity": 0, + "collation": "SQL_Latin1_General_CP1_CI_AS", + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "elasticPoolId": "", + "encryptionProtectorObj": { + "serverKeyName": "", + "serverKeyType": "AzureKeyVault" + }, + "licenseType": "LicenseIncluded", + "maxSizeBytes": 34359738368, + "name": "sqlsmaxdb-001", + "skuName": "ElasticPool", + "skuTier": "GeneralPurpose" + } + ] + }, + "elasticPools": { + "value": [ + { + "maintenanceConfigurationId": "", + "name": "sqlsmax-ep-001", + "skuCapacity": 10, + "skuName": "GP_Gen5", + "skuTier": "GeneralPurpose" + } + ] + }, + "firewallRules": { + "value": [ + { + "endIpAddress": "0.0.0.0", + "name": "AllowAllWindowsAzureIps", + "startIpAddress": "0.0.0.0" + } + ] + }, + "keys": { + "value": [ + { + "name": "", + "serverKeyType": "AzureKeyVault", + "uri": "" + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "primaryUserAssignedIdentityId": { + "value": "" + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "service": "sqlServer", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + ] + }, + "restrictOutboundNetworkAccess": { + "value": "Disabled" + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "securityAlertPolicies": { + "value": [ + { + "emailAccountAdmins": true, + "name": "Default", + "state": "Enabled" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + }, + "virtualNetworkRules": { + "value": [ + { + "ignoreMissingVnetServiceEndpoint": true, + "name": "newVnetRule1", + "virtualNetworkSubnetId": "" + } + ] + }, + "vulnerabilityAssessmentsObj": { + "value": { + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "" + } + } + } +} +``` + +
+

+ +### Example 4: _Using Private Endpoints_ + +This instance deploys the module with Private Endpoints. + + +

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlspe' + params: { + // Required parameters + name: 'sqlspe' + // Non-required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + location: '' + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + ] + 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": "sqlspe" + }, + // Non-required parameters + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "location": { + "value": "" + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ +### Example 5: _With a secondary database_ + +This instance deploys the module with a secondary database. + + +

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlsec' + params: { + // Required parameters + name: 'sqlsec-sec' + // Non-required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + databases: [ + { + createMode: 'Secondary' + maxSizeBytes: 2147483648 + name: '' + skuName: 'Basic' + skuTier: 'Basic' + sourceDatabaseResourceId: '' + } + ] + 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": "sqlsec-sec" + }, + // Non-required parameters + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "databases": { + "value": [ + { + "createMode": "Secondary", + "maxSizeBytes": 2147483648, + "name": "", + "skuName": "Basic", + "skuTier": "Basic", + "sourceDatabaseResourceId": "" + } + ] + }, + "location": { + "value": "" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlsvln' + params: { + // Required parameters + name: 'sqlsvln' + // Non-required parameters + administratorLogin: 'adminUserName' + administratorLoginPassword: '' + location: '' + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + primaryUserAssignedIdentityId: '' + securityAlertPolicies: [ + { + emailAccountAdmins: true + name: 'Default' + state: 'Enabled' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + vulnerabilityAssessmentsObj: { + createStorageRoleAssignment: true + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + 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 + "name": { + "value": "sqlsvln" + }, + // Non-required parameters + "administratorLogin": { + "value": "adminUserName" + }, + "administratorLoginPassword": { + "value": "" + }, + "location": { + "value": "" + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "primaryUserAssignedIdentityId": { + "value": "" + }, + "securityAlertPolicies": { + "value": [ + { + "emailAccountAdmins": true, + "name": "Default", + "state": "Enabled" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + }, + "vulnerabilityAssessmentsObj": { + "value": { + "createStorageRoleAssignment": true, + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "", + "useStorageAccountAccessKey": false + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module server 'br/public:avm/res/sql/server:' = { + name: '${uniqueString(deployment().name, location)}-test-sqlswaf' + params: { + // Required parameters + name: 'sqlswaf' + // Non-required parameters + administrators: { + azureADOnlyAuthentication: true + login: 'myspn' + principalType: 'Application' + sid: '' + tenantId: '' + } + databases: [ + { + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + capacity: 0 + collation: 'SQL_Latin1_General_CP1_CI_AS' + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + elasticPoolId: '' + encryptionProtectorObj: { + serverKeyName: '' + serverKeyType: 'AzureKeyVault' + } + licenseType: 'LicenseIncluded' + maxSizeBytes: 34359738368 + name: 'sqlswafdb-001' + skuName: 'ElasticPool' + skuTier: 'GeneralPurpose' + } + ] + elasticPools: [ + { + maintenanceConfigurationId: '' + name: 'sqlswaf-ep-001' + skuCapacity: 10 + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + } + ] + keys: [ + { + serverKeyType: 'AzureKeyVault' + uri: '' + } + ] + location: '' + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + primaryUserAssignedIdentityId: '' + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + '' + ] + service: 'sqlServer' + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + ] + restrictOutboundNetworkAccess: 'Disabled' + securityAlertPolicies: [ + { + emailAccountAdmins: true + name: 'Default' + state: 'Enabled' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + virtualNetworkRules: [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetId: '' + } + ] + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: '' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "sqlswaf" + }, + "administrators": { + "value": { + "azureADOnlyAuthentication": true, + "login": "myspn", + "principalType": "Application", + "sid": "", + "tenantId": "" + } + }, + "databases": { + "value": [ + { + "backupLongTermRetentionPolicy": { + "monthlyRetention": "P6M" + }, + "backupShortTermRetentionPolicy": { + "retentionDays": 14 + }, + "capacity": 0, + "collation": "SQL_Latin1_General_CP1_CI_AS", + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "elasticPoolId": "", + "encryptionProtectorObj": { + "serverKeyName": "", + "serverKeyType": "AzureKeyVault" + }, + "licenseType": "LicenseIncluded", + "maxSizeBytes": 34359738368, + "name": "sqlswafdb-001", + "skuName": "ElasticPool", + "skuTier": "GeneralPurpose" + } + ] + }, + "elasticPools": { + "value": [ + { + "maintenanceConfigurationId": "", + "name": "sqlswaf-ep-001", + "skuCapacity": 10, + "skuName": "GP_Gen5", + "skuTier": "GeneralPurpose" + } + ] + }, + "keys": { + "value": [ + { + "serverKeyType": "AzureKeyVault", + "uri": "" + } + ] + }, + "location": { + "value": "" + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "primaryUserAssignedIdentityId": { + "value": "" + }, + "privateEndpoints": { + "value": [ + { + "privateDnsZoneResourceIds": [ + "" + ], + "service": "sqlServer", + "subnetResourceId": "", + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + ] + }, + "restrictOutboundNetworkAccess": { + "value": "Disabled" + }, + "securityAlertPolicies": { + "value": [ + { + "emailAccountAdmins": true, + "name": "Default", + "state": "Enabled" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + }, + "virtualNetworkRules": { + "value": [ + { + "ignoreMissingVnetServiceEndpoint": true, + "name": "newVnetRule1", + "virtualNetworkSubnetId": "" + } + ] + }, + "vulnerabilityAssessmentsObj": { + "value": { + "emailSubscriptionAdmins": true, + "name": "default", + "recurringScansEmails": [ + "test1@contoso.com", + "test2@contoso.com" + ], + "recurringScansIsEnabled": true, + "storageAccountResourceId": "" + } + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the server. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`administratorLogin`](#parameter-administratorlogin) | string | The administrator username for the server. Required if no `administrators` object for AAD authentication is provided. | +| [`administratorLoginPassword`](#parameter-administratorloginpassword) | securestring | The administrator login password. Required if no `administrators` object for AAD authentication is provided. | +| [`administrators`](#parameter-administrators) | object | The Azure Active Directory (AAD) administrator authentication. Required if no `administratorLogin` & `administratorLoginPassword` is provided. | +| [`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 | +| :-- | :-- | :-- | +| [`databases`](#parameter-databases) | array | The databases to create in the server. | +| [`elasticPools`](#parameter-elasticpools) | array | The Elastic Pools to create in the server. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`encryptionProtectorObj`](#parameter-encryptionprotectorobj) | object | The encryption protection configuration. | +| [`firewallRules`](#parameter-firewallrules) | array | The firewall rules to create in the server. | +| [`keys`](#parameter-keys) | array | The keys to configure. | +| [`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. | +| [`minimalTlsVersion`](#parameter-minimaltlsversion) | string | Minimal TLS version allowed. | +| [`privateEndpoints`](#parameter-privateendpoints) | array | Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. | +| [`publicNetworkAccess`](#parameter-publicnetworkaccess) | string | Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and neither firewall rules nor virtual network rules are set. | +| [`restrictOutboundNetworkAccess`](#parameter-restrictoutboundnetworkaccess) | string | Whether or not to restrict outbound network access for this server. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| [`securityAlertPolicies`](#parameter-securityalertpolicies) | array | The security alert policies to create in the server. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`virtualNetworkRules`](#parameter-virtualnetworkrules) | array | The virtual network rules to create in the server. | +| [`vulnerabilityAssessmentsObj`](#parameter-vulnerabilityassessmentsobj) | object | The vulnerability assessment configuration. | + +### Parameter: `administratorLogin` + +The administrator username for the server. Required if no `administrators` object for AAD authentication is provided. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `administratorLoginPassword` + +The administrator login password. Required if no `administrators` object for AAD authentication is provided. +- Required: No +- Type: securestring +- Default: `''` + +### Parameter: `administrators` + +The Azure Active Directory (AAD) administrator authentication. Required if no `administratorLogin` & `administratorLoginPassword` is provided. +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `databases` + +The databases to create in the server. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `elasticPools` + +The Elastic Pools to create in the server. +- Required: No +- Type: array +- 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: `firewallRules` + +The firewall rules to create in the server. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `keys` + +The keys to configure. +- 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 + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`kind`](#parameter-lockkind) | No | string | Optional. Specify the type of lock. | +| [`name`](#parameter-lockname) | No | string | Optional. Specify the name of lock. | + +### Parameter: `lock.kind` + +Optional. Specify the type of lock. + +- Required: No +- Type: string +- Allowed: `[CanNotDelete, None, ReadOnly]` + +### Parameter: `lock.name` + +Optional. Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `managedIdentities` + +The managed identity definition for this resource. +- Required: No +- Type: object + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`systemAssigned`](#parameter-managedidentitiessystemassigned) | No | bool | Optional. Enables system assigned managed identity on the resource. | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | No | array | Optional. The resource ID(s) to assign to the resource. | + +### Parameter: `managedIdentities.systemAssigned` + +Optional. Enables system assigned managed identity on the resource. + +- Required: No +- Type: bool + +### Parameter: `managedIdentities.userAssignedResourceIds` + +Optional. The resource ID(s) to assign to the resource. + +- Required: No +- Type: array + +### Parameter: `minimalTlsVersion` + +Minimal TLS version allowed. +- Required: No +- Type: string +- Default: `'1.2'` +- Allowed: + ```Bicep + [ + '1.0' + '1.1' + '1.2' + ] + ``` + +### Parameter: `name` + +The name of the server. +- 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: `privateEndpoints` + +Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`applicationSecurityGroupResourceIds`](#parameter-privateendpointsapplicationsecuritygroupresourceids) | No | array | Optional. Application security groups in which the private endpoint IP configuration is included. | +| [`customDnsConfigs`](#parameter-privateendpointscustomdnsconfigs) | No | array | Optional. Custom DNS configurations. | +| [`customNetworkInterfaceName`](#parameter-privateendpointscustomnetworkinterfacename) | No | string | Optional. The custom name of the network interface attached to the private endpoint. | +| [`enableTelemetry`](#parameter-privateendpointsenabletelemetry) | No | bool | Optional. Enable/Disable usage telemetry for module. | +| [`ipConfigurations`](#parameter-privateendpointsipconfigurations) | No | array | Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints. | +| [`location`](#parameter-privateendpointslocation) | No | string | Optional. The location to deploy the private endpoint to. | +| [`lock`](#parameter-privateendpointslock) | No | object | Optional. Specify the type of lock. | +| [`manualPrivateLinkServiceConnections`](#parameter-privateendpointsmanualprivatelinkserviceconnections) | No | array | Optional. Manual PrivateLink Service Connections. | +| [`name`](#parameter-privateendpointsname) | No | string | Optional. The name of the private endpoint. | +| [`privateDnsZoneGroupName`](#parameter-privateendpointsprivatednszonegroupname) | No | string | Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. | +| [`privateDnsZoneResourceIds`](#parameter-privateendpointsprivatednszoneresourceids) | No | array | Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. | +| [`roleAssignments`](#parameter-privateendpointsroleassignments) | No | array | Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| [`service`](#parameter-privateendpointsservice) | No | string | Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". | +| [`subnetResourceId`](#parameter-privateendpointssubnetresourceid) | Yes | string | Required. Resource ID of the subnet where the endpoint needs to be created. | +| [`tags`](#parameter-privateendpointstags) | No | object | Optional. Tags to be applied on all resources/resource groups in this deployment. | + +### Parameter: `privateEndpoints.applicationSecurityGroupResourceIds` + +Optional. Application security groups in which the private endpoint IP configuration is included. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.customDnsConfigs` + +Optional. Custom DNS configurations. + +- Required: No +- Type: array + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`fqdn`](#parameter-privateendpointscustomdnsconfigsfqdn) | No | string | Required. Fqdn that resolves to private endpoint ip address. | +| [`ipAddresses`](#parameter-privateendpointscustomdnsconfigsipaddresses) | Yes | array | Required. A list of private ip addresses of the private endpoint. | + +### Parameter: `privateEndpoints.customDnsConfigs.fqdn` + +Required. Fqdn that resolves to private endpoint ip address. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.customDnsConfigs.ipAddresses` + +Required. A list of private ip addresses of the private endpoint. + +- Required: Yes +- Type: array + + +### Parameter: `privateEndpoints.customNetworkInterfaceName` + +Optional. The custom name of the network interface attached to the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.enableTelemetry` + +Optional. Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `privateEndpoints.ipConfigurations` + +Optional. 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 + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`name`](#parameter-privateendpointsipconfigurationsname) | Yes | string | Required. The name of the resource that is unique within a resource group. | +| [`properties`](#parameter-privateendpointsipconfigurationsproperties) | Yes | object | Required. Properties of private endpoint IP configurations. | + +### Parameter: `privateEndpoints.ipConfigurations.name` + +Required. The name of the resource that is unique within a resource group. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.ipConfigurations.properties` + +Required. Properties of private endpoint IP configurations. + +- Required: Yes +- Type: object + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`groupId`](#parameter-privateendpointsipconfigurationspropertiesgroupid) | Yes | string | Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. | +| [`memberName`](#parameter-privateendpointsipconfigurationspropertiesmembername) | Yes | string | Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. | +| [`privateIPAddress`](#parameter-privateendpointsipconfigurationspropertiesprivateipaddress) | Yes | string | Required. A private ip address obtained from the private endpoint's subnet. | + +### Parameter: `privateEndpoints.ipConfigurations.properties.groupId` + +Required. 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` + +Required. 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` + +Required. A private ip address obtained from the private endpoint's subnet. + +- Required: Yes +- Type: string + + + +### Parameter: `privateEndpoints.location` + +Optional. The location to deploy the private endpoint to. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.lock` + +Optional. Specify the type of lock. + +- Required: No +- Type: object + +### Parameter: `privateEndpoints.manualPrivateLinkServiceConnections` + +Optional. Manual PrivateLink Service Connections. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.name` + +Optional. The name of the private endpoint. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneGroupName` + +Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided. + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.privateDnsZoneResourceIds` + +Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.roleAssignments` + +Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: No +- Type: array + +### Parameter: `privateEndpoints.service` + +Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob". + +- Required: No +- Type: string + +### Parameter: `privateEndpoints.subnetResourceId` + +Required. Resource ID of the subnet where the endpoint needs to be created. + +- Required: Yes +- Type: string + +### Parameter: `privateEndpoints.tags` + +Optional. Tags to be applied on all resources/resource groups in this deployment. + +- Required: No +- Type: object + +### Parameter: `publicNetworkAccess` + +Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and neither firewall rules nor virtual network rules are set. +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `restrictOutboundNetworkAccess` + +Whether or not to restrict outbound network access for this server. +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `roleAssignments` + +Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`condition`](#parameter-roleassignmentscondition) | No | string | 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`](#parameter-roleassignmentsconditionversion) | No | string | Optional. Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | No | string | Optional. The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | No | string | Optional. The description of the role assignment. | +| [`principalId`](#parameter-roleassignmentsprincipalid) | Yes | string | Required. The principal ID of the principal (user/group/identity) to assign the role to. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | No | string | Optional. The principal type of the assigned principal ID. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | Yes | string | Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. | + +### Parameter: `roleAssignments.condition` + +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" + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Optional. Version of the condition. + +- Required: No +- Type: string +- Allowed: `[2.0]` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +Optional. The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +Optional. The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalId` + +Required. The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.principalType` + +Optional. The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: `[Device, ForeignGroup, Group, ServicePrincipal, User]` + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. + +- Required: Yes +- Type: string + +### Parameter: `securityAlertPolicies` + +The security alert policies to create in the server. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `tags` + +Tags of the resource. +- Required: No +- Type: object + +### Parameter: `virtualNetworkRules` + +The virtual network rules to create in the server. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `vulnerabilityAssessmentsObj` + +The vulnerability assessment configuration. +- Required: No +- Type: object +- Default: `{}` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployed SQL server. | +| `resourceGroupName` | string | The resource group of the deployed SQL server. | +| `resourceId` | string | The resource ID of the deployed SQL server. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other CARML modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/network/private-endpoint:0.2.1` | Remote reference | + +## Notes + +### Parameter Usage: `administrators` + +Configure Azure Active Directory Authentication method for server administrator. + + +

+ +Parameter JSON format + +```json +"administrators": { + "value": { + "azureADOnlyAuthentication": true, + "login": "John Doe", // if application can be anything + "sid": "[[objectId]]", // if application, the object ID + "principalType" : "User", // options: "User", "Group", "Application" + "tenantId": "[[tenantId]]" + } +} +``` + +
+ +
+ +Bicep format + +```bicep +administrators: { + azureADOnlyAuthentication: true + login: 'John Doe' // if application can be anything + sid: '[[objectId]]' // if application the object ID + 'principalType' : 'User' // options: 'User' 'Group' 'Application' + tenantId: '[[tenantId]]' +} +``` + +
+

diff --git a/avm/res/sql/server/database/README.md b/avm/res/sql/server/database/README.md new file mode 100644 index 0000000000..005391f790 --- /dev/null +++ b/avm/res/sql/server/database/README.md @@ -0,0 +1,439 @@ +# SQL Server Database `[Microsoft.Sql/servers/databases]` + +This module deploys an Azure SQL Server Database. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Sql/servers/databases` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases) | +| `Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupLongTermRetentionPolicies) | +| `Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupShortTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the database. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`autoPauseDelay`](#parameter-autopausedelay) | int | Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled. | +| [`backupLongTermRetentionPolicy`](#parameter-backuplongtermretentionpolicy) | object | The long term backup retention policy to create for the database. | +| [`backupShortTermRetentionPolicy`](#parameter-backupshorttermretentionpolicy) | object | The short term backup retention policy to create for the database. | +| [`collation`](#parameter-collation) | string | The collation of the database. | +| [`createMode`](#parameter-createmode) | string | Specifies the mode of database creation. | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`elasticPoolId`](#parameter-elasticpoolid) | string | The resource ID of the elastic pool containing this database. | +| [`highAvailabilityReplicaCount`](#parameter-highavailabilityreplicacount) | int | The number of readonly secondary replicas associated with the database. | +| [`isLedgerOn`](#parameter-isledgeron) | bool | Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created. | +| [`licenseType`](#parameter-licensetype) | string | The license type to apply for this database. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`maintenanceConfigurationId`](#parameter-maintenanceconfigurationid) | string | Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur. | +| [`maxSizeBytes`](#parameter-maxsizebytes) | int | The max size of the database expressed in bytes. | +| [`minCapacity`](#parameter-mincapacity) | string | Minimal capacity that database will always have allocated. | +| [`preferredEnclaveType`](#parameter-preferredenclavetype) | string | Type of enclave requested on the database i.e. Default or VBS enclaves. | +| [`readScale`](#parameter-readscale) | string | The state of read-only routing. | +| [`recoveryServicesRecoveryPointResourceId`](#parameter-recoveryservicesrecoverypointresourceid) | string | Resource ID of backup if createMode set to RestoreLongTermRetentionBackup. | +| [`requestedBackupStorageRedundancy`](#parameter-requestedbackupstorageredundancy) | string | The storage account type to be used to store backups for this database. | +| [`restorePointInTime`](#parameter-restorepointintime) | string | Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore. | +| [`sampleName`](#parameter-samplename) | string | The name of the sample schema to apply when creating this database. | +| [`skuCapacity`](#parameter-skucapacity) | int | Capacity of the particular SKU. | +| [`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 name of the SKU. | +| [`skuSize`](#parameter-skusize) | string | Size of the particular SKU. | +| [`skuTier`](#parameter-skutier) | string | The skuTier or edition of the particular SKU. | +| [`sourceDatabaseDeletionDate`](#parameter-sourcedatabasedeletiondate) | string | The time that the database was deleted when restoring a deleted database. | +| [`sourceDatabaseResourceId`](#parameter-sourcedatabaseresourceid) | string | Resource ID of database if createMode set to Copy, Secondary, PointInTimeRestore, Recovery or Restore. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`zoneRedundant`](#parameter-zoneredundant) | bool | Whether or not this database is zone redundant. | + +### Parameter: `autoPauseDelay` + +Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `backupLongTermRetentionPolicy` + +The long term backup retention policy to create for the database. +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `backupShortTermRetentionPolicy` + +The short term backup retention policy to create for the database. +- Required: No +- Type: object +- Default: `{}` + +### Parameter: `collation` + +The collation of the database. +- Required: No +- Type: string +- Default: `'SQL_Latin1_General_CP1_CI_AS'` + +### Parameter: `createMode` + +Specifies the mode of database creation. +- Required: No +- Type: string +- Default: `'Default'` +- Allowed: + ```Bicep + [ + 'Copy' + 'Default' + 'OnlineSecondary' + 'PointInTimeRestore' + 'Recovery' + 'Restore' + 'RestoreLongTermRetentionBackup' + 'Secondary' + ] + ``` + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | No | string | 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`](#parameter-diagnosticsettingseventhubname) | No | string | 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. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | No | string | Optional. 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) | No | array | Optional. 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) | No | string | Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | No | array | Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to '' to disable log collection. | +| [`name`](#parameter-diagnosticsettingsname) | No | string | Optional. The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | No | string | 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. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | No | string | 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. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +Optional. 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: `[AzureDiagnostics, Dedicated]` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +Optional. 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 + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | No | string | Optional. 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) | No | string | Optional. 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` + +Optional. 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` + +Optional. 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` + +Optional. 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` + +Optional. 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 + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | Yes | string | Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to 'AllMetrics' to collect all metrics. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Required. 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.name` + +Optional. The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `elasticPoolId` + +The resource ID of the elastic pool containing this database. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `highAvailabilityReplicaCount` + +The number of readonly secondary replicas associated with the database. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `isLedgerOn` + +Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `licenseType` + +The license type to apply for this database. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `location` + +Location for all resources. +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `maintenanceConfigurationId` + +Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `maxSizeBytes` + +The max size of the database expressed in bytes. +- Required: No +- Type: int +- Default: `34359738368` + +### Parameter: `minCapacity` + +Minimal capacity that database will always have allocated. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `name` + +The name of the database. +- Required: Yes +- Type: string + +### Parameter: `preferredEnclaveType` + +Type of enclave requested on the database i.e. Default or VBS enclaves. +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'Default' + 'VBS' + ] + ``` + +### Parameter: `readScale` + +The state of read-only routing. +- Required: No +- Type: string +- Default: `'Disabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `recoveryServicesRecoveryPointResourceId` + +Resource ID of backup if createMode set to RestoreLongTermRetentionBackup. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `requestedBackupStorageRedundancy` + +The storage account type to be used to store backups for this database. +- Required: No +- Type: string +- Default: `''` +- Allowed: + ```Bicep + [ + '' + 'Geo' + 'Local' + 'Zone' + ] + ``` + +### Parameter: `restorePointInTime` + +Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `sampleName` + +The name of the sample schema to apply when creating this database. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `serverName` + +The name of the parent SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `skuCapacity` + +Capacity of the particular SKU. +- Required: No +- Type: int + +### 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: `''` + +### Parameter: `skuName` + +The name of the SKU. +- Required: No +- Type: string +- Default: `'GP_Gen5_2'` + +### Parameter: `skuSize` + +Size of the particular SKU. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `skuTier` + +The skuTier or edition of the particular SKU. +- Required: No +- Type: string +- Default: `'GeneralPurpose'` + +### Parameter: `sourceDatabaseDeletionDate` + +The time that the database was deleted when restoring a deleted database. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `sourceDatabaseResourceId` + +Resource ID of database if createMode set to Copy, Secondary, PointInTimeRestore, Recovery or Restore. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `tags` + +Tags of the resource. +- Required: No +- Type: object + +### Parameter: `zoneRedundant` + +Whether or not this database is zone redundant. +- 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 database. | +| `resourceGroupName` | string | The resource group of the deployed database. | +| `resourceId` | string | The resource ID of the deployed database. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/database/backup-long-term-retention-policy/README.md b/avm/res/sql/server/database/backup-long-term-retention-policy/README.md new file mode 100644 index 0000000000..595e959e14 --- /dev/null +++ b/avm/res/sql/server/database/backup-long-term-retention-policy/README.md @@ -0,0 +1,87 @@ +# SQL Server Database Long Term Backup Retention Policies `[Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies]` + +This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupLongTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`databaseName`](#parameter-databasename) | string | The name of the parent database. | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`monthlyRetention`](#parameter-monthlyretention) | string | Weekly retention in ISO 8601 duration format. | +| [`weeklyRetention`](#parameter-weeklyretention) | string | Monthly retention in ISO 8601 duration format. | +| [`weekOfYear`](#parameter-weekofyear) | int | Week of year backup to keep for yearly retention. | +| [`yearlyRetention`](#parameter-yearlyretention) | string | Yearly retention in ISO 8601 duration format. | + +### Parameter: `databaseName` + +The name of the parent database. +- Required: Yes +- Type: string + +### Parameter: `monthlyRetention` + +Weekly retention in ISO 8601 duration format. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `serverName` + +The name of the parent SQL Server. +- Required: Yes +- Type: string + +### Parameter: `weeklyRetention` + +Monthly retention in ISO 8601 duration format. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `weekOfYear` + +Week of year backup to keep for yearly retention. +- Required: No +- Type: int +- Default: `1` + +### Parameter: `yearlyRetention` + +Yearly retention in ISO 8601 duration format. +- Required: No +- Type: string +- Default: `''` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the long-term policy. | +| `resourceGroupName` | string | The resource group the long-term policy was deployed into. | +| `resourceId` | string | The resource ID of the long-term policy. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/database/backup-long-term-retention-policy/main.bicep b/avm/res/sql/server/database/backup-long-term-retention-policy/main.bicep new file mode 100644 index 0000000000..ad504a2672 --- /dev/null +++ b/avm/res/sql/server/database/backup-long-term-retention-policy/main.bicep @@ -0,0 +1,49 @@ +metadata name = 'SQL Server Database Long Term Backup Retention Policies' +metadata description = 'This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the parent SQL Server.') +param serverName string + +@description('Required. The name of the parent database.') +param databaseName string + +@description('Optional. Monthly retention in ISO 8601 duration format.') +param weeklyRetention string = '' + +@description('Optional. Weekly retention in ISO 8601 duration format.') +param monthlyRetention string = '' + +@description('Optional. Week of year backup to keep for yearly retention.') +param weekOfYear int = 1 + +@description('Optional. Yearly retention in ISO 8601 duration format.') +param yearlyRetention string = '' + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName + + resource database 'databases@2022-05-01-preview' existing = { + name: databaseName + } +} + +resource backupLongTermRetentionPolicy 'Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies@2022-05-01-preview' = { + name: 'default' + parent: server::database + properties: { + monthlyRetention: monthlyRetention + weeklyRetention: weeklyRetention + weekOfYear: weekOfYear + yearlyRetention: yearlyRetention + } +} + +@description('The resource group the long-term policy was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the long-term policy.') +output name string = backupLongTermRetentionPolicy.name + +@description('The resource ID of the long-term policy.') +output resourceId string = backupLongTermRetentionPolicy.id diff --git a/avm/res/sql/server/database/backup-long-term-retention-policy/main.json b/avm/res/sql/server/database/backup-long-term-retention-policy/main.json new file mode 100644 index 0000000000..c5227580b2 --- /dev/null +++ b/avm/res/sql/server/database/backup-long-term-retention-policy/main.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4846793430941310733" + }, + "name": "SQL Server Database Long Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the long-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the long-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the long-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/database/backup-short-term-retention-policy/README.md b/avm/res/sql/server/database/backup-short-term-retention-policy/README.md new file mode 100644 index 0000000000..59341e7fae --- /dev/null +++ b/avm/res/sql/server/database/backup-short-term-retention-policy/README.md @@ -0,0 +1,71 @@ +# Azure SQL Server Database Short Term Backup Retention Policies `[Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies]` + +This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/databases/backupShortTermRetentionPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`databaseName`](#parameter-databasename) | string | The name of the parent database. | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diffBackupIntervalInHours`](#parameter-diffbackupintervalinhours) | int | Differential backup interval in hours. | +| [`retentionDays`](#parameter-retentiondays) | int | Poin-in-time retention in days. | + +### Parameter: `databaseName` + +The name of the parent database. +- Required: Yes +- Type: string + +### Parameter: `diffBackupIntervalInHours` + +Differential backup interval in hours. +- Required: No +- Type: int +- Default: `24` + +### Parameter: `retentionDays` + +Poin-in-time retention in days. +- Required: No +- Type: int +- Default: `7` + +### Parameter: `serverName` + +The name of the parent SQL Server. +- Required: Yes +- Type: string + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the short-term policy. | +| `resourceGroupName` | string | The resource group the short-term policy was deployed into. | +| `resourceId` | string | The resource ID of the short-term policy. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/database/backup-short-term-retention-policy/main.bicep b/avm/res/sql/server/database/backup-short-term-retention-policy/main.bicep new file mode 100644 index 0000000000..0d86255e88 --- /dev/null +++ b/avm/res/sql/server/database/backup-short-term-retention-policy/main.bicep @@ -0,0 +1,41 @@ +metadata name = 'Azure SQL Server Database Short Term Backup Retention Policies' +metadata description = 'This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the parent SQL Server.') +param serverName string + +@description('Required. The name of the parent database.') +param databaseName string + +@description('Optional. Differential backup interval in hours.') +param diffBackupIntervalInHours int = 24 + +@description('Optional. Poin-in-time retention in days.') +param retentionDays int = 7 + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName + + resource database 'databases@2022-05-01-preview' existing = { + name: databaseName + } +} + +resource backupShortTermRetentionPolicy 'Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies@2022-05-01-preview' = { + name: 'default' + parent: server::database + properties: { + diffBackupIntervalInHours: diffBackupIntervalInHours + retentionDays: retentionDays + } +} + +@description('The resource group the short-term policy was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the short-term policy.') +output name string = backupShortTermRetentionPolicy.name + +@description('The resource ID of the short-term policy.') +output resourceId string = backupShortTermRetentionPolicy.id diff --git a/avm/res/sql/server/database/backup-short-term-retention-policy/main.json b/avm/res/sql/server/database/backup-short-term-retention-policy/main.json new file mode 100644 index 0000000000..1a54d72351 --- /dev/null +++ b/avm/res/sql/server/database/backup-short-term-retention-policy/main.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18285215987066720007" + }, + "name": "Azure SQL Server Database Short Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "diffBackupIntervalInHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Optional. Differential backup interval in hours." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 7, + "metadata": { + "description": "Optional. Poin-in-time retention in days." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "diffBackupIntervalInHours": "[parameters('diffBackupIntervalInHours')]", + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the short-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the short-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the short-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/database/main.bicep b/avm/res/sql/server/database/main.bicep new file mode 100644 index 0000000000..fad766e1cb --- /dev/null +++ b/avm/res/sql/server/database/main.bicep @@ -0,0 +1,269 @@ +metadata name = 'SQL Server Database' +metadata description = 'This module deploys an Azure SQL Server Database.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the database.') +param name string + +@description('Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment.') +param serverName string + +@description('Optional. The collation of the database.') +param collation string = 'SQL_Latin1_General_CP1_CI_AS' + +@description('Optional. The skuTier or edition of the particular SKU.') +param skuTier string = 'GeneralPurpose' + +@description('Optional. The name of the SKU.') +param skuName string = 'GP_Gen5_2' + +@description('Optional. Capacity of the particular SKU.') +param skuCapacity int? + +@description('Optional. Type of enclave requested on the database i.e. Default or VBS enclaves.') +@allowed([ + '' + 'Default' + 'VBS' +]) +param preferredEnclaveType string = '' + +@description('Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here.') +param skuFamily string = '' + +@description('Optional. Size of the particular SKU.') +param skuSize string = '' + +@description('Optional. The max size of the database expressed in bytes.') +param maxSizeBytes int = 34359738368 + +@description('Optional. The name of the sample schema to apply when creating this database.') +param sampleName string = '' + +@description('Optional. Whether or not this database is zone redundant.') +param zoneRedundant bool = false + +@description('Optional. The license type to apply for this database.') +param licenseType string = '' + +@description('Optional. The state of read-only routing.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param readScale string = 'Disabled' + +@description('Optional. The number of readonly secondary replicas associated with the database.') +param highAvailabilityReplicaCount int = 0 + +@description('Optional. Minimal capacity that database will always have allocated.') +param minCapacity string = '' + +@description('Optional. Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled.') +param autoPauseDelay int = 0 + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. The resource ID of the elastic pool containing this database.') +param elasticPoolId string = '' + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. Specifies the mode of database creation.') +@allowed([ + 'Default' + 'Copy' + 'OnlineSecondary' + 'PointInTimeRestore' + 'Recovery' + 'Restore' + 'RestoreLongTermRetentionBackup' + 'Secondary' +]) +param createMode string = 'Default' + +@description('Optional. Resource ID of database if createMode set to Copy, Secondary, PointInTimeRestore, Recovery or Restore.') +param sourceDatabaseResourceId string = '' + +@description('Optional. The time that the database was deleted when restoring a deleted database.') +param sourceDatabaseDeletionDate string = '' + +@description('Optional. Resource ID of backup if createMode set to RestoreLongTermRetentionBackup.') +param recoveryServicesRecoveryPointResourceId string = '' + +@description('Optional. Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore.') +param restorePointInTime string = '' + +@description('Optional. The storage account type to be used to store backups for this database.') +@allowed([ + 'Geo' + 'Local' + 'Zone' + '' +]) +param requestedBackupStorageRedundancy string = '' + +@description('Optional. Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created.') +param isLedgerOn bool = false + +@description('Optional. Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur.') +param maintenanceConfigurationId string = '' + +@description('Optional. The short term backup retention policy to create for the database.') +param backupShortTermRetentionPolicy object = {} + +@description('Optional. The long term backup retention policy to create for the database.') +param backupLongTermRetentionPolicy object = {} + +// The SKU object must be built in a variable +// The alternative, 'null' as default values, leads to non-terminating deployments +var skuVar = union({ + name: skuName + tier: skuTier + }, (skuCapacity != null) ? { + capacity: skuCapacity + } : !empty(skuFamily) ? { + family: skuFamily + } : !empty(skuSize) ? { + size: skuSize + } : {}) + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource database 'Microsoft.Sql/servers/databases@2022-05-01-preview' = { + name: name + parent: server + location: location + tags: tags + properties: { + preferredEnclaveType: !empty(preferredEnclaveType) ? preferredEnclaveType : null + collation: collation + maxSizeBytes: maxSizeBytes + sampleName: sampleName + zoneRedundant: zoneRedundant + licenseType: licenseType + readScale: readScale + minCapacity: !empty(minCapacity) ? json(minCapacity) : 0 // The json() function is used to allow specifying a decimal value. + autoPauseDelay: autoPauseDelay + highAvailabilityReplicaCount: highAvailabilityReplicaCount + requestedBackupStorageRedundancy: any(requestedBackupStorageRedundancy) + isLedgerOn: isLedgerOn + maintenanceConfigurationId: !empty(maintenanceConfigurationId) ? maintenanceConfigurationId : null + elasticPoolId: elasticPoolId + createMode: createMode + sourceDatabaseId: !empty(sourceDatabaseResourceId) ? sourceDatabaseResourceId : null + sourceDatabaseDeletionDate: !empty(sourceDatabaseDeletionDate) ? sourceDatabaseDeletionDate : null + recoveryServicesRecoveryPointId: !empty(recoveryServicesRecoveryPointResourceId) ? recoveryServicesRecoveryPointResourceId : null + restorePointInTime: !empty(restorePointInTime) ? restorePointInTime : null + } + sku: skuVar +} + +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 + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: database +}] + +module database_backupShortTermRetentionPolicy 'backup-short-term-retention-policy/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-${name}-shBakRetPol' + params: { + serverName: serverName + databaseName: database.name + diffBackupIntervalInHours: contains(backupShortTermRetentionPolicy, 'diffBackupIntervalInHours') ? backupShortTermRetentionPolicy.diffBackupIntervalInHours : 24 + retentionDays: contains(backupShortTermRetentionPolicy, 'retentionDays') ? backupShortTermRetentionPolicy.retentionDays : 7 + } +} + +module database_backupLongTermRetentionPolicy 'backup-long-term-retention-policy/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-${name}-lgBakRetPol' + params: { + serverName: serverName + databaseName: database.name + weeklyRetention: contains(backupLongTermRetentionPolicy, 'weeklyRetention') ? backupLongTermRetentionPolicy.weeklyRetention : '' + monthlyRetention: contains(backupLongTermRetentionPolicy, 'monthlyRetention') ? backupLongTermRetentionPolicy.monthlyRetention : '' + yearlyRetention: contains(backupLongTermRetentionPolicy, 'yearlyRetention') ? backupLongTermRetentionPolicy.yearlyRetention : '' + weekOfYear: contains(backupLongTermRetentionPolicy, 'weekOfYear') ? backupLongTermRetentionPolicy.weekOfYear : 1 + } +} + +@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 of the deployed database.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = database.location + +// =============== // +// Definitions // +// =============== // + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @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/server/database/main.json b/avm/res/sql/server/database/main.json new file mode 100644 index 0000000000..fe1d8fd60b --- /dev/null +++ b/avm/res/sql/server/database/main.json @@ -0,0 +1,678 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "16643582358589893317" + }, + "name": "SQL Server Database", + "description": "This module deploys an Azure SQL Server Database.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "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." + } + }, + "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." + } + } + } + }, + "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 database." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. The collation of the database." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "GeneralPurpose", + "metadata": { + "description": "Optional. The skuTier or edition of the particular SKU." + } + }, + "skuName": { + "type": "string", + "defaultValue": "GP_Gen5_2", + "metadata": { + "description": "Optional. The name of the SKU." + } + }, + "skuCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Capacity of the particular SKU." + } + }, + "preferredEnclaveType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Default", + "VBS" + ], + "metadata": { + "description": "Optional. Type of enclave requested on the database i.e. Default or VBS enclaves." + } + }, + "skuFamily": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "skuSize": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The max size of the database expressed in bytes." + } + }, + "sampleName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the sample schema to apply when creating this database." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this database is zone redundant." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The license type to apply for this database." + } + }, + "readScale": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The state of read-only routing." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The number of readonly secondary replicas associated with the database." + } + }, + "minCapacity": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Minimal capacity that database will always have allocated." + } + }, + "autoPauseDelay": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "elasticPoolId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the elastic pool containing this database." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "createMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "Copy", + "OnlineSecondary", + "PointInTimeRestore", + "Recovery", + "Restore", + "RestoreLongTermRetentionBackup", + "Secondary" + ], + "metadata": { + "description": "Optional. Specifies the mode of database creation." + } + }, + "sourceDatabaseResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of database if createMode set to Copy, Secondary, PointInTimeRestore, Recovery or Restore." + } + }, + "sourceDatabaseDeletionDate": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The time that the database was deleted when restoring a deleted database." + } + }, + "recoveryServicesRecoveryPointResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of backup if createMode set to RestoreLongTermRetentionBackup." + } + }, + "restorePointInTime": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore." + } + }, + "requestedBackupStorageRedundancy": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Geo", + "Local", + "Zone", + "" + ], + "metadata": { + "description": "Optional. The storage account type to be used to store backups for this database." + } + }, + "isLedgerOn": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur." + } + }, + "backupShortTermRetentionPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The short term backup retention policy to create for the database." + } + }, + "backupLongTermRetentionPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The long term backup retention policy to create for the database." + } + } + }, + "variables": { + "skuVar": "[union(createObject('name', parameters('skuName'), 'tier', parameters('skuTier')), if(not(equals(parameters('skuCapacity'), null())), createObject('capacity', parameters('skuCapacity')), if(not(empty(parameters('skuFamily'))), createObject('family', parameters('skuFamily')), if(not(empty(parameters('skuSize'))), createObject('size', parameters('skuSize')), createObject()))))]" + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "database": { + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "preferredEnclaveType": "[if(not(empty(parameters('preferredEnclaveType'))), parameters('preferredEnclaveType'), null())]", + "collation": "[parameters('collation')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "sampleName": "[parameters('sampleName')]", + "zoneRedundant": "[parameters('zoneRedundant')]", + "licenseType": "[parameters('licenseType')]", + "readScale": "[parameters('readScale')]", + "minCapacity": "[if(not(empty(parameters('minCapacity'))), json(parameters('minCapacity')), 0)]", + "autoPauseDelay": "[parameters('autoPauseDelay')]", + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "requestedBackupStorageRedundancy": "[parameters('requestedBackupStorageRedundancy')]", + "isLedgerOn": "[parameters('isLedgerOn')]", + "maintenanceConfigurationId": "[if(not(empty(parameters('maintenanceConfigurationId'))), parameters('maintenanceConfigurationId'), null())]", + "elasticPoolId": "[parameters('elasticPoolId')]", + "createMode": "[parameters('createMode')]", + "sourceDatabaseId": "[if(not(empty(parameters('sourceDatabaseResourceId'))), parameters('sourceDatabaseResourceId'), null())]", + "sourceDatabaseDeletionDate": "[if(not(empty(parameters('sourceDatabaseDeletionDate'))), parameters('sourceDatabaseDeletionDate'), null())]", + "recoveryServicesRecoveryPointId": "[if(not(empty(parameters('recoveryServicesRecoveryPointResourceId'))), parameters('recoveryServicesRecoveryPointResourceId'), null())]", + "restorePointInTime": "[if(not(empty(parameters('restorePointInTime'))), parameters('restorePointInTime'), null())]" + }, + "sku": "[variables('skuVar')]", + "dependsOn": [ + "server" + ] + }, + "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/servers/{0}/databases/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "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')]", + "metrics": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics', 'timeGrain', null(), 'enabled', true())))]", + "logs": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'AllLogs', 'enabled', true())))]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "database" + ] + }, + "database_backupShortTermRetentionPolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-shBakRetPol', uniqueString(deployment().name, parameters('location')), parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "diffBackupIntervalInHours": "[if(contains(parameters('backupShortTermRetentionPolicy'), 'diffBackupIntervalInHours'), createObject('value', parameters('backupShortTermRetentionPolicy').diffBackupIntervalInHours), createObject('value', 24))]", + "retentionDays": "[if(contains(parameters('backupShortTermRetentionPolicy'), 'retentionDays'), createObject('value', parameters('backupShortTermRetentionPolicy').retentionDays), createObject('value', 7))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18285215987066720007" + }, + "name": "Azure SQL Server Database Short Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "diffBackupIntervalInHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Optional. Differential backup interval in hours." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 7, + "metadata": { + "description": "Optional. Poin-in-time retention in days." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "diffBackupIntervalInHours": "[parameters('diffBackupIntervalInHours')]", + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the short-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the short-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the short-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "dependsOn": [ + "database" + ] + }, + "database_backupLongTermRetentionPolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-lgBakRetPol', uniqueString(deployment().name, parameters('location')), parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "weeklyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'weeklyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').weeklyRetention), createObject('value', ''))]", + "monthlyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'monthlyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').monthlyRetention), createObject('value', ''))]", + "yearlyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'yearlyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').yearlyRetention), createObject('value', ''))]", + "weekOfYear": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'weekOfYear'), createObject('value', parameters('backupLongTermRetentionPolicy').weekOfYear), createObject('value', 1))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4846793430941310733" + }, + "name": "SQL Server Database Long Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the long-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the long-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the long-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "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/servers/databases', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('database', '2022-05-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/elastic-pool/README.md b/avm/res/sql/server/elastic-pool/README.md new file mode 100644 index 0000000000..90a855bb6a --- /dev/null +++ b/avm/res/sql/server/elastic-pool/README.md @@ -0,0 +1,169 @@ +# SQL Server Elastic Pool `[Microsoft.Sql/servers/elasticPools]` + +This module deploys an Azure SQL Server Elastic Pool. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/elasticPools` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/elasticPools) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Elastic Pool. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`databaseMaxCapacity`](#parameter-databasemaxcapacity) | int | The maximum capacity any one database can consume. | +| [`databaseMinCapacity`](#parameter-databasemincapacity) | int | The minimum capacity all databases are guaranteed. | +| [`highAvailabilityReplicaCount`](#parameter-highavailabilityreplicacount) | int | The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools. | +| [`licenseType`](#parameter-licensetype) | string | The license type to apply for this elastic pool. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`maintenanceConfigurationId`](#parameter-maintenanceconfigurationid) | string | Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur. | +| [`maxSizeBytes`](#parameter-maxsizebytes) | int | The storage limit for the database elastic pool in bytes. | +| [`minCapacity`](#parameter-mincapacity) | int | Minimal capacity that serverless pool will not shrink below, if not paused. | +| [`skuCapacity`](#parameter-skucapacity) | int | Capacity of the particular SKU. | +| [`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. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`zoneRedundant`](#parameter-zoneredundant) | bool | Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones. | + +### Parameter: `databaseMaxCapacity` + +The maximum capacity any one database can consume. +- Required: No +- Type: int +- Default: `2` + +### Parameter: `databaseMinCapacity` + +The minimum capacity all databases are guaranteed. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `highAvailabilityReplicaCount` + +The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools. +- Required: No +- Type: int + +### Parameter: `licenseType` + +The license type to apply for this elastic pool. +- Required: No +- Type: string +- Default: `'LicenseIncluded'` +- Allowed: + ```Bicep + [ + 'BasePrice' + 'LicenseIncluded' + ] + ``` + +### Parameter: `location` + +Location for all resources. +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `maintenanceConfigurationId` + +Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `maxSizeBytes` + +The storage limit for the database elastic pool in bytes. +- Required: No +- Type: int +- Default: `34359738368` + +### Parameter: `minCapacity` + +Minimal capacity that serverless pool will not shrink below, if not paused. +- Required: No +- Type: int + +### Parameter: `name` + +The name of the Elastic Pool. +- Required: Yes +- Type: string + +### Parameter: `serverName` + +The name of the parent SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `skuCapacity` + +Capacity of the particular SKU. +- Required: No +- Type: int +- Default: `2` + +### 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: `tags` + +Tags of the resource. +- Required: No +- Type: object + +### Parameter: `zoneRedundant` + +Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones. +- 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 Elastic Pool. | +| `resourceGroupName` | string | The resource group of the deployed Elastic Pool. | +| `resourceId` | string | The resource ID of the deployed Elastic Pool. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/elastic-pool/main.bicep b/avm/res/sql/server/elastic-pool/main.bicep new file mode 100644 index 0000000000..c1c37a5831 --- /dev/null +++ b/avm/res/sql/server/elastic-pool/main.bicep @@ -0,0 +1,92 @@ +metadata name = 'SQL Server Elastic Pool' +metadata description = 'This module deploys an Azure SQL Server Elastic Pool.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Elastic Pool.') +param name string + +@description('Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment.') +param serverName string + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Capacity of the particular SKU.') +param skuCapacity int = 2 + +@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. The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools.') +param highAvailabilityReplicaCount int? + +@description('Optional. The license type to apply for this elastic pool.') +@allowed([ + 'BasePrice' + 'LicenseIncluded' +]) +param licenseType string = 'LicenseIncluded' + +@description('Optional. Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur.') +param maintenanceConfigurationId string = '' + +@description('Optional. The storage limit for the database elastic pool in bytes.') +param maxSizeBytes int = 34359738368 + +@description('Optional. Minimal capacity that serverless pool will not shrink below, if not paused.') +param minCapacity int? + +@description('Optional. The maximum capacity any one database can consume.') +param databaseMaxCapacity int = 2 + +@description('Optional. The minimum capacity all databases are guaranteed.') +param databaseMinCapacity int = 0 + +@description('Optional. Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones.') +param zoneRedundant bool = false + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource elasticPool 'Microsoft.Sql/servers/elasticPools@2022-05-01-preview' = { + name: name + location: location + parent: server + tags: tags + sku: { + capacity: skuCapacity + name: skuName + tier: skuTier + } + properties: { + highAvailabilityReplicaCount: highAvailabilityReplicaCount + licenseType: licenseType + maintenanceConfigurationId: maintenanceConfigurationId + maxSizeBytes: maxSizeBytes + minCapacity: minCapacity + perDatabaseSettings: { + minCapacity: databaseMinCapacity + maxCapacity: databaseMaxCapacity + } + zoneRedundant: zoneRedundant + } +} + +@description('The name of the deployed Elastic Pool.') +output name string = elasticPool.name + +@description('The resource ID of the deployed Elastic Pool.') +output resourceId string = elasticPool.id + +@description('The resource group of the deployed Elastic Pool.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = elasticPool.location diff --git a/avm/res/sql/server/elastic-pool/main.json b/avm/res/sql/server/elastic-pool/main.json new file mode 100644 index 0000000000..0fa28e212e --- /dev/null +++ b/avm/res/sql/server/elastic-pool/main.json @@ -0,0 +1,189 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "3978881342800176671" + }, + "name": "SQL Server Elastic Pool", + "description": "This module deploys an Azure SQL Server Elastic Pool.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Elastic Pool." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "skuCapacity": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. Capacity of the particular SKU." + } + }, + "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." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "LicenseIncluded", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "metadata": { + "description": "Optional. The license type to apply for this elastic pool." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The storage limit for the database elastic pool in bytes." + } + }, + "minCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Minimal capacity that serverless pool will not shrink below, if not paused." + } + }, + "databaseMaxCapacity": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. The maximum capacity any one database can consume." + } + }, + "databaseMinCapacity": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The minimum capacity all databases are guaranteed." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones." + } + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "elasticPool": { + "type": "Microsoft.Sql/servers/elasticPools", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "capacity": "[parameters('skuCapacity')]", + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]" + }, + "properties": { + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "licenseType": "[parameters('licenseType')]", + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationId')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "minCapacity": "[parameters('minCapacity')]", + "perDatabaseSettings": { + "minCapacity": "[parameters('databaseMinCapacity')]", + "maxCapacity": "[parameters('databaseMaxCapacity')]" + }, + "zoneRedundant": "[parameters('zoneRedundant')]" + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed Elastic Pool." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed Elastic Pool." + }, + "value": "[resourceId('Microsoft.Sql/servers/elasticPools', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed Elastic Pool." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('elasticPool', '2022-05-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/encryption-protector/README.md b/avm/res/sql/server/encryption-protector/README.md new file mode 100644 index 0000000000..c65c22e551 --- /dev/null +++ b/avm/res/sql/server/encryption-protector/README.md @@ -0,0 +1,83 @@ +# Azure SQL Server Encryption Protector `[Microsoft.Sql/servers/encryptionProtector]` + +This module deploys an Azure SQL Server Encryption Protector. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/encryptionProtector` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/encryptionProtector) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverKeyName`](#parameter-serverkeyname) | string | The name of the server key. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`sqlServerName`](#parameter-sqlservername) | string | The name of the sql server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`autoRotationEnabled`](#parameter-autorotationenabled) | bool | Key auto rotation opt-in. | +| [`serverKeyType`](#parameter-serverkeytype) | string | The encryption protector type. | + +### Parameter: `autoRotationEnabled` + +Key auto rotation opt-in. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `serverKeyName` + +The name of the server key. +- Required: Yes +- Type: string + +### Parameter: `serverKeyType` + +The encryption protector type. +- Required: No +- Type: string +- Default: `'ServiceManaged'` +- Allowed: + ```Bicep + [ + 'AzureKeyVault' + 'ServiceManaged' + ] + ``` + +### Parameter: `sqlServerName` + +The name of the sql server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed encryption protector. | +| `resourceGroupName` | string | The resource group of the deployed encryption protector. | +| `resourceId` | string | The resource ID of the encryption protector. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/encryption-protector/main.bicep b/avm/res/sql/server/encryption-protector/main.bicep new file mode 100644 index 0000000000..ac137994a9 --- /dev/null +++ b/avm/res/sql/server/encryption-protector/main.bicep @@ -0,0 +1,42 @@ +metadata name = 'Azure SQL Server Encryption Protector' +metadata description = 'This module deploys an Azure SQL Server Encryption Protector.' +metadata owner = 'Azure/module-maintainers' + +@description('Conditional. The name of the sql server. Required if the template is used in a standalone deployment.') +param sqlServerName string + +@description('Required. The name of the server key.') +param serverKeyName string + +@description('Optional. Key auto rotation opt-in.') +param autoRotationEnabled bool = false + +@description('Optional. The encryption protector type.') +@allowed([ + 'AzureKeyVault' + 'ServiceManaged' +]) +param serverKeyType string = 'ServiceManaged' + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: sqlServerName +} + +resource encryptionProtector 'Microsoft.Sql/servers/encryptionProtector@2022-05-01-preview' = { + name: 'current' + parent: sqlServer + properties: { + serverKeyType: serverKeyType + autoRotationEnabled: autoRotationEnabled + serverKeyName: serverKeyName + } +} + +@description('The name of the deployed encryption protector.') +output name string = encryptionProtector.name + +@description('The resource ID of the encryption protector.') +output resourceId string = encryptionProtector.id + +@description('The resource group of the deployed encryption protector.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/server/encryption-protector/main.json b/avm/res/sql/server/encryption-protector/main.json new file mode 100644 index 0000000000..2eeb13ece9 --- /dev/null +++ b/avm/res/sql/server/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.23.1.45101", + "templateHash": "4659476195817177643" + }, + "name": "Azure SQL Server Encryption Protector", + "description": "This module deploys an Azure SQL Server Encryption Protector.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "sqlServerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the sql server. Required if the template is used in a standalone deployment." + } + }, + "serverKeyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the server key." + } + }, + "autoRotationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Key auto rotation opt-in." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/encryptionProtector", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('sqlServerName'), 'current')]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "autoRotationEnabled": "[parameters('autoRotationEnabled')]", + "serverKeyName": "[parameters('serverKeyName')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed encryption protector." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the encryption protector." + }, + "value": "[resourceId('Microsoft.Sql/servers/encryptionProtector', parameters('sqlServerName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed encryption protector." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/firewall-rule/README.md b/avm/res/sql/server/firewall-rule/README.md new file mode 100644 index 0000000000..d428ee56a8 --- /dev/null +++ b/avm/res/sql/server/firewall-rule/README.md @@ -0,0 +1,76 @@ +# Azure SQL Server Firewall Rule `[Microsoft.Sql/servers/firewallRules]` + +This module deploys an Azure SQL Server Firewall Rule. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/firewallRules` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/firewallRules) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Server Firewall Rule. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`endIpAddress`](#parameter-endipaddress) | string | The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses. | +| [`startIpAddress`](#parameter-startipaddress) | string | The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses. | + +### Parameter: `endIpAddress` + +The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses. +- Required: No +- Type: string +- Default: `'0.0.0.0'` + +### Parameter: `name` + +The name of the Server Firewall Rule. +- Required: Yes +- Type: string + +### Parameter: `serverName` + +The name of the parent SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `startIpAddress` + +The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses. +- Required: No +- Type: string +- Default: `'0.0.0.0'` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed firewall rule. | +| `resourceGroupName` | string | The resource group of the deployed firewall rule. | +| `resourceId` | string | The resource ID of the deployed firewall rule. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/firewall-rule/main.bicep b/avm/res/sql/server/firewall-rule/main.bicep new file mode 100644 index 0000000000..54b4afd511 --- /dev/null +++ b/avm/res/sql/server/firewall-rule/main.bicep @@ -0,0 +1,37 @@ +metadata name = 'Azure SQL Server Firewall Rule' +metadata description = 'This module deploys an Azure SQL Server Firewall Rule.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Server Firewall Rule.') +param name string + +@description('Optional. The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value \'0.0.0.0\' for all Azure-internal IP addresses.') +param endIpAddress string = '0.0.0.0' + +@description('Optional. The start IP address of the firewall rule. Must be IPv4 format. Use value \'0.0.0.0\' for all Azure-internal IP addresses.') +param startIpAddress string = '0.0.0.0' + +@description('Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment.') +param serverName string + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource firewallRule 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = { + name: name + parent: server + properties: { + endIpAddress: endIpAddress + startIpAddress: startIpAddress + } +} + +@description('The name of the deployed firewall rule.') +output name string = firewallRule.name + +@description('The resource ID of the deployed firewall rule.') +output resourceId string = firewallRule.id + +@description('The resource group of the deployed firewall rule.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/server/firewall-rule/main.json b/avm/res/sql/server/firewall-rule/main.json new file mode 100644 index 0000000000..2e67b847ef --- /dev/null +++ b/avm/res/sql/server/firewall-rule/main.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "3897281461367168361" + }, + "name": "Azure SQL Server Firewall Rule", + "description": "This module deploys an Azure SQL Server Firewall Rule.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Firewall Rule." + } + }, + "endIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "startIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/firewallRules", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "endIpAddress": "[parameters('endIpAddress')]", + "startIpAddress": "[parameters('startIpAddress')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed firewall rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed firewall rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/firewallRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed firewall rule." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/key/README.md b/avm/res/sql/server/key/README.md new file mode 100644 index 0000000000..da5d8f9019 --- /dev/null +++ b/avm/res/sql/server/key/README.md @@ -0,0 +1,78 @@ +# Azure SQL Server Keys `[Microsoft.Sql/servers/keys]` + +This module deploys an Azure SQL Server Key. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/keys` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/keys) | + +## Parameters + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the key. Must follow the [__] pattern. | +| [`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: No +- Type: string + +### Parameter: `serverKeyType` + +The encryption protector type like "ServiceManaged", "AzureKeyVault". +- Required: No +- Type: string +- Default: `'ServiceManaged'` +- Allowed: + ```Bicep + [ + 'AzureKeyVault' + 'ServiceManaged' + ] + ``` + +### Parameter: `serverName` + +The name of the parent SQL server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### 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 server key. | +| `resourceGroupName` | string | The resource group of the deployed server key. | +| `resourceId` | string | The resource ID of the deployed server key. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/key/main.bicep b/avm/res/sql/server/key/main.bicep new file mode 100644 index 0000000000..402a634b33 --- /dev/null +++ b/avm/res/sql/server/key/main.bicep @@ -0,0 +1,47 @@ +metadata name = 'Azure SQL Server Keys' +metadata description = 'This module deploys an Azure SQL Server Key.' +metadata owner = 'Azure/module-maintainers' + +@description('Optional. The name of the key. Must follow the [__] pattern.') +param name string? + +@description('Conditional. The name of the parent SQL server. Required if the template is used in a standalone deployment.') +param serverName 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 server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource key 'Microsoft.Sql/servers/keys@2022-05-01-preview' = { + name: name ?? serverKeyName + parent: server + properties: { + serverKeyType: serverKeyType + uri: uri + } +} + +@description('The name of the deployed server key.') +output name string = key.name + +@description('The resource ID of the deployed server key.') +output resourceId string = key.id + +@description('The resource group of the deployed server key.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/server/key/main.json b/avm/res/sql/server/key/main.json new file mode 100644 index 0000000000..1d272ee76a --- /dev/null +++ b/avm/res/sql/server/key/main.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "2292967663993625779" + }, + "name": "Azure SQL Server Keys", + "description": "This module deploys an Azure SQL Server Key.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the key. Must follow the [__] pattern." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL server. 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": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "key": { + "type": "Microsoft.Sql/servers/keys", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed server key." + }, + "value": "[coalesce(parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed server key." + }, + "value": "[resourceId('Microsoft.Sql/servers/keys', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed server key." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/main.bicep b/avm/res/sql/server/main.bicep new file mode 100644 index 0000000000..e397cc2bf0 --- /dev/null +++ b/avm/res/sql/server/main.bicep @@ -0,0 +1,460 @@ +metadata name = 'Azure SQL Servers' +metadata description = 'This module deploys an Azure SQL Server.' +metadata owner = 'Azure/module-maintainers' + +@description('Conditional. The administrator username for the server. Required if no `administrators` object for AAD authentication is provided.') +param administratorLogin string = '' + +@description('Conditional. The administrator login password. Required if no `administrators` object for AAD authentication is provided.') +@secure() +param administratorLoginPassword string = '' + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Required. The name of the server.') +param name string + +@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. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments roleAssignmentType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. The databases to create in the server.') +param databases array = [] + +@description('Optional. The Elastic Pools to create in the server.') +param elasticPools array = [] + +@description('Optional. The firewall rules to create in the server.') +param firewallRules array = [] + +@description('Optional. The virtual network rules to create in the server.') +param virtualNetworkRules array = [] + +@description('Optional. The security alert policies to create in the server.') +param securityAlertPolicies array = [] + +@description('Optional. The keys to configure.') +param keys array = [] + +@description('Conditional. The Azure Active Directory (AAD) administrator authentication. Required if no `administratorLogin` & `administratorLoginPassword` is provided.') +param administrators object = {} + +@allowed([ + '1.0' + '1.1' + '1.2' +]) +@description('Optional. Minimal TLS version allowed.') +param minimalTlsVersion string = '1.2' + +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointType + +@description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and neither firewall rules nor virtual network rules are set.') +@allowed([ + '' + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string = '' + +@description('Optional. Whether or not to restrict outbound network access for this server.') +@allowed([ + '' + 'Enabled' + 'Disabled' +]) +param restrictOutboundNetworkAccess string = '' + +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 + +@description('Optional. The encryption protection configuration.') +param encryptionProtectorObj object = {} + +@description('Optional. The vulnerability assessment configuration.') +param vulnerabilityAssessmentsObj object = {} + +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-server.${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 server 'Microsoft.Sql/servers@2022-05-01-preview' = { + location: location + name: name + tags: tags + identity: identity + properties: { + administratorLogin: !empty(administratorLogin) ? administratorLogin : null + administratorLoginPassword: !empty(administratorLoginPassword) ? administratorLoginPassword : null + administrators: !empty(administrators) ? { + administratorType: 'ActiveDirectory' + azureADOnlyAuthentication: administrators.azureADOnlyAuthentication + login: administrators.login + principalType: administrators.principalType + sid: administrators.sid + tenantId: administrators.?tenantId ?? tenant().tenantId + } : null + version: '12.0' + minimalTlsVersion: minimalTlsVersion + primaryUserAssignedIdentityId: !empty(primaryUserAssignedIdentityId) ? primaryUserAssignedIdentityId : null + publicNetworkAccess: !empty(publicNetworkAccess) ? any(publicNetworkAccess) : (!empty(privateEndpoints) && empty(firewallRules) && empty(virtualNetworkRules) ? 'Disabled' : null) + restrictOutboundNetworkAccess: !empty(restrictOutboundNetworkAccess) ? restrictOutboundNetworkAccess : null + } +} + +resource server_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: server +} + +resource server_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(server.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: server +}] + +module server_databases 'database/main.bicep' = [for (database, index) in databases: { + name: '${uniqueString(deployment().name, location)}-Sql-DB-${index}' + params: { + name: database.name + serverName: server.name + skuTier: contains(database, 'skuTier') ? database.skuTier : 'GeneralPurpose' + skuName: contains(database, 'skuName') ? database.skuName : 'GP_Gen5_2' + skuCapacity: database.?skuCapacity + skuFamily: contains(database, 'skuFamily') ? database.skuFamily : '' + skuSize: contains(database, 'skuSize') ? database.skuSize : '' + collation: contains(database, 'collation') ? database.collation : 'SQL_Latin1_General_CP1_CI_AS' + maxSizeBytes: contains(database, 'maxSizeBytes') ? database.maxSizeBytes : 34359738368 + autoPauseDelay: contains(database, 'autoPauseDelay') ? database.autoPauseDelay : 0 + diagnosticSettings: database.?diagnosticSettings + isLedgerOn: contains(database, 'isLedgerOn') ? database.isLedgerOn : false + location: location + licenseType: contains(database, 'licenseType') ? database.licenseType : '' + maintenanceConfigurationId: contains(database, 'maintenanceConfigurationId') ? database.maintenanceConfigurationId : '' + minCapacity: contains(database, 'minCapacity') ? database.minCapacity : '' + highAvailabilityReplicaCount: contains(database, 'highAvailabilityReplicaCount') ? database.highAvailabilityReplicaCount : 0 + readScale: contains(database, 'readScale') ? database.readScale : 'Disabled' + requestedBackupStorageRedundancy: contains(database, 'requestedBackupStorageRedundancy') ? database.requestedBackupStorageRedundancy : '' + sampleName: contains(database, 'sampleName') ? database.sampleName : '' + tags: database.?tags ?? tags + zoneRedundant: contains(database, 'zoneRedundant') ? database.zoneRedundant : false + elasticPoolId: contains(database, 'elasticPoolId') ? database.elasticPoolId : '' + backupShortTermRetentionPolicy: contains(database, 'backupShortTermRetentionPolicy') ? database.backupShortTermRetentionPolicy : {} + backupLongTermRetentionPolicy: contains(database, 'backupLongTermRetentionPolicy') ? database.backupLongTermRetentionPolicy : {} + createMode: contains(database, 'createMode') ? database.createMode : 'Default' + sourceDatabaseResourceId: contains(database, 'sourceDatabaseResourceId') ? database.sourceDatabaseResourceId : '' + sourceDatabaseDeletionDate: contains(database, 'sourceDatabaseDeletionDate') ? database.sourceDatabaseDeletionDate : '' + recoveryServicesRecoveryPointResourceId: contains(database, 'recoveryServicesRecoveryPointResourceId') ? database.recoveryServicesRecoveryPointResourceId : '' + restorePointInTime: contains(database, 'restorePointInTime') ? database.restorePointInTime : '' + } + dependsOn: [ + server_elasticPools // Enables us to add databases to existing elastic pools + ] +}] + +module server_elasticPools 'elastic-pool/main.bicep' = [for (elasticPool, index) in elasticPools: { + name: '${uniqueString(deployment().name, location)}-SQLServer-ElasticPool-${index}' + params: { + name: elasticPool.name + serverName: server.name + databaseMaxCapacity: contains(elasticPool, 'databaseMaxCapacity') ? elasticPool.databaseMaxCapacity : 2 + databaseMinCapacity: contains(elasticPool, 'databaseMinCapacity') ? elasticPool.databaseMinCapacity : 0 + highAvailabilityReplicaCount: elasticPool.?highAvailabilityReplicaCount + licenseType: contains(elasticPool, 'licenseType') ? elasticPool.licenseType : 'LicenseIncluded' + maintenanceConfigurationId: contains(elasticPool, 'maintenanceConfigurationId') ? elasticPool.maintenanceConfigurationId : '' + maxSizeBytes: contains(elasticPool, 'maxSizeBytes') ? elasticPool.maxSizeBytes : 34359738368 + minCapacity: elasticPool.?minCapacity + skuCapacity: contains(elasticPool, 'skuCapacity') ? elasticPool.skuCapacity : 2 + skuName: contains(elasticPool, 'skuName') ? elasticPool.skuName : 'GP_Gen5' + skuTier: contains(elasticPool, 'skuTier') ? elasticPool.skuTier : 'GeneralPurpose' + zoneRedundant: contains(elasticPool, 'zoneRedundant') ? elasticPool.zoneRedundant : false + location: location + tags: elasticPool.?tags ?? tags + } +}] + +module server_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.2.1' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-server-PrivateEndpoint-${index}' + params: { + groupIds: [ + privateEndpoint.?service ?? 'sqlServer' + ] + name: privateEndpoint.?name ?? 'pep-${last(split(server.id, '/'))}-${privateEndpoint.?service ?? 'sqlServer'}-${index}' + serviceResourceId: server.id + subnetResourceId: privateEndpoint.subnetResourceId + location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location + lock: privateEndpoint.?lock ?? lock + privateDnsZoneGroupName: privateEndpoint.?privateDnsZoneGroupName + privateDnsZoneResourceIds: privateEndpoint.?privateDnsZoneResourceIds + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + enableTelemetry: enableTelemetry + } +}] + +module server_firewallRules 'firewall-rule/main.bicep' = [for (firewallRule, index) in firewallRules: { + name: '${uniqueString(deployment().name, location)}-Sql-FirewallRules-${index}' + params: { + name: firewallRule.name + serverName: server.name + endIpAddress: contains(firewallRule, 'endIpAddress') ? firewallRule.endIpAddress : '0.0.0.0' + startIpAddress: contains(firewallRule, 'startIpAddress') ? firewallRule.startIpAddress : '0.0.0.0' + } +}] + +module server_virtualNetworkRules 'virtual-network-rule/main.bicep' = [for (virtualNetworkRule, index) in virtualNetworkRules: { + name: '${uniqueString(deployment().name, location)}-Sql-VirtualNetworkRules-${index}' + params: { + name: virtualNetworkRule.name + serverName: server.name + ignoreMissingVnetServiceEndpoint: contains(virtualNetworkRule, 'ignoreMissingVnetServiceEndpoint') ? virtualNetworkRule.ignoreMissingVnetServiceEndpoint : false + virtualNetworkSubnetId: virtualNetworkRule.virtualNetworkSubnetId + } +}] + +module server_securityAlertPolicies 'security-alert-policy/main.bicep' = [for (securityAlertPolicy, index) in securityAlertPolicies: { + name: '${uniqueString(deployment().name, location)}-Sql-SecAlertPolicy-${index}' + params: { + name: securityAlertPolicy.name + serverName: server.name + disabledAlerts: contains(securityAlertPolicy, 'disabledAlerts') ? securityAlertPolicy.disabledAlerts : [] + emailAccountAdmins: contains(securityAlertPolicy, 'emailAccountAdmins') ? securityAlertPolicy.emailAccountAdmins : false + emailAddresses: contains(securityAlertPolicy, 'emailAddresses') ? securityAlertPolicy.emailAddresses : [] + retentionDays: contains(securityAlertPolicy, 'retentionDays') ? securityAlertPolicy.retentionDays : 0 + state: contains(securityAlertPolicy, 'state') ? securityAlertPolicy.state : 'Disabled' + storageAccountAccessKey: contains(securityAlertPolicy, 'storageAccountAccessKey') ? securityAlertPolicy.storageAccountAccessKey : '' + storageEndpoint: contains(securityAlertPolicy, 'storageEndpoint') ? securityAlertPolicy.storageEndpoint : '' + } +}] + +module server_vulnerabilityAssessment 'vulnerability-assessment/main.bicep' = if (!empty(vulnerabilityAssessmentsObj)) { + name: '${uniqueString(deployment().name, location)}-Sql-VulnAssessm' + params: { + serverName: server.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: [ + server_securityAlertPolicies + ] +} + +module server_keys 'key/main.bicep' = [for (key, index) in keys: { + name: '${uniqueString(deployment().name, location)}-Sql-Key-${index}' + params: { + name: key.?name + serverName: server.name + serverKeyType: contains(key, 'serverKeyType') ? key.serverKeyType : 'ServiceManaged' + uri: contains(key, 'uri') ? key.uri : '' + } +}] + +module server_encryptionProtector 'encryption-protector/main.bicep' = if (!empty(encryptionProtectorObj)) { + name: '${uniqueString(deployment().name, location)}-Sql-EncryProtector' + params: { + sqlServerName: server.name + serverKeyName: encryptionProtectorObj.serverKeyName + serverKeyType: contains(encryptionProtectorObj, 'serverKeyType') ? encryptionProtectorObj.serverKeyType : 'ServiceManaged' + autoRotationEnabled: contains(encryptionProtectorObj, 'autoRotationEnabled') ? encryptionProtectorObj.autoRotationEnabled : true + } + dependsOn: [ + server_keys + ] +} + +@description('The name of the deployed SQL server.') +output name string = server.name + +@description('The resource ID of the deployed SQL server.') +output resourceId string = server.id + +@description('The resource group of the deployed SQL server.') +output resourceGroupName string = resourceGroup().name + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string = (managedIdentities.?systemAssigned ?? false) && contains(server.identity, 'principalId') ? server.identity.principalId : '' + +@description('The location the resource was deployed into.') +output location string = server.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 name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type privateEndpointType = { + @description('Optional. The name of the private endpoint.') + name: string? + + @description('Optional. The location to deploy the private endpoint to.') + location: string? + + @description('Optional. The service (sub-) type to deploy the private endpoint for. For example "vault" or "blob".') + service: string? + + @description('Required. Resource ID of the subnet where the endpoint needs to be created.') + subnetResourceId: string + + @description('Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided.') + privateDnsZoneGroupName: string? + + @description('Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones.') + privateDnsZoneResourceIds: string[]? + + @description('Optional. Custom DNS configurations.') + customDnsConfigs: { + @description('Required. Fqdn that resolves to private endpoint ip address.') + fqdn: string? + + @description('Required. A list of private ip addresses of the private endpoint.') + ipAddresses: string[] + }[]? + + @description('Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints.') + ipConfigurations: { + @description('Required. The name of the resource that is unique within a resource group.') + name: string + + @description('Required. Properties of private endpoint IP configurations.') + properties: { + @description('Required. The ID of a group obtained from the remote resource that this private endpoint should connect to.') + groupId: string + + @description('Required. The member name of a group obtained from the remote resource that this private endpoint should connect to.') + memberName: string + + @description('Required. A private ip address obtained from the private endpoint\'s subnet.') + privateIPAddress: string + } + }[]? + + @description('Optional. Application security groups in which the private endpoint IP configuration is included.') + applicationSecurityGroupResourceIds: string[]? + + @description('Optional. The custom name of the network interface attached to the private endpoint.') + customNetworkInterfaceName: string? + + @description('Optional. Specify the type of lock.') + lock: lockType + + @description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + roleAssignments: roleAssignmentType + + @description('Optional. Tags to be applied on all resources/resource groups in this deployment.') + tags: object? + + @description('Optional. Manual PrivateLink Service Connections.') + manualPrivateLinkServiceConnections: array? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? +}[]? diff --git a/avm/res/sql/server/main.json b/avm/res/sql/server/main.json new file mode 100644 index 0000000000..a89c9c28fe --- /dev/null +++ b/avm/res/sql/server/main.json @@ -0,0 +1,2968 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "12295213353677593935" + }, + "name": "Azure SQL Servers", + "description": "This module deploys an Azure SQL Server.", + "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 name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "administratorLogin": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The administrator username for the server. Required if no `administrators` object for AAD authentication is provided." + } + }, + "administratorLoginPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Conditional. The administrator login password. Required if no `administrators` object for AAD authentication is provided." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the server." + } + }, + "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." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "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." + } + }, + "databases": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The databases to create in the server." + } + }, + "elasticPools": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The Elastic Pools to create in the server." + } + }, + "firewallRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The firewall rules to create in the server." + } + }, + "virtualNetworkRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The virtual network rules to create in the server." + } + }, + "securityAlertPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The security alert policies to create in the server." + } + }, + "keys": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The keys to configure." + } + }, + "administrators": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Conditional. The Azure Active Directory (AAD) administrator authentication. Required if no `administratorLogin` & `administratorLoginPassword` is provided." + } + }, + "minimalTlsVersion": { + "type": "string", + "defaultValue": "1.2", + "allowedValues": [ + "1.0", + "1.1", + "1.2" + ], + "metadata": { + "description": "Optional. Minimal TLS version allowed." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and neither firewall rules nor virtual network rules are set." + } + }, + "restrictOutboundNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not to restrict outbound network access for this server." + } + }, + "encryptionProtectorObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The encryption protection configuration." + } + }, + "vulnerabilityAssessmentsObj": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The vulnerability assessment configuration." + } + } + }, + "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-server.{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" + } + } + } + } + }, + "server": { + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "administratorLogin": "[if(not(empty(parameters('administratorLogin'))), parameters('administratorLogin'), null())]", + "administratorLoginPassword": "[if(not(empty(parameters('administratorLoginPassword'))), parameters('administratorLoginPassword'), null())]", + "administrators": "[if(not(empty(parameters('administrators'))), createObject('administratorType', 'ActiveDirectory', 'azureADOnlyAuthentication', parameters('administrators').azureADOnlyAuthentication, 'login', parameters('administrators').login, 'principalType', parameters('administrators').principalType, 'sid', parameters('administrators').sid, 'tenantId', coalesce(tryGet(parameters('administrators'), 'tenantId'), tenant().tenantId)), null())]", + "version": "12.0", + "minimalTlsVersion": "[parameters('minimalTlsVersion')]", + "primaryUserAssignedIdentityId": "[if(not(empty(parameters('primaryUserAssignedIdentityId'))), parameters('primaryUserAssignedIdentityId'), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(and(not(empty(parameters('privateEndpoints'))), empty(parameters('firewallRules'))), empty(parameters('virtualNetworkRules'))), 'Disabled', null()))]", + "restrictOutboundNetworkAccess": "[if(not(empty(parameters('restrictOutboundNetworkAccess'))), parameters('restrictOutboundNetworkAccess'), null())]" + } + }, + "server_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/servers/{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": [ + "server" + ] + }, + "server_roleAssignments": { + "copy": { + "name": "server_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Sql/servers/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Sql/servers', 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], 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": [ + "server" + ] + }, + "server_databases": { + "copy": { + "name": "server_databases", + "count": "[length(parameters('databases'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-DB-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('databases')[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "skuTier": "[if(contains(parameters('databases')[copyIndex()], 'skuTier'), createObject('value', parameters('databases')[copyIndex()].skuTier), createObject('value', 'GeneralPurpose'))]", + "skuName": "[if(contains(parameters('databases')[copyIndex()], 'skuName'), createObject('value', parameters('databases')[copyIndex()].skuName), createObject('value', 'GP_Gen5_2'))]", + "skuCapacity": { + "value": "[tryGet(parameters('databases')[copyIndex()], 'skuCapacity')]" + }, + "skuFamily": "[if(contains(parameters('databases')[copyIndex()], 'skuFamily'), createObject('value', parameters('databases')[copyIndex()].skuFamily), createObject('value', ''))]", + "skuSize": "[if(contains(parameters('databases')[copyIndex()], 'skuSize'), createObject('value', parameters('databases')[copyIndex()].skuSize), createObject('value', ''))]", + "collation": "[if(contains(parameters('databases')[copyIndex()], 'collation'), createObject('value', parameters('databases')[copyIndex()].collation), createObject('value', 'SQL_Latin1_General_CP1_CI_AS'))]", + "maxSizeBytes": "[if(contains(parameters('databases')[copyIndex()], 'maxSizeBytes'), createObject('value', parameters('databases')[copyIndex()].maxSizeBytes), createObject('value', json('34359738368')))]", + "autoPauseDelay": "[if(contains(parameters('databases')[copyIndex()], 'autoPauseDelay'), createObject('value', parameters('databases')[copyIndex()].autoPauseDelay), createObject('value', 0))]", + "diagnosticSettings": { + "value": "[tryGet(parameters('databases')[copyIndex()], 'diagnosticSettings')]" + }, + "isLedgerOn": "[if(contains(parameters('databases')[copyIndex()], 'isLedgerOn'), createObject('value', parameters('databases')[copyIndex()].isLedgerOn), createObject('value', false()))]", + "location": { + "value": "[parameters('location')]" + }, + "licenseType": "[if(contains(parameters('databases')[copyIndex()], 'licenseType'), createObject('value', parameters('databases')[copyIndex()].licenseType), createObject('value', ''))]", + "maintenanceConfigurationId": "[if(contains(parameters('databases')[copyIndex()], 'maintenanceConfigurationId'), createObject('value', parameters('databases')[copyIndex()].maintenanceConfigurationId), createObject('value', ''))]", + "minCapacity": "[if(contains(parameters('databases')[copyIndex()], 'minCapacity'), createObject('value', parameters('databases')[copyIndex()].minCapacity), createObject('value', ''))]", + "highAvailabilityReplicaCount": "[if(contains(parameters('databases')[copyIndex()], 'highAvailabilityReplicaCount'), createObject('value', parameters('databases')[copyIndex()].highAvailabilityReplicaCount), createObject('value', 0))]", + "readScale": "[if(contains(parameters('databases')[copyIndex()], 'readScale'), createObject('value', parameters('databases')[copyIndex()].readScale), createObject('value', 'Disabled'))]", + "requestedBackupStorageRedundancy": "[if(contains(parameters('databases')[copyIndex()], 'requestedBackupStorageRedundancy'), createObject('value', parameters('databases')[copyIndex()].requestedBackupStorageRedundancy), createObject('value', ''))]", + "sampleName": "[if(contains(parameters('databases')[copyIndex()], 'sampleName'), createObject('value', parameters('databases')[copyIndex()].sampleName), createObject('value', ''))]", + "tags": { + "value": "[coalesce(tryGet(parameters('databases')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "zoneRedundant": "[if(contains(parameters('databases')[copyIndex()], 'zoneRedundant'), createObject('value', parameters('databases')[copyIndex()].zoneRedundant), createObject('value', false()))]", + "elasticPoolId": "[if(contains(parameters('databases')[copyIndex()], 'elasticPoolId'), createObject('value', parameters('databases')[copyIndex()].elasticPoolId), createObject('value', ''))]", + "backupShortTermRetentionPolicy": "[if(contains(parameters('databases')[copyIndex()], 'backupShortTermRetentionPolicy'), createObject('value', parameters('databases')[copyIndex()].backupShortTermRetentionPolicy), createObject('value', createObject()))]", + "backupLongTermRetentionPolicy": "[if(contains(parameters('databases')[copyIndex()], 'backupLongTermRetentionPolicy'), createObject('value', parameters('databases')[copyIndex()].backupLongTermRetentionPolicy), createObject('value', createObject()))]", + "createMode": "[if(contains(parameters('databases')[copyIndex()], 'createMode'), createObject('value', parameters('databases')[copyIndex()].createMode), createObject('value', 'Default'))]", + "sourceDatabaseResourceId": "[if(contains(parameters('databases')[copyIndex()], 'sourceDatabaseResourceId'), createObject('value', parameters('databases')[copyIndex()].sourceDatabaseResourceId), createObject('value', ''))]", + "sourceDatabaseDeletionDate": "[if(contains(parameters('databases')[copyIndex()], 'sourceDatabaseDeletionDate'), createObject('value', parameters('databases')[copyIndex()].sourceDatabaseDeletionDate), createObject('value', ''))]", + "recoveryServicesRecoveryPointResourceId": "[if(contains(parameters('databases')[copyIndex()], 'recoveryServicesRecoveryPointResourceId'), createObject('value', parameters('databases')[copyIndex()].recoveryServicesRecoveryPointResourceId), createObject('value', ''))]", + "restorePointInTime": "[if(contains(parameters('databases')[copyIndex()], 'restorePointInTime'), createObject('value', parameters('databases')[copyIndex()].restorePointInTime), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "16643582358589893317" + }, + "name": "SQL Server Database", + "description": "This module deploys an Azure SQL Server Database.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "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." + } + }, + "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." + } + } + } + }, + "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 database." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. The collation of the database." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "GeneralPurpose", + "metadata": { + "description": "Optional. The skuTier or edition of the particular SKU." + } + }, + "skuName": { + "type": "string", + "defaultValue": "GP_Gen5_2", + "metadata": { + "description": "Optional. The name of the SKU." + } + }, + "skuCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Capacity of the particular SKU." + } + }, + "preferredEnclaveType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Default", + "VBS" + ], + "metadata": { + "description": "Optional. Type of enclave requested on the database i.e. Default or VBS enclaves." + } + }, + "skuFamily": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "skuSize": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The max size of the database expressed in bytes." + } + }, + "sampleName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the sample schema to apply when creating this database." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this database is zone redundant." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The license type to apply for this database." + } + }, + "readScale": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The state of read-only routing." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The number of readonly secondary replicas associated with the database." + } + }, + "minCapacity": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Minimal capacity that database will always have allocated." + } + }, + "autoPauseDelay": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "elasticPoolId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the elastic pool containing this database." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "createMode": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "Copy", + "OnlineSecondary", + "PointInTimeRestore", + "Recovery", + "Restore", + "RestoreLongTermRetentionBackup", + "Secondary" + ], + "metadata": { + "description": "Optional. Specifies the mode of database creation." + } + }, + "sourceDatabaseResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of database if createMode set to Copy, Secondary, PointInTimeRestore, Recovery or Restore." + } + }, + "sourceDatabaseDeletionDate": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The time that the database was deleted when restoring a deleted database." + } + }, + "recoveryServicesRecoveryPointResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of backup if createMode set to RestoreLongTermRetentionBackup." + } + }, + "restorePointInTime": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore." + } + }, + "requestedBackupStorageRedundancy": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Geo", + "Local", + "Zone", + "" + ], + "metadata": { + "description": "Optional. The storage account type to be used to store backups for this database." + } + }, + "isLedgerOn": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur." + } + }, + "backupShortTermRetentionPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The short term backup retention policy to create for the database." + } + }, + "backupLongTermRetentionPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The long term backup retention policy to create for the database." + } + } + }, + "variables": { + "skuVar": "[union(createObject('name', parameters('skuName'), 'tier', parameters('skuTier')), if(not(equals(parameters('skuCapacity'), null())), createObject('capacity', parameters('skuCapacity')), if(not(empty(parameters('skuFamily'))), createObject('family', parameters('skuFamily')), if(not(empty(parameters('skuSize'))), createObject('size', parameters('skuSize')), createObject()))))]" + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "database": { + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "preferredEnclaveType": "[if(not(empty(parameters('preferredEnclaveType'))), parameters('preferredEnclaveType'), null())]", + "collation": "[parameters('collation')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "sampleName": "[parameters('sampleName')]", + "zoneRedundant": "[parameters('zoneRedundant')]", + "licenseType": "[parameters('licenseType')]", + "readScale": "[parameters('readScale')]", + "minCapacity": "[if(not(empty(parameters('minCapacity'))), json(parameters('minCapacity')), 0)]", + "autoPauseDelay": "[parameters('autoPauseDelay')]", + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "requestedBackupStorageRedundancy": "[parameters('requestedBackupStorageRedundancy')]", + "isLedgerOn": "[parameters('isLedgerOn')]", + "maintenanceConfigurationId": "[if(not(empty(parameters('maintenanceConfigurationId'))), parameters('maintenanceConfigurationId'), null())]", + "elasticPoolId": "[parameters('elasticPoolId')]", + "createMode": "[parameters('createMode')]", + "sourceDatabaseId": "[if(not(empty(parameters('sourceDatabaseResourceId'))), parameters('sourceDatabaseResourceId'), null())]", + "sourceDatabaseDeletionDate": "[if(not(empty(parameters('sourceDatabaseDeletionDate'))), parameters('sourceDatabaseDeletionDate'), null())]", + "recoveryServicesRecoveryPointId": "[if(not(empty(parameters('recoveryServicesRecoveryPointResourceId'))), parameters('recoveryServicesRecoveryPointResourceId'), null())]", + "restorePointInTime": "[if(not(empty(parameters('restorePointInTime'))), parameters('restorePointInTime'), null())]" + }, + "sku": "[variables('skuVar')]", + "dependsOn": [ + "server" + ] + }, + "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/servers/{0}/databases/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "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')]", + "metrics": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics', 'timeGrain', null(), 'enabled', true())))]", + "logs": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'AllLogs', 'enabled', true())))]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "database" + ] + }, + "database_backupShortTermRetentionPolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-shBakRetPol', uniqueString(deployment().name, parameters('location')), parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "diffBackupIntervalInHours": "[if(contains(parameters('backupShortTermRetentionPolicy'), 'diffBackupIntervalInHours'), createObject('value', parameters('backupShortTermRetentionPolicy').diffBackupIntervalInHours), createObject('value', 24))]", + "retentionDays": "[if(contains(parameters('backupShortTermRetentionPolicy'), 'retentionDays'), createObject('value', parameters('backupShortTermRetentionPolicy').retentionDays), createObject('value', 7))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18285215987066720007" + }, + "name": "Azure SQL Server Database Short Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "diffBackupIntervalInHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Optional. Differential backup interval in hours." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 7, + "metadata": { + "description": "Optional. Poin-in-time retention in days." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "diffBackupIntervalInHours": "[parameters('diffBackupIntervalInHours')]", + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the short-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the short-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the short-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "dependsOn": [ + "database" + ] + }, + "database_backupLongTermRetentionPolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-lgBakRetPol', uniqueString(deployment().name, parameters('location')), parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "weeklyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'weeklyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').weeklyRetention), createObject('value', ''))]", + "monthlyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'monthlyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').monthlyRetention), createObject('value', ''))]", + "yearlyRetention": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'yearlyRetention'), createObject('value', parameters('backupLongTermRetentionPolicy').yearlyRetention), createObject('value', ''))]", + "weekOfYear": "[if(contains(parameters('backupLongTermRetentionPolicy'), 'weekOfYear'), createObject('value', parameters('backupLongTermRetentionPolicy').weekOfYear), createObject('value', 1))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4846793430941310733" + }, + "name": "SQL Server Database Long Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "weeklyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "monthlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the long-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the long-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the long-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "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/servers/databases', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('database', '2022-05-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "server", + "server_elasticPools" + ] + }, + "server_elasticPools": { + "copy": { + "name": "server_elasticPools", + "count": "[length(parameters('elasticPools'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SQLServer-ElasticPool-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('elasticPools')[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "databaseMaxCapacity": "[if(contains(parameters('elasticPools')[copyIndex()], 'databaseMaxCapacity'), createObject('value', parameters('elasticPools')[copyIndex()].databaseMaxCapacity), createObject('value', 2))]", + "databaseMinCapacity": "[if(contains(parameters('elasticPools')[copyIndex()], 'databaseMinCapacity'), createObject('value', parameters('elasticPools')[copyIndex()].databaseMinCapacity), createObject('value', 0))]", + "highAvailabilityReplicaCount": { + "value": "[tryGet(parameters('elasticPools')[copyIndex()], 'highAvailabilityReplicaCount')]" + }, + "licenseType": "[if(contains(parameters('elasticPools')[copyIndex()], 'licenseType'), createObject('value', parameters('elasticPools')[copyIndex()].licenseType), createObject('value', 'LicenseIncluded'))]", + "maintenanceConfigurationId": "[if(contains(parameters('elasticPools')[copyIndex()], 'maintenanceConfigurationId'), createObject('value', parameters('elasticPools')[copyIndex()].maintenanceConfigurationId), createObject('value', ''))]", + "maxSizeBytes": "[if(contains(parameters('elasticPools')[copyIndex()], 'maxSizeBytes'), createObject('value', parameters('elasticPools')[copyIndex()].maxSizeBytes), createObject('value', json('34359738368')))]", + "minCapacity": { + "value": "[tryGet(parameters('elasticPools')[copyIndex()], 'minCapacity')]" + }, + "skuCapacity": "[if(contains(parameters('elasticPools')[copyIndex()], 'skuCapacity'), createObject('value', parameters('elasticPools')[copyIndex()].skuCapacity), createObject('value', 2))]", + "skuName": "[if(contains(parameters('elasticPools')[copyIndex()], 'skuName'), createObject('value', parameters('elasticPools')[copyIndex()].skuName), createObject('value', 'GP_Gen5'))]", + "skuTier": "[if(contains(parameters('elasticPools')[copyIndex()], 'skuTier'), createObject('value', parameters('elasticPools')[copyIndex()].skuTier), createObject('value', 'GeneralPurpose'))]", + "zoneRedundant": "[if(contains(parameters('elasticPools')[copyIndex()], 'zoneRedundant'), createObject('value', parameters('elasticPools')[copyIndex()].zoneRedundant), createObject('value', false()))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('elasticPools')[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "3978881342800176671" + }, + "name": "SQL Server Elastic Pool", + "description": "This module deploys an Azure SQL Server Elastic Pool.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Elastic Pool." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "skuCapacity": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. Capacity of the particular SKU." + } + }, + "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." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "LicenseIncluded", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "metadata": { + "description": "Optional. The license type to apply for this elastic pool." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The storage limit for the database elastic pool in bytes." + } + }, + "minCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Minimal capacity that serverless pool will not shrink below, if not paused." + } + }, + "databaseMaxCapacity": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. The maximum capacity any one database can consume." + } + }, + "databaseMinCapacity": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The minimum capacity all databases are guaranteed." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones." + } + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "elasticPool": { + "type": "Microsoft.Sql/servers/elasticPools", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "capacity": "[parameters('skuCapacity')]", + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]" + }, + "properties": { + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "licenseType": "[parameters('licenseType')]", + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationId')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "minCapacity": "[parameters('minCapacity')]", + "perDatabaseSettings": { + "minCapacity": "[parameters('databaseMinCapacity')]", + "maxCapacity": "[parameters('databaseMaxCapacity')]" + }, + "zoneRedundant": "[parameters('zoneRedundant')]" + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed Elastic Pool." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed Elastic Pool." + }, + "value": "[resourceId('Microsoft.Sql/servers/elasticPools', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed Elastic Pool." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('elasticPool', '2022-05-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_privateEndpoints": { + "copy": { + "name": "server_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-server-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "groupIds": { + "value": [ + "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer')]" + ] + }, + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Sql/servers', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer'), copyIndex()))]" + }, + "serviceResourceId": { + "value": "[resourceId('Microsoft.Sql/servers', parameters('name'))]" + }, + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "manualPrivateLinkServiceConnections": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections')]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "13477311172998188302" + }, + "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 name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "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 + }, + "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." + } + }, + "serviceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource that needs to be connected to the network." + } + }, + "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." + } + }, + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. Subtype(s) of the connection to be created. The allowed values depend on the type serviceResourceId refers to." + } + }, + "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 assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "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": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "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.2.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": [ + { + "name": "[parameters('name')]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceId')]", + "groupIds": "[parameters('groupIds')]" + } + } + ], + "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], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18168683629401652671" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_firewallRules": { + "copy": { + "name": "server_firewallRules", + "count": "[length(parameters('firewallRules'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-FirewallRules-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('firewallRules')[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "endIpAddress": "[if(contains(parameters('firewallRules')[copyIndex()], 'endIpAddress'), createObject('value', parameters('firewallRules')[copyIndex()].endIpAddress), createObject('value', '0.0.0.0'))]", + "startIpAddress": "[if(contains(parameters('firewallRules')[copyIndex()], 'startIpAddress'), createObject('value', parameters('firewallRules')[copyIndex()].startIpAddress), createObject('value', '0.0.0.0'))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "3897281461367168361" + }, + "name": "Azure SQL Server Firewall Rule", + "description": "This module deploys an Azure SQL Server Firewall Rule.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Firewall Rule." + } + }, + "endIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "startIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/firewallRules", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "endIpAddress": "[parameters('endIpAddress')]", + "startIpAddress": "[parameters('startIpAddress')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed firewall rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed firewall rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/firewallRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed firewall rule." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_virtualNetworkRules": { + "copy": { + "name": "server_virtualNetworkRules", + "count": "[length(parameters('virtualNetworkRules'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-VirtualNetworkRules-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('virtualNetworkRules')[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "ignoreMissingVnetServiceEndpoint": "[if(contains(parameters('virtualNetworkRules')[copyIndex()], 'ignoreMissingVnetServiceEndpoint'), createObject('value', parameters('virtualNetworkRules')[copyIndex()].ignoreMissingVnetServiceEndpoint), createObject('value', false()))]", + "virtualNetworkSubnetId": { + "value": "[parameters('virtualNetworkRules')[copyIndex()].virtualNetworkSubnetId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4900264168794496336" + }, + "name": "Azure SQL Server Virtual Network Rules", + "description": "This module deploys an Azure SQL Server Virtual Network Rule.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Virtual Network Rule." + } + }, + "ignoreMissingVnetServiceEndpoint": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Allow creating a firewall rule before the virtual network has vnet service endpoint enabled." + } + }, + "virtualNetworkSubnetId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the virtual network subnet." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/virtualNetworkRules", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "ignoreMissingVnetServiceEndpoint": "[parameters('ignoreMissingVnetServiceEndpoint')]", + "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed virtual network rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed virtual network rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/virtualNetworkRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed virtual network rule." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_securityAlertPolicies": { + "copy": { + "name": "server_securityAlertPolicies", + "count": "[length(parameters('securityAlertPolicies'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-SecAlertPolicy-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('securityAlertPolicies')[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "disabledAlerts": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'disabledAlerts'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].disabledAlerts), createObject('value', createArray()))]", + "emailAccountAdmins": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'emailAccountAdmins'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].emailAccountAdmins), createObject('value', false()))]", + "emailAddresses": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'emailAddresses'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].emailAddresses), createObject('value', createArray()))]", + "retentionDays": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'retentionDays'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].retentionDays), createObject('value', 0))]", + "state": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'state'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].state), createObject('value', 'Disabled'))]", + "storageAccountAccessKey": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'storageAccountAccessKey'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].storageAccountAccessKey), createObject('value', ''))]", + "storageEndpoint": "[if(contains(parameters('securityAlertPolicies')[copyIndex()], 'storageEndpoint'), createObject('value', parameters('securityAlertPolicies')[copyIndex()].storageEndpoint), 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.23.1.45101", + "templateHash": "14514097923633572625" + }, + "name": "Azure SQL Server Security Alert Policies", + "description": "This module deploys an Azure SQL Server Security Alert Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Security Alert Policy." + } + }, + "disabledAlerts": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of alerts that are disabled. Allowed values are: Sql_Injection, Sql_Injection_Vulnerability, Access_Anomaly, Data_Exfiltration, Unsafe_Action, Brute_Force." + } + }, + "emailAccountAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the alert is sent to the account administrators." + } + }, + "emailAddresses": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the alert is sent." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the Threat Detection audit logs." + } + }, + "state": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database." + } + }, + "storageAccountAccessKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the identifier key of the Threat Detection audit storage account.." + } + }, + "storageEndpoint": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/securityAlertPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "disabledAlerts": "[parameters('disabledAlerts')]", + "emailAccountAdmins": "[parameters('emailAccountAdmins')]", + "emailAddresses": "[parameters('emailAddresses')]", + "retentionDays": "[parameters('retentionDays')]", + "state": "[parameters('state')]", + "storageAccountAccessKey": "[if(empty(parameters('storageAccountAccessKey')), null(), parameters('storageAccountAccessKey'))]", + "storageEndpoint": "[if(empty(parameters('storageEndpoint')), null(), parameters('storageEndpoint'))]" + } + } + ], + "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/servers/securityAlertPolicies', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed security alert policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_vulnerabilityAssessment": { + "condition": "[not(empty(parameters('vulnerabilityAssessmentsObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-VulnAssessm', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "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.23.1.45101", + "templateHash": "3944798211825370066" + }, + "name": "Azure SQL Server Vulnerability Assessments", + "description": "This module deploys an Azure SQL Server Vulnerability Assessment.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The Name of SQL Server. 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 Server 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/servers/vulnerabilityAssessments", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), 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('serverName'))]", + "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedInstanceIdentityPrincipalId": { + "value": "[reference(resourceId('Microsoft.Sql/servers', parameters('serverName')), '2022-05-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.23.1.45101", + "templateHash": "11127995627829971090" + } + }, + "parameters": { + "storageAccountName": { + "type": "string" + }, + "managedInstanceIdentityPrincipalId": { + "type": "string" + } + }, + "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/servers/vulnerabilityAssessments', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed vulnerability assessment." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server", + "server_securityAlertPolicies" + ] + }, + "server_keys": { + "copy": { + "name": "server_keys", + "count": "[length(parameters('keys'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[tryGet(parameters('keys')[copyIndex()], 'name')]" + }, + "serverName": { + "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#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "2292967663993625779" + }, + "name": "Azure SQL Server Keys", + "description": "This module deploys an Azure SQL Server Key.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the key. Must follow the [__] pattern." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL server. 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": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2022-05-01-preview", + "name": "[parameters('serverName')]" + }, + "key": { + "type": "Microsoft.Sql/servers/keys", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed server key." + }, + "value": "[coalesce(parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed server key." + }, + "value": "[resourceId('Microsoft.Sql/servers/keys', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed server key." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_encryptionProtector": { + "condition": "[not(empty(parameters('encryptionProtectorObj')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-EncryProtector', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "sqlServerName": { + "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.23.1.45101", + "templateHash": "4659476195817177643" + }, + "name": "Azure SQL Server Encryption Protector", + "description": "This module deploys an Azure SQL Server Encryption Protector.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "sqlServerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the sql server. Required if the template is used in a standalone deployment." + } + }, + "serverKeyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the server key." + } + }, + "autoRotationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Key auto rotation opt-in." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/encryptionProtector", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('sqlServerName'), 'current')]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "autoRotationEnabled": "[parameters('autoRotationEnabled')]", + "serverKeyName": "[parameters('serverKeyName')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed encryption protector." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the encryption protector." + }, + "value": "[resourceId('Microsoft.Sql/servers/encryptionProtector', parameters('sqlServerName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed encryption protector." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server", + "server_keys" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed SQL server." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed SQL server." + }, + "value": "[resourceId('Microsoft.Sql/servers', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed SQL server." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[if(and(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), contains(reference('server', '2022-05-01-preview', 'full').identity, 'principalId')), reference('server', '2022-05-01-preview', 'full').identity.principalId, '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('server', '2022-05-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/security-alert-policy/README.md b/avm/res/sql/server/security-alert-policy/README.md new file mode 100644 index 0000000000..2d924f95b0 --- /dev/null +++ b/avm/res/sql/server/security-alert-policy/README.md @@ -0,0 +1,123 @@ +# Azure SQL Server Security Alert Policies `[Microsoft.Sql/servers/securityAlertPolicies]` + +This module deploys an Azure SQL Server Security Alert Policy. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/securityAlertPolicies` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/securityAlertPolicies) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Security Alert Policy. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`disabledAlerts`](#parameter-disabledalerts) | array | Specifies an array of alerts that are disabled. Allowed values are: Sql_Injection, Sql_Injection_Vulnerability, Access_Anomaly, Data_Exfiltration, Unsafe_Action, Brute_Force. | +| [`emailAccountAdmins`](#parameter-emailaccountadmins) | bool | Specifies that the alert is sent to the account administrators. | +| [`emailAddresses`](#parameter-emailaddresses) | array | Specifies an array of email addresses to which the alert is sent. | +| [`retentionDays`](#parameter-retentiondays) | int | Specifies the number of days to keep in the Threat Detection audit logs. | +| [`state`](#parameter-state) | string | Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database. | +| [`storageAccountAccessKey`](#parameter-storageaccountaccesskey) | securestring | Specifies the identifier key of the Threat Detection audit storage account.. | +| [`storageEndpoint`](#parameter-storageendpoint) | string | Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs. | + +### Parameter: `disabledAlerts` + +Specifies an array of alerts that are disabled. Allowed values are: Sql_Injection, Sql_Injection_Vulnerability, Access_Anomaly, Data_Exfiltration, Unsafe_Action, Brute_Force. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `emailAccountAdmins` + +Specifies that the alert is sent to the account administrators. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `emailAddresses` + +Specifies an array of email addresses to which the alert is sent. +- Required: No +- Type: array +- Default: `[]` + +### Parameter: `name` + +The name of the Security Alert Policy. +- Required: Yes +- Type: string + +### Parameter: `retentionDays` + +Specifies the number of days to keep in the Threat Detection audit logs. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `serverName` + +The name of the parent SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `state` + +Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database. +- Required: No +- Type: string +- Default: `'Disabled'` +- Allowed: + ```Bicep + [ + 'Disabled' + 'Enabled' + ] + ``` + +### Parameter: `storageAccountAccessKey` + +Specifies the identifier key of the Threat Detection audit storage account.. +- Required: No +- Type: securestring +- Default: `''` + +### Parameter: `storageEndpoint` + +Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs. +- Required: No +- Type: string +- Default: `''` + + +## 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_ diff --git a/avm/res/sql/server/security-alert-policy/main.bicep b/avm/res/sql/server/security-alert-policy/main.bicep new file mode 100644 index 0000000000..5629e61597 --- /dev/null +++ b/avm/res/sql/server/security-alert-policy/main.bicep @@ -0,0 +1,62 @@ +metadata name = 'Azure SQL Server Security Alert Policies' +metadata description = 'This module deploys an Azure SQL Server Security Alert Policy.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Security Alert Policy.') +param name string + +@description('Optional. Specifies an array of alerts that are disabled. Allowed values are: Sql_Injection, Sql_Injection_Vulnerability, Access_Anomaly, Data_Exfiltration, Unsafe_Action, Brute_Force.') +param disabledAlerts array = [] + +@description('Optional. Specifies that the alert is sent to the account administrators.') +param emailAccountAdmins bool = false + +@description('Optional. Specifies an array of email addresses to which the alert is sent.') +param emailAddresses array = [] + +@description('Optional. Specifies the number of days to keep in the Threat Detection audit logs.') +param retentionDays int = 0 + +@description('Optional. Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database.') +@allowed([ + 'Disabled' + 'Enabled' +]) +param state string = 'Disabled' + +@description('Optional. Specifies the identifier key of the Threat Detection audit storage account..') +@secure() +param storageAccountAccessKey string = '' + +@description('Optional. Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs.') +param storageEndpoint string = '' + +@description('Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment.') +param serverName string + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource securityAlertPolicy 'Microsoft.Sql/servers/securityAlertPolicies@2022-05-01-preview' = { + name: name + parent: server + properties: { + disabledAlerts: disabledAlerts + emailAccountAdmins: emailAccountAdmins + emailAddresses: emailAddresses + retentionDays: retentionDays + state: state + storageAccountAccessKey: empty(storageAccountAccessKey) ? null : storageAccountAccessKey + storageEndpoint: empty(storageEndpoint) ? null : storageEndpoint + } +} + +@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/server/security-alert-policy/main.json b/avm/res/sql/server/security-alert-policy/main.json new file mode 100644 index 0000000000..bdefa1dcfc --- /dev/null +++ b/avm/res/sql/server/security-alert-policy/main.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "14514097923633572625" + }, + "name": "Azure SQL Server Security Alert Policies", + "description": "This module deploys an Azure SQL Server Security Alert Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Security Alert Policy." + } + }, + "disabledAlerts": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of alerts that are disabled. Allowed values are: Sql_Injection, Sql_Injection_Vulnerability, Access_Anomaly, Data_Exfiltration, Unsafe_Action, Brute_Force." + } + }, + "emailAccountAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the alert is sent to the account administrators." + } + }, + "emailAddresses": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the alert is sent." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the Threat Detection audit logs." + } + }, + "state": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database." + } + }, + "storageAccountAccessKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the identifier key of the Threat Detection audit storage account.." + } + }, + "storageEndpoint": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/securityAlertPolicies", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "disabledAlerts": "[parameters('disabledAlerts')]", + "emailAccountAdmins": "[parameters('emailAccountAdmins')]", + "emailAddresses": "[parameters('emailAddresses')]", + "retentionDays": "[parameters('retentionDays')]", + "state": "[parameters('state')]", + "storageAccountAccessKey": "[if(empty(parameters('storageAccountAccessKey')), null(), parameters('storageAccountAccessKey'))]", + "storageEndpoint": "[if(empty(parameters('storageEndpoint')), null(), parameters('storageEndpoint'))]" + } + } + ], + "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/servers/securityAlertPolicies', parameters('serverName'), 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/server/tests/e2e/admin/dependencies.bicep b/avm/res/sql/server/tests/e2e/admin/dependencies.bicep new file mode 100644 index 0000000000..29b9641692 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/admin/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/res/sql/server/tests/e2e/admin/main.test.bicep b/avm/res/sql/server/tests/e2e/admin/main.test.bicep new file mode 100644 index 0000000000..64e245ecca --- /dev/null +++ b/avm/res/sql/server/tests/e2e/admin/main.test.bicep @@ -0,0 +1,60 @@ +targetScope = 'subscription' + +metadata name = 'With an administrator' +metadata description = 'This instance deploys the module with a Microsoft Entra ID identity as SQL administrator.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-sql.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlsadmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}' + location: location + administrators: { + azureADOnlyAuthentication: true + login: 'myspn' + sid: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'Application' + } + } +} diff --git a/avm/res/sql/server/tests/e2e/defaults/main.test.bicep b/avm/res/sql/server/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..e0805dbc90 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,54 @@ +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}-sql.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +// 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 = 'ssmin' + +@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. 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: location +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + administratorLogin: 'adminUserName' + administratorLoginPassword: password + } +}] diff --git a/avm/res/sql/server/tests/e2e/max/dependencies.bicep b/avm/res/sql/server/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..5f68856202 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/max/dependencies.bicep @@ -0,0 +1,111 @@ +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@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 + +var addressPrefix = '10.0.0.0/16' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: map(range(0, 2), i => { + name: 'subnet-${i}' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, i) + } + }) + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink${environment().suffixes.sqlServerHostname}' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +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 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-Vault-Crypto-Service-Encryption-User-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 principal ID of the created managed identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created managed identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created virtual network subnet for a Private Endpoint.') +output privateEndpointSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created virtual network subnet for a Service Endpoint.') +output serviceEndpointSubnetResourceId string = virtualNetwork.properties.subnets[1].id + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.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/server/tests/e2e/max/main.test.bicep b/avm/res/sql/server/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..49194820d3 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/max/main.test.bicep @@ -0,0 +1,193 @@ +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.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlsmax' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}azsa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + administratorLogin: 'adminUserName' + administratorLoginPassword: password + location: location + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + vulnerabilityAssessmentsObj: { + name: 'default' + emailSubscriptionAdmins: true + recurringScansIsEnabled: true + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + } + elasticPools: [ + { + name: '${namePrefix}-${serviceShort}-ep-001' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + skuCapacity: 10 + // Pre-existing 'public' configuration + maintenanceConfigurationId: '${subscription().id}/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_${location}_DB_1' + } + ] + databases: [ + { + name: '${namePrefix}-${serviceShort}db-001' + collation: 'SQL_Latin1_General_CP1_CI_AS' + skuTier: 'GeneralPurpose' + skuName: 'ElasticPool' + capacity: 0 + maxSizeBytes: 34359738368 + licenseType: 'LicenseIncluded' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + elasticPoolId: '${resourceGroup.id}/providers/Microsoft.Sql/servers/${namePrefix}-${serviceShort}/elasticPools/${namePrefix}-${serviceShort}-ep-001' + encryptionProtectorObj: { + serverKeyType: 'AzureKeyVault' + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + } + ] + firewallRules: [ + { + name: 'AllowAllWindowsAzureIps' + endIpAddress: '0.0.0.0' + startIpAddress: '0.0.0.0' + } + ] + securityAlertPolicies: [ + { + name: 'Default' + state: 'Enabled' + emailAccountAdmins: true + } + ] + keys: [ + { + name: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + privateEndpoints: [ + { + subnetResourceId: nestedDependencies.outputs.privateEndpointSubnetResourceId + service: 'sqlServer' + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + virtualNetworkRules: [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetId: nestedDependencies.outputs.serviceEndpointSubnetResourceId + } + ] + restrictOutboundNetworkAccess: 'Disabled' + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/sql/server/tests/e2e/pe/dependencies.bicep b/avm/res/sql/server/tests/e2e/pe/dependencies.bicep new file mode 100644 index 0000000000..ef2f9239a0 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/pe/dependencies.bicep @@ -0,0 +1,50 @@ +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink${environment().suffixes.sqlServerHostname}' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +@description('The resource ID of the created virtual network subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/avm/res/sql/server/tests/e2e/pe/main.test.bicep b/avm/res/sql/server/tests/e2e/pe/main.test.bicep new file mode 100644 index 0000000000..cf2f89b0ac --- /dev/null +++ b/avm/res/sql/server/tests/e2e/pe/main.test.bicep @@ -0,0 +1,78 @@ +targetScope = 'subscription' + +metadata name = 'Using Private Endpoints' +metadata description = 'This instance deploys the module with Private Endpoints.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-sql.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlspe' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}' + location: location + administratorLogin: 'adminUserName' + administratorLoginPassword: password + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + subnetResourceId: nestedDependencies.outputs.subnetResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/sql/server/tests/e2e/secondary/dependencies.bicep b/avm/res/sql/server/tests/e2e/secondary/dependencies.bicep new file mode 100644 index 0000000000..d67dcca0fa --- /dev/null +++ b/avm/res/sql/server/tests/e2e/secondary/dependencies.bicep @@ -0,0 +1,36 @@ +@description('Required. The name of the server.') +param serverName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Optional. The password to leverage for the login.') +@secure() +param password string = newGuid() + +resource server 'Microsoft.Sql/servers@2021-11-01' = { + name: serverName + location: location + properties: { + administratorLogin: 'adminUserName' + administratorLoginPassword: password + } + + resource database 'databases@2021-11-01' = { + name: 'db1' + location: location + sku: { + name: 'Basic' + tier: 'Basic' + } + properties: { + maxSizeBytes: 2147483648 + } + } +} + +@description('The resource ID of the created database.') +output databaseResourceId string = server::database.id + +@description('The name of the created database.') +output databaseName string = server::database.name diff --git a/avm/res/sql/server/tests/e2e/secondary/main.test.bicep b/avm/res/sql/server/tests/e2e/secondary/main.test.bicep new file mode 100644 index 0000000000..b453f4a054 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/secondary/main.test.bicep @@ -0,0 +1,75 @@ +targetScope = 'subscription' + +metadata name = 'With a secondary database' +metadata description = 'This instance deploys the module with a secondary database.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-sql.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlsec' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + serverName: 'dep-${namePrefix}-${serviceShort}-pri' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}-sec' + location: location + administratorLogin: 'adminUserName' + administratorLoginPassword: password + databases: [ + { + name: nestedDependencies.outputs.databaseName + skuTier: 'Basic' + skuName: 'Basic' + maxSizeBytes: 2147483648 + createMode: 'Secondary' + sourceDatabaseResourceId: nestedDependencies.outputs.databaseResourceId + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/sql/server/tests/e2e/vulnAssm/dependencies.bicep b/avm/res/sql/server/tests/e2e/vulnAssm/dependencies.bicep new file mode 100644 index 0000000000..6eb808e8c6 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/vulnAssm/dependencies.bicep @@ -0,0 +1,35 @@ +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Storage Account to create.') +param storageAccountName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: managedIdentityName + location: location +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + allowBlobPublicAccess: false + networkAcls: { + defaultAction: 'Deny' + bypass: 'AzureServices' + } + } +} + +@description('The resource ID of the created managed identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created Storage Account.') +output storageAccountResourceId string = storageAccount.id diff --git a/avm/res/sql/server/tests/e2e/vulnAssm/main.test.bicep b/avm/res/sql/server/tests/e2e/vulnAssm/main.test.bicep new file mode 100644 index 0000000000..2483b7dab6 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/vulnAssm/main.test.bicep @@ -0,0 +1,92 @@ +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.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlsvln' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + storageAccountName: 'dep${namePrefix}cdnstore${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}' + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + administratorLogin: 'adminUserName' + administratorLoginPassword: password + location: location + vulnerabilityAssessmentsObj: { + emailSubscriptionAdmins: true + name: 'default' + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + recurringScansIsEnabled: true + storageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId + useStorageAccountAccessKey: false + createStorageRoleAssignment: true + } + securityAlertPolicies: [ + { + name: 'Default' + state: 'Enabled' + emailAccountAdmins: true + } + ] + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/sql/server/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/sql/server/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..5f68856202 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,111 @@ +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@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 + +var addressPrefix = '10.0.0.0/16' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: map(range(0, 2), i => { + name: 'subnet-${i}' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, i) + } + }) + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink${environment().suffixes.sqlServerHostname}' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +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 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-Vault-Crypto-Service-Encryption-User-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 principal ID of the created managed identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created managed identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created virtual network subnet for a Private Endpoint.') +output privateEndpointSubnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created virtual network subnet for a Service Endpoint.') +output serviceEndpointSubnetResourceId string = virtualNetwork.properties.subnets[1].id + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.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/server/tests/e2e/waf-aligned/main.test.bicep b/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..1b0524da20 --- /dev/null +++ b/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,175 @@ +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.servers-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'sqlswaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}azsa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}-${serviceShort}' + primaryUserAssignedIdentityId: nestedDependencies.outputs.managedIdentityResourceId + administrators: { + azureADOnlyAuthentication: true + login: 'myspn' + sid: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'Application' + tenantId: tenant().tenantId + } + location: location + vulnerabilityAssessmentsObj: { + name: 'default' + emailSubscriptionAdmins: true + recurringScansIsEnabled: true + recurringScansEmails: [ + 'test1@contoso.com' + 'test2@contoso.com' + ] + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + } + elasticPools: [ + { + name: '${namePrefix}-${serviceShort}-ep-001' + skuName: 'GP_Gen5' + skuTier: 'GeneralPurpose' + skuCapacity: 10 + // Pre-existing 'public' configuration + maintenanceConfigurationId: '${subscription().id}/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_${location}_DB_1' + } + ] + databases: [ + { + name: '${namePrefix}-${serviceShort}db-001' + collation: 'SQL_Latin1_General_CP1_CI_AS' + skuTier: 'GeneralPurpose' + skuName: 'ElasticPool' + capacity: 0 + maxSizeBytes: 34359738368 + licenseType: 'LicenseIncluded' + diagnosticSettings: [ + { + name: 'customSetting' + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + elasticPoolId: '${resourceGroup.id}/providers/Microsoft.Sql/servers/${namePrefix}-${serviceShort}/elasticPools/${namePrefix}-${serviceShort}-ep-001' + encryptionProtectorObj: { + serverKeyType: 'AzureKeyVault' + serverKeyName: '${nestedDependencies.outputs.keyVaultName}_${nestedDependencies.outputs.keyVaultKeyName}_${last(split(nestedDependencies.outputs.keyVaultEncryptionKeyUrl, '/'))}' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + } + ] + securityAlertPolicies: [ + { + name: 'Default' + state: 'Enabled' + emailAccountAdmins: true + } + ] + keys: [ + { + serverKeyType: 'AzureKeyVault' + uri: nestedDependencies.outputs.keyVaultEncryptionKeyUrl + } + ] + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + privateEndpoints: [ + { + subnetResourceId: nestedDependencies.outputs.privateEndpointSubnetResourceId + service: 'sqlServer' + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + virtualNetworkRules: [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetId: nestedDependencies.outputs.serviceEndpointSubnetResourceId + } + ] + restrictOutboundNetworkAccess: 'Disabled' + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/sql/server/version.json b/avm/res/sql/server/version.json new file mode 100644 index 0000000000..83083db694 --- /dev/null +++ b/avm/res/sql/server/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file diff --git a/avm/res/sql/server/virtual-network-rule/README.md b/avm/res/sql/server/virtual-network-rule/README.md new file mode 100644 index 0000000000..824c30630d --- /dev/null +++ b/avm/res/sql/server/virtual-network-rule/README.md @@ -0,0 +1,75 @@ +# Azure SQL Server Virtual Network Rules `[Microsoft.Sql/servers/virtualNetworkRules]` + +This module deploys an Azure SQL Server Virtual Network Rule. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Sql/servers/virtualNetworkRules` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/virtualNetworkRules) | + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | The name of the Server Virtual Network Rule. | +| [`virtualNetworkSubnetId`](#parameter-virtualnetworksubnetid) | string | The resource ID of the virtual network subnet. | + +**Conditional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The name of the parent SQL Server. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`ignoreMissingVnetServiceEndpoint`](#parameter-ignoremissingvnetserviceendpoint) | bool | Allow creating a firewall rule before the virtual network has vnet service endpoint enabled. | + +### Parameter: `ignoreMissingVnetServiceEndpoint` + +Allow creating a firewall rule before the virtual network has vnet service endpoint enabled. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `name` + +The name of the Server Virtual Network Rule. +- Required: Yes +- Type: string + +### Parameter: `serverName` + +The name of the parent SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `virtualNetworkSubnetId` + +The resource ID of the virtual network subnet. +- Required: Yes +- Type: string + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the deployed virtual network rule. | +| `resourceGroupName` | string | The resource group of the deployed virtual network rule. | +| `resourceId` | string | The resource ID of the deployed virtual network rule. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/sql/server/virtual-network-rule/main.bicep b/avm/res/sql/server/virtual-network-rule/main.bicep new file mode 100644 index 0000000000..f9f4b6513d --- /dev/null +++ b/avm/res/sql/server/virtual-network-rule/main.bicep @@ -0,0 +1,37 @@ +metadata name = 'Azure SQL Server Virtual Network Rules' +metadata description = 'This module deploys an Azure SQL Server Virtual Network Rule.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the Server Virtual Network Rule.') +param name string + +@description('Optional. Allow creating a firewall rule before the virtual network has vnet service endpoint enabled.') +param ignoreMissingVnetServiceEndpoint bool = false + +@description('Required. The resource ID of the virtual network subnet.') +param virtualNetworkSubnetId string + +@description('Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment.') +param serverName string + +resource server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +resource virtualNetworkRule 'Microsoft.Sql/servers/virtualNetworkRules@2022-05-01-preview' = { + name: name + parent: server + properties: { + ignoreMissingVnetServiceEndpoint: ignoreMissingVnetServiceEndpoint + virtualNetworkSubnetId: virtualNetworkSubnetId + } +} + +@description('The name of the deployed virtual network rule.') +output name string = virtualNetworkRule.name + +@description('The resource ID of the deployed virtual network rule.') +output resourceId string = virtualNetworkRule.id + +@description('The resource group of the deployed virtual network rule.') +output resourceGroupName string = resourceGroup().name diff --git a/avm/res/sql/server/virtual-network-rule/main.json b/avm/res/sql/server/virtual-network-rule/main.json new file mode 100644 index 0000000000..4f41ca2538 --- /dev/null +++ b/avm/res/sql/server/virtual-network-rule/main.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4900264168794496336" + }, + "name": "Azure SQL Server Virtual Network Rules", + "description": "This module deploys an Azure SQL Server Virtual Network Rule.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Virtual Network Rule." + } + }, + "ignoreMissingVnetServiceEndpoint": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Allow creating a firewall rule before the virtual network has vnet service endpoint enabled." + } + }, + "virtualNetworkSubnetId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the virtual network subnet." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/virtualNetworkRules", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "ignoreMissingVnetServiceEndpoint": "[parameters('ignoreMissingVnetServiceEndpoint')]", + "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed virtual network rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed virtual network rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/virtualNetworkRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed virtual network rule." + }, + "value": "[resourceGroup().name]" + } + } +} \ No newline at end of file diff --git a/avm/res/sql/server/vulnerability-assessment/README.md b/avm/res/sql/server/vulnerability-assessment/README.md new file mode 100644 index 0000000000..5667609e79 --- /dev/null +++ b/avm/res/sql/server/vulnerability-assessment/README.md @@ -0,0 +1,108 @@ +# Azure SQL Server Vulnerability Assessments `[Microsoft.Sql/servers/vulnerabilityAssessments]` + +This module deploys an Azure SQL Server Vulnerability Assessment. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## 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/servers/vulnerabilityAssessments` | [2022-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Sql/2022-05-01-preview/servers/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 | +| :-- | :-- | :-- | +| [`serverName`](#parameter-servername) | string | The Name of SQL Server. 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 Server system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account. | + +### 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: `name` + +The name of the vulnerability assessment. +- Required: Yes +- Type: string + +### 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: `serverName` + +The Name of SQL Server. Required if the template is used in a standalone deployment. +- Required: Yes +- Type: string + +### Parameter: `storageAccountResourceId` + +A blob storage to hold the scan results. +- Required: Yes +- Type: string + +### 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 Server 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_ diff --git a/avm/res/sql/server/vulnerability-assessment/main.bicep b/avm/res/sql/server/vulnerability-assessment/main.bicep new file mode 100644 index 0000000000..519123f228 --- /dev/null +++ b/avm/res/sql/server/vulnerability-assessment/main.bicep @@ -0,0 +1,64 @@ +metadata name = 'Azure SQL Server Vulnerability Assessments' +metadata description = 'This module deploys an Azure SQL Server Vulnerability Assessment.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the vulnerability assessment.') +param name string + +@description('Conditional. The Name of SQL Server. Required if the template is used in a standalone deployment.') +param serverName 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 Server 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 server 'Microsoft.Sql/servers@2022-05-01-preview' existing = { + name: serverName +} + +// Assign SQL Server MSI access to storage account +module storageAccount_sbdc_rbac 'modules/nested_storageRoleAssignment.bicep' = if (!useStorageAccountAccessKey && createStorageRoleAssignment) { + name: '${server.name}-sbdc-rbac' + scope: resourceGroup(split(storageAccountResourceId, '/')[2], split(storageAccountResourceId, '/')[4]) + params: { + storageAccountName: last(split(storageAccountResourceId, '/')) + managedInstanceIdentityPrincipalId: server.identity.principalId + } +} + +resource vulnerabilityAssessment 'Microsoft.Sql/servers/vulnerabilityAssessments@2022-05-01-preview' = { + name: name + parent: server + 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/server/vulnerability-assessment/main.json b/avm/res/sql/server/vulnerability-assessment/main.json new file mode 100644 index 0000000000..4de960e82e --- /dev/null +++ b/avm/res/sql/server/vulnerability-assessment/main.json @@ -0,0 +1,162 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "3944798211825370066" + }, + "name": "Azure SQL Server Vulnerability Assessments", + "description": "This module deploys an Azure SQL Server Vulnerability Assessment.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The Name of SQL Server. 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 Server 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/servers/vulnerabilityAssessments", + "apiVersion": "2022-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), 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('serverName'))]", + "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedInstanceIdentityPrincipalId": { + "value": "[reference(resourceId('Microsoft.Sql/servers', parameters('serverName')), '2022-05-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.23.1.45101", + "templateHash": "11127995627829971090" + } + }, + "parameters": { + "storageAccountName": { + "type": "string" + }, + "managedInstanceIdentityPrincipalId": { + "type": "string" + } + }, + "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/servers/vulnerabilityAssessments', parameters('serverName'), 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/server/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep b/avm/res/sql/server/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep new file mode 100644 index 0000000000..7855e9f142 --- /dev/null +++ b/avm/res/sql/server/vulnerability-assessment/modules/nested_storageRoleAssignment.bicep @@ -0,0 +1,17 @@ +param storageAccountName string +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 e34415b9f33d46a47959269117991945197034f1 Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Tue, 21 Nov 2023 09:31:09 -0700 Subject: [PATCH 4/7] feat: avm/res/network/express-route-gateway (#643) Bicep module for express route gateway. Closes # [![avm.res.network.express-route-gateway](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml/badge.svg?branch=avm%2Fres%2Fnetwork%2Fexpress-route-gateway)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml) --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .../avm.res.network.express-route-gateway.yml | 81 +++ .../network/express-route-gateway/README.md | 464 ++++++++++++++++++ .../network/express-route-gateway/main.bicep | 152 ++++++ .../network/express-route-gateway/main.json | 301 ++++++++++++ .../tests/e2e/defaults/dependencies.bicep | 27 + .../tests/e2e/defaults/main.test.bicep | 61 +++ .../tests/e2e/max/dependencies.bicep | 38 ++ .../tests/e2e/max/main.test.bicep | 77 +++ .../tests/e2e/waf-aligned/dependencies.bicep | 28 ++ .../tests/e2e/waf-aligned/main.test.bicep | 70 +++ .../express-route-gateway/version.json | 7 + 11 files changed, 1306 insertions(+) create mode 100644 .github/workflows/avm.res.network.express-route-gateway.yml create mode 100644 avm/res/network/express-route-gateway/README.md create mode 100644 avm/res/network/express-route-gateway/main.bicep create mode 100644 avm/res/network/express-route-gateway/main.json create mode 100644 avm/res/network/express-route-gateway/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/network/express-route-gateway/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/network/express-route-gateway/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/network/express-route-gateway/tests/e2e/max/main.test.bicep create mode 100644 avm/res/network/express-route-gateway/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/network/express-route-gateway/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/network/express-route-gateway/version.json diff --git a/.github/workflows/avm.res.network.express-route-gateway.yml b/.github/workflows/avm.res.network.express-route-gateway.yml new file mode 100644 index 0000000000..7a0e195db6 --- /dev/null +++ b/.github/workflows/avm.res.network.express-route-gateway.yml @@ -0,0 +1,81 @@ +name: "avm.res.network.express-route-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 + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.network.express-route-gateway.yml" + - "avm/res/network/express-route-gateway/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/network/express-route-gateway" + workflowPath: ".github/workflows/avm.res.network.express-route-gateway.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get parameter 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 }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Module" + 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 }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/network/express-route-gateway/README.md b/avm/res/network/express-route-gateway/README.md new file mode 100644 index 0000000000..8de1fbc892 --- /dev/null +++ b/avm/res/network/express-route-gateway/README.md @@ -0,0 +1,464 @@ +# Express Route Gateways `[Microsoft.Network/expressRouteGateways]` + +This module deploys an Express Route Gateway. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Network/expressRouteGateways` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/expressRouteGateways) | + +## 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/express-route-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 expressRouteGateway 'br/public:avm/res/network/express-route-gateway:' = { + name: '${uniqueString(deployment().name, location)}-test-nergmin' + params: { + // Required parameters + name: 'nergmin001' + virtualHubId: '' + // Non-required parameters + enableTelemetry: '' + 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": "nergmin001" + }, + "virtualHubId": { + "value": "" + }, + // Non-required parameters + "enableTelemetry": { + "value": "" + }, + "location": { + "value": "" + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module expressRouteGateway 'br/public:avm/res/network/express-route-gateway:' = { + name: '${uniqueString(deployment().name, location)}-test-nergmax' + params: { + // Required parameters + name: 'nergmax001' + virtualHubId: '' + // Non-required parameters + autoScaleConfigurationBoundsMax: 3 + autoScaleConfigurationBoundsMin: 2 + enableTelemetry: '' + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + tags: { + hello: 'world' + 'hidden-title': 'This is visible in the resource name' + } + } +} +``` + +
+

+ +

+ +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": "nergmax001" + }, + "virtualHubId": { + "value": "" + }, + // Non-required parameters + "autoScaleConfigurationBoundsMax": { + "value": 3 + }, + "autoScaleConfigurationBoundsMin": { + "value": 2 + }, + "enableTelemetry": { + "value": "" + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "tags": { + "value": { + "hello": "world", + "hidden-title": "This is visible in the resource name" + } + } + } +} +``` + +
+

+ +### 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 expressRouteGateway 'br/public:avm/res/network/express-route-gateway:' = { + name: '${uniqueString(deployment().name, location)}-test-nergwaf' + params: { + // Required parameters + name: 'nergwaf001' + virtualHubId: '' + // Non-required parameters + autoScaleConfigurationBoundsMax: 3 + autoScaleConfigurationBoundsMin: 2 + enableTelemetry: '' + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + tags: { + hello: 'world' + 'hidden-title': 'This is visible in the resource name' + } + } +} +``` + +
+

+ +

+ +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": "nergwaf001" + }, + "virtualHubId": { + "value": "" + }, + // Non-required parameters + "autoScaleConfigurationBoundsMax": { + "value": 3 + }, + "autoScaleConfigurationBoundsMin": { + "value": 2 + }, + "enableTelemetry": { + "value": "" + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "tags": { + "value": { + "hello": "world", + "hidden-title": "This is visible in the resource name" + } + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Name of the Express Route Gateway. | +| [`virtualHubId`](#parameter-virtualhubid) | string | Resource ID of the Virtual Wan Hub. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowNonVirtualWanTraffic`](#parameter-allownonvirtualwantraffic) | bool | Configures this gateway to accept traffic from non Virtual WAN networks. | +| [`autoScaleConfigurationBoundsMax`](#parameter-autoscaleconfigurationboundsmax) | int | Maximum number of scale units deployed for ExpressRoute gateway. | +| [`autoScaleConfigurationBoundsMin`](#parameter-autoscaleconfigurationboundsmin) | int | Minimum number of scale units deployed for ExpressRoute gateway. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable telemetry via a Globally Unique Identifier (GUID). | +| [`expressRouteConnections`](#parameter-expressrouteconnections) | array | List of ExpressRoute connections to the ExpressRoute gateway. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| [`tags`](#parameter-tags) | object | Tags of the Firewall policy resource. | + +### Parameter: `allowNonVirtualWanTraffic` + +Configures this gateway to accept traffic from non Virtual WAN networks. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `autoScaleConfigurationBoundsMax` + +Maximum number of scale units deployed for ExpressRoute gateway. +- Required: No +- Type: int +- Default: `2` + +### Parameter: `autoScaleConfigurationBoundsMin` + +Minimum number of scale units deployed for ExpressRoute gateway. +- Required: No +- Type: int +- Default: `2` + +### Parameter: `enableTelemetry` + +Enable telemetry via a Globally Unique Identifier (GUID). +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `expressRouteConnections` + +List of ExpressRoute connections to the ExpressRoute gateway. +- 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 + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`kind`](#parameter-lockkind) | No | string | Optional. Specify the type of lock. | +| [`name`](#parameter-lockname) | No | string | Optional. Specify the name of lock. | + +### Parameter: `lock.kind` + +Optional. Specify the type of lock. + +- Required: No +- Type: string +- Allowed: `[CanNotDelete, None, ReadOnly]` + +### Parameter: `lock.name` + +Optional. Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `name` + +Name of the Express Route Gateway. +- Required: Yes +- Type: string + +### Parameter: `roleAssignments` + +Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`condition`](#parameter-roleassignmentscondition) | No | string | 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`](#parameter-roleassignmentsconditionversion) | No | string | Optional. Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | No | string | Optional. The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | No | string | Optional. The description of the role assignment. | +| [`principalId`](#parameter-roleassignmentsprincipalid) | Yes | string | Required. The principal ID of the principal (user/group/identity) to assign the role to. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | No | string | Optional. The principal type of the assigned principal ID. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | Yes | string | Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. | + +### Parameter: `roleAssignments.condition` + +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" + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Optional. Version of the condition. + +- Required: No +- Type: string +- Allowed: `[2.0]` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +Optional. The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +Optional. The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalId` + +Required. The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.principalType` + +Optional. The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: `[Device, ForeignGroup, Group, ServicePrincipal, User]` + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. + +- Required: Yes +- Type: string + +### Parameter: `tags` + +Tags of the Firewall policy resource. +- Required: No +- Type: object + +### Parameter: `virtualHubId` + +Resource ID of the Virtual Wan Hub. +- Required: Yes +- Type: string + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the ExpressRoute Gateway. | +| `resourceGroupName` | string | The resource group of the ExpressRoute Gateway was deployed into. | +| `resourceId` | string | The resource ID of the ExpressRoute Gateway. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/network/express-route-gateway/main.bicep b/avm/res/network/express-route-gateway/main.bicep new file mode 100644 index 0000000000..3cd3b7103e --- /dev/null +++ b/avm/res/network/express-route-gateway/main.bicep @@ -0,0 +1,152 @@ +metadata name = 'Express Route Gateways' +metadata description = 'This module deploys an Express Route Gateway.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the Express Route Gateway.') +param name string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Tags of the Firewall policy resource.') +param tags object? + +@description('Optional. Configures this gateway to accept traffic from non Virtual WAN networks.') +param allowNonVirtualWanTraffic bool = false + +@description('Optional. Maximum number of scale units deployed for ExpressRoute gateway.') +param autoScaleConfigurationBoundsMax int = 2 + +@description('Optional. Minimum number of scale units deployed for ExpressRoute gateway.') +param autoScaleConfigurationBoundsMin int = 2 + +@description('Optional. List of ExpressRoute connections to the ExpressRoute gateway.') +param expressRouteConnections array = [] + +@description('Required. Resource ID of the Virtual Wan Hub.') +param virtualHubId string + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments roleAssignmentType + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + +@description('Optional. The lock settings of the service.') +param lock lockType + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Network Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') + 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-expressroutegateway.${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 expressRouteGateway 'Microsoft.Network/expressRouteGateways@2023-04-01' = { + name: name + location: location + tags: tags + properties: { + allowNonVirtualWanTraffic: allowNonVirtualWanTraffic + autoScaleConfiguration: { + bounds: { + max: autoScaleConfigurationBoundsMax + min: autoScaleConfigurationBoundsMin + } + } + expressRouteConnections: expressRouteConnections + virtualHub: { + id: virtualHubId + } + } +} + +resource expressRouteGateway_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: expressRouteGateway +} + +resource expressRouteGateway_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(expressRouteGateway.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: expressRouteGateway +}] + +@description('The resource ID of the ExpressRoute Gateway.') +output resourceId string = expressRouteGateway.id + +@description('The resource group of the ExpressRoute Gateway was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the ExpressRoute Gateway.') +output name string = expressRouteGateway.name + +@description('The location the resource was deployed into.') +output location string = expressRouteGateway.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @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? +}[]? diff --git a/avm/res/network/express-route-gateway/main.json b/avm/res/network/express-route-gateway/main.json new file mode 100644 index 0000000000..af5151b17c --- /dev/null +++ b/avm/res/network/express-route-gateway/main.json @@ -0,0 +1,301 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "4461263099821836128" + }, + "name": "Express Route Gateways", + "description": "This module deploys an Express Route Gateway.", + "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 name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "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 + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Express Route Gateway." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the Firewall policy resource." + } + }, + "allowNonVirtualWanTraffic": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Configures this gateway to accept traffic from non Virtual WAN networks." + } + }, + "autoScaleConfigurationBoundsMax": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. Maximum number of scale units deployed for ExpressRoute gateway." + } + }, + "autoScaleConfigurationBoundsMin": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. Minimum number of scale units deployed for ExpressRoute gateway." + } + }, + "expressRouteConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of ExpressRoute connections to the ExpressRoute gateway." + } + }, + "virtualHubId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the Virtual Wan Hub." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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-expressroutegateway.{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" + } + } + } + } + }, + "expressRouteGateway": { + "type": "Microsoft.Network/expressRouteGateways", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "allowNonVirtualWanTraffic": "[parameters('allowNonVirtualWanTraffic')]", + "autoScaleConfiguration": { + "bounds": { + "max": "[parameters('autoScaleConfigurationBoundsMax')]", + "min": "[parameters('autoScaleConfigurationBoundsMin')]" + } + }, + "expressRouteConnections": "[parameters('expressRouteConnections')]", + "virtualHub": { + "id": "[parameters('virtualHubId')]" + } + } + }, + "expressRouteGateway_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/expressRouteGateways/{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": [ + "expressRouteGateway" + ] + }, + "expressRouteGateway_roleAssignments": { + "copy": { + "name": "expressRouteGateway_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/expressRouteGateways/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/expressRouteGateways', 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], 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": [ + "expressRouteGateway" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the ExpressRoute Gateway." + }, + "value": "[resourceId('Microsoft.Network/expressRouteGateways', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the ExpressRoute Gateway was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the ExpressRoute Gateway." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('expressRouteGateway', '2023-04-01', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/network/express-route-gateway/tests/e2e/defaults/dependencies.bicep b/avm/res/network/express-route-gateway/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..0e84400a01 --- /dev/null +++ b/avm/res/network/express-route-gateway/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,27 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Required. The name of the virtual Hub to create.') +param virtualHubName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +resource virtualHub 'Microsoft.Network/virtualHubs@2023-04-01' = { + name: virtualHubName + location: location + properties: { + addressPrefix: '10.0.0.0/16' + virtualWan: { + id: virtualWan.id + } + } +} + +@description('The resource ID of the created Virtual Hub.') +output virtualHubResourceId string = virtualHub.id diff --git a/avm/res/network/express-route-gateway/tests/e2e/defaults/main.test.bicep b/avm/res/network/express-route-gateway/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..313f56145b --- /dev/null +++ b/avm/res/network/express-route-gateway/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}-network.expressRouteGateway-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nergmin' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualWANName: 'dep-${namePrefix}-vwan-${serviceShort}' + virtualHubName: 'dep-${namePrefix}-hub-${serviceShort}' + location: location + } +} +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableTelemetry: enableTelemetry + name: '${namePrefix}${serviceShort}001' + location: location + virtualHubId: nestedDependencies.outputs.virtualHubResourceId + + } +} diff --git a/avm/res/network/express-route-gateway/tests/e2e/max/dependencies.bicep b/avm/res/network/express-route-gateway/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..acaa3b4df8 --- /dev/null +++ b/avm/res/network/express-route-gateway/tests/e2e/max/dependencies.bicep @@ -0,0 +1,38 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Required. The name of the virtual Hub to create.') +param virtualHubName string + +@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 +} + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +resource virtualHub 'Microsoft.Network/virtualHubs@2023-04-01' = { + name: virtualHubName + location: location + properties: { + addressPrefix: '10.0.0.0/16' + virtualWan: { + id: virtualWan.id + } + } +} + +@description('The resource ID of the created Virtual Hub.') +output virtualHubResourceId string = virtualHub.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/res/network/express-route-gateway/tests/e2e/max/main.test.bicep b/avm/res/network/express-route-gateway/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..e7f942e9dc --- /dev/null +++ b/avm/res/network/express-route-gateway/tests/e2e/max/main.test.bicep @@ -0,0 +1,77 @@ +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.expressRouteGateway-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nergmax' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualWANName: 'dep-${namePrefix}-vwan-${serviceShort}' + virtualHubName: 'dep-${namePrefix}-hub-${serviceShort}' + location: location + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + } +} +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableTelemetry: enableTelemetry + name: '${namePrefix}${serviceShort}001' + location: location + tags: { + 'hidden-title': 'This is visible in the resource name' + hello: 'world' + } + autoScaleConfigurationBoundsMin: 2 + autoScaleConfigurationBoundsMax: 3 + virtualHubId: nestedDependencies.outputs.virtualHubResourceId + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + } +} diff --git a/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..cf53573850 --- /dev/null +++ b/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,28 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Required. The name of the virtual Hub to create.') +param virtualHubName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +resource virtualHub 'Microsoft.Network/virtualHubs@2023-04-01' = { + name: virtualHubName + location: location + properties: { + addressPrefix: '10.0.0.0/16' + virtualWan: { + id: virtualWan.id + } + } +} + +@description('The resource ID of the created Virtual Hub.') +output virtualHubResourceId string = virtualHub.id + diff --git a/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..c61b8486a3 --- /dev/null +++ b/avm/res/network/express-route-gateway/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,70 @@ +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.expressRouteGateway-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nergwaf' + +@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: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualWANName: 'dep-${namePrefix}-vwan-${serviceShort}' + virtualHubName: 'dep-${namePrefix}-hub-${serviceShort}' + location: location + } +} +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableTelemetry: enableTelemetry + name: '${namePrefix}${serviceShort}001' + location: location + tags: { + 'hidden-title': 'This is visible in the resource name' + hello: 'world' + } + autoScaleConfigurationBoundsMin: 2 + autoScaleConfigurationBoundsMax: 3 + virtualHubId: nestedDependencies.outputs.virtualHubResourceId + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + } +} diff --git a/avm/res/network/express-route-gateway/version.json b/avm/res/network/express-route-gateway/version.json new file mode 100644 index 0000000000..04a0dd1a80 --- /dev/null +++ b/avm/res/network/express-route-gateway/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.5", + "pathFilters": [ + "./main.json" + ] +} From a2aacf4329f60ff5c16c2d285f2d36147d2a3c91 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Tue, 21 Nov 2023 19:01:58 +0100 Subject: [PATCH 5/7] fix: Fixed CODEOWNERS reviewer reference (#660) ## Description Fixed CODEOWNERS reviewer reference --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e0a9e83bb3..69a9c0b8ce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ * @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /.github/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep /scripts/ @Azure/bicep-admins @Azure/avm-core-team-technical-bicep -/avm @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 #/avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep From a3bfd8deb6784a764e2cf5b3862b4dc30ccd1986 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Tue, 21 Nov 2023 19:18:13 +0100 Subject: [PATCH 6/7] fix: Added core team to all code owner paths (#661) Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .github/CODEOWNERS | 280 ++++++++++++++++++++++----------------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 69a9c0b8ce..24b1975eab 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,143 +4,143 @@ /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 -#/avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep -#/avm/res/api-management/service/ @Azure/avm-res-apimanagement-service-module-owners-bicep -#/avm/res/app/container-app/ @Azure/avm-res-app-containerapp-module-owners-bicep -#/avm/res/app/managed-environment/ @Azure/avm-res-app-managedenvironment-module-owners-bicep -#/avm/res/app-configuration/configuration-store/ @Azure/avm-res-appconfiguration-configurationstore-module-owners-bicep -#/avm/res/authorization/lock/ @Azure/avm-res-authorization-lock-module-owners-bicep -#/avm/res/authorization/policy-assignment/ @Azure/avm-res-authorization-policyassignment-module-owners-bicep -#/avm/res/authorization/policy-definition/ @Azure/avm-res-authorization-policydefinition-module-owners-bicep -#/avm/res/authorization/policy-exemption/ @Azure/avm-res-authorization-policyexemption-module-owners-bicep -#/avm/res/authorization/policy-set-definition/ @Azure/avm-res-authorization-policysetdefinition-module-owners-bicep -#/avm/res/authorization/role-assignment/ @Azure/avm-res-authorization-roleassignment-module-owners-bicep -#/avm/res/authorization/role-definition/ @Azure/avm-res-authorization-roledefinition-module-owners-bicep -#/avm/res/automation/automation-account/ @Azure/avm-res-automation-automationaccount-module-owners-bicep -#/avm/res/batch/batch-account/ @Azure/avm-res-batch-batchaccount-module-owners-bicep -#/avm/res/cache/redis/ @Azure/avm-res-cache-redis-module-owners-bicep -#/avm/res/cdn/profile/ @Azure/avm-res-cdn-profile-module-owners-bicep -/avm/res/cognitive-services/account/ @Azure/avm-res-cognitiveservices-account-module-owners-bicep -#/avm/res/compute/availability-set/ @Azure/avm-res-compute-availabilityset-module-owners-bicep -#/avm/res/compute/disk/ @Azure/avm-res-compute-disk-module-owners-bicep -#/avm/res/compute/disk-encryption-set/ @Azure/avm-res-compute-diskencryptionset-module-owners-bicep -#/avm/res/compute/gallery/ @Azure/avm-res-compute-gallery-module-owners-bicep -#/avm/res/compute/image/ @Azure/avm-res-compute-image-module-owners-bicep -#/avm/res/compute/proximity-placement-group/ @Azure/avm-res-compute-proximityplacementgroup-module-owners-bicep -/avm/res/compute/ssh-public-key/ @Azure/avm-res-compute-sshpublickey-module-owners-bicep -#/avm/res/compute/virtual-machine/ @Azure/avm-res-compute-virtualmachine-module-owners-bicep -#/avm/res/compute/virtual-machine-scale-set/ @Azure/avm-res-compute-virtualmachinescaleset-module-owners-bicep -#/avm/res/consumption/budget/ @Azure/avm-res-consumption-budget-module-owners-bicep -#/avm/res/container-instance/container-group/ @Azure/avm-res-containerinstance-containergroup-module-owners-bicep -#/avm/res/container-registry/registry/ @Azure/avm-res-containerregistry-registry-module-owners-bicep -#/avm/res/container-service/managed-cluster/ @Azure/avm-res-containerservice-managedcluster-module-owners-bicep -#/avm/res/databricks/access-connector/ @Azure/avm-res-databricks-accessconnector-module-owners-bicep -#/avm/res/databricks/workspace/ @Azure/avm-res-databricks-workspace-module-owners-bicep -#/avm/res/data-factory/factory/ @Azure/avm-res-datafactory-factory-module-owners-bicep -#/avm/res/data-protection/backup-vault/ @Azure/avm-res-dataprotection-backupvault-module-owners-bicep -#/avm/res/db-for-my-sql/flexible-server/ @Azure/avm-res-dbformysql-flexibleserver-module-owners-bicep -#/avm/res/db-for-postgre-sql/flexible-server/ @Azure/avm-res-dbforpostgresql-flexibleserver-module-owners-bicep -#/avm/res/desktop-virtualization/application-group/ @Azure/avm-res-desktopvirtualization-applicationgroup-module-owners-bicep -#/avm/res/desktop-virtualization/host-pool/ @Azure/avm-res-desktopvirtualization-hostpool-module-owners-bicep -#/avm/res/desktop-virtualization/scaling-plan/ @Azure/avm-res-desktopvirtualization-scalingplan-module-owners-bicep -#/avm/res/desktop-virtualization/workspace/ @Azure/avm-res-desktopvirtualization-workspace-module-owners-bicep -#/avm/res/dev-center/devcenter/ @Azure/avm-res-devcenter-devcenter-module-owners-bicep -#/avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep -#/avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep -#/avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep -/avm/res/event-grid/domain/ @Azure/avm-res-eventgrid-domain-module-owners-bicep -#/avm/res/event-grid/system-topic/ @Azure/avm-res-eventgrid-systemtopic-module-owners-bicep -#/avm/res/event-grid/topic/ @Azure/avm-res-eventgrid-topic-module-owners-bicep -#/avm/res/event-hub/namespace/ @Azure/avm-res-eventhub-namespace-module-owners-bicep -#/avm/res/health-bot/health-bot/ @Azure/avm-res-healthbot-healthbot-module-owners-bicep -#/avm/res/healthcare-apis/workspace/ @Azure/avm-res-healthcareapis-workspace-module-owners-bicep -/avm/res/insights/action-group/ @Azure/avm-res-insights-actiongroup-module-owners-bicep -#/avm/res/insights/activity-log-alert/ @Azure/avm-res-insights-activitylogalert-module-owners-bicep -/avm/res/insights/component/ @Azure/avm-res-insights-component-module-owners-bicep -#/avm/res/insights/data-collection-endpoint/ @Azure/avm-res-insights-datacollectionendpoint-module-owners-bicep -#/avm/res/insights/data-collection-rule/ @Azure/avm-res-insights-datacollectionrule-module-owners-bicep -/avm/res/insights/diagnostic-setting/ @Azure/avm-res-insights-diagnosticsetting-module-owners-bicep -#/avm/res/insights/metric-alert/ @Azure/avm-res-insights-metricalert-module-owners-bicep -#/avm/res/insights/private-link-scope/ @Azure/avm-res-insights-privatelinkscope-module-owners-bicep -#/avm/res/insights/scheduled-query-rule/ @Azure/avm-res-insights-scheduledqueryrule-module-owners-bicep -#/avm/res/insights/webtest/ @Azure/avm-res-insights-webtest-module-owners-bicep -/avm/res/key-vault/vault/ @Azure/avm-res-keyvault-vault-module-owners-bicep -/avm/res/kubernetes-configuration/extension/ @Azure/avm-res-kubernetesconfiguration-extension-module-owners-bicep -/avm/res/kubernetes-configuration/flux-configuration/ @Azure/avm-res-kubernetesconfiguration-fluxconfiguration-module-owners-bicep -#/avm/res/load-test-service/load-test/ @Azure/avm-res-loadtestservice-loadtest-module-owners-bicep -/avm/res/logic/workflow/ @Azure/avm-res-logic-workflow-module-owners-bicep -#/avm/res/machine-learning-services/workspace/ @Azure/avm-res-machinelearningservices-workspace-module-owners-bicep -#/avm/res/maintenance/maintenance-configuration/ @Azure/avm-res-maintenance-maintenanceconfiguration-module-owners-bicep -#/avm/res/managed-identity/user-assigned-identity/ @Azure/avm-res-managedidentity-userassignedidentity-module-owners-bicep -#/avm/res/managed-services/registration-definition/ @Azure/avm-res-managedservices-registrationdefinition-module-owners-bicep -#/avm/res/management/management-group/ @Azure/avm-res-management-managementgroup-module-owners-bicep -#/avm/res/net-app/net-app-account/ @Azure/avm-res-netapp-netappaccount-module-owners-bicep -#/avm/res/network/application-gateway/ @Azure/avm-res-network-applicationgateway-module-owners-bicep -#/avm/res/network/application-gateway-web-application-firewall-policy/ @Azure/avm-res-network-applicationgatewaywebapplicationfirewallpolicy-module-owners-bicep -#/avm/res/network/application-security-group/ @Azure/avm-res-network-applicationsecuritygroup-module-owners-bicep -#/avm/res/network/azure-firewall/ @Azure/avm-res-network-azurefirewall-module-owners-bicep -#/avm/res/network/bastion-host/ @Azure/avm-res-network-bastionhost-module-owners-bicep -#/avm/res/network/connection/ @Azure/avm-res-network-connection-module-owners-bicep -#/avm/res/network/ddos-protection-plan/ @Azure/avm-res-network-ddosprotectionplan-module-owners-bicep -/avm/res/network/dns-forwarding-ruleset/ @Azure/avm-res-network-dnsforwardingruleset-module-owners-bicep -/avm/res/network/dns-resolver/ @Azure/avm-res-network-dnsresolver-module-owners-bicep -/avm/res/network/dns-zone/ @Azure/avm-res-network-dnszone-module-owners-bicep -#/avm/res/network/express-route-circuit/ @Azure/avm-res-network-expressroutecircuit-module-owners-bicep -#/avm/res/network/express-route-gateway/ @Azure/avm-res-network-expressroutegateway-module-owners-bicep -#/avm/res/network/firewall-policy/ @Azure/avm-res-network-firewallpolicy-module-owners-bicep -#/avm/res/network/front-door/ @Azure/avm-res-network-frontdoor-module-owners-bicep -#/avm/res/network/front-door-web-application-firewall-policy/ @Azure/avm-res-network-frontdoorwebapplicationfirewallpolicy-module-owners-bicep -#/avm/res/network/ip-group/ @Azure/avm-res-network-ipgroup-module-owners-bicep -/avm/res/network/load-balancer/ @Azure/avm-res-network-loadbalancer-module-owners-bicep -#/avm/res/network/local-network-gateway/ @Azure/avm-res-network-localnetworkgateway-module-owners-bicep -#/avm/res/network/nat-gateway/ @Azure/avm-res-network-natgateway-module-owners-bicep -/avm/res/network/network-interface/ @Azure/avm-res-network-networkinterface-module-owners-bicep -#/avm/res/network/network-manager/ @Azure/avm-res-network-networkmanager-module-owners-bicep -#/avm/res/network/network-security-group/ @Azure/avm-res-network-networksecuritygroup-module-owners-bicep -#/avm/res/network/network-watcher/ @Azure/avm-res-network-networkwatcher-module-owners-bicep -/avm/res/network/private-dns-zone/ @Azure/avm-res-network-privatednszone-module-owners-bicep -/avm/res/network/private-endpoint/ @Azure/avm-res-network-privateendpoint-module-owners-bicep -#/avm/res/network/private-link-service/ @Azure/avm-res-network-privatelinkservice-module-owners-bicep -/avm/res/network/public-ip-address/ @Azure/avm-res-network-publicipaddress-module-owners-bicep -#/avm/res/network/public-ip-prefix/ @Azure/avm-res-network-publicipprefix-module-owners-bicep -#/avm/res/network/route-table/ @Azure/avm-res-network-routetable-module-owners-bicep -#/avm/res/network/service-endpoint-policy/ @Azure/avm-res-network-serviceendpointpolicy-module-owners-bicep -#/avm/res/network/trafficmanagerprofile/ @Azure/avm-res-network-trafficmanagerprofile-module-owners-bicep -#/avm/res/network/virtual-hub/ @Azure/avm-res-network-virtualhub-module-owners-bicep -#/avm/res/network/virtual-network/ @Azure/avm-res-network-virtualnetwork-module-owners-bicep -#/avm/res/network/virtual-network-gateway/ @Azure/avm-res-network-virtualnetworkgateway-module-owners-bicep -#/avm/res/network/virtual-wan/ @Azure/avm-res-network-virtualwan-module-owners-bicep -#/avm/res/network/vpn-gateway/ @Azure/avm-res-network-vpngateway-module-owners-bicep -#/avm/res/network/vpn-site/ @Azure/avm-res-network-vpnsite-module-owners-bicep -/avm/res/operational-insights/workspace/ @Azure/avm-res-operationalinsights-workspace-module-owners-bicep -/avm/res/operations-management/solution/ @Azure/avm-res-operationsmanagement-solution-module-owners-bicep -#/avm/res/policy-insights/remediation/ @Azure/avm-res-policyinsights-remediation-module-owners-bicep -/avm/res/power-bi-dedicated/capacity/ @Azure/avm-res-powerbidedicated-capacity-module-owners-bicep -#/avm/res/purview/account/ @Azure/avm-res-purview-account-module-owners-bicep -#/avm/res/recovery-services/vault/ @Azure/avm-res-recoveryservices-vault-module-owners-bicep -#/avm/res/relay/namespace/ @Azure/avm-res-relay-namespace-module-owners-bicep -#/avm/res/resource-graph/query/ @Azure/avm-res-resourcegraph-query-module-owners-bicep -#/avm/res/resources/deployment-script/ @Azure/avm-res-resources-deploymentscript-module-owners-bicep -#/avm/res/resources/resource-group/ @Azure/avm-res-resources-resourcegroup-module-owners-bicep -#/avm/res/resources/tags/ @Azure/avm-res-resources-tags-module-owners-bicep -/avm/res/search/search-service/ @Azure/avm-res-search-searchservice-module-owners-bicep -#/avm/res/service-bus/namespace/ @Azure/avm-res-servicebus-namespace-module-owners-bicep -#/avm/res/service-fabric/cluster/ @Azure/avm-res-servicefabric-cluster-module-owners-bicep -#/avm/res/signal-r-service/signal-r/ @Azure/avm-res-signalrservice-signalr-module-owners-bicep -#/avm/res/signal-r-service/web-pub-sub/ @Azure/avm-res-signalrservice-webpubsub-module-owners-bicep -#/avm/res/sql/managed-instance/ @Azure/avm-res-sql-managedinstance-module-owners-bicep -/avm/res/sql/server/ @Azure/avm-res-sql-server-module-owners-bicep -#/avm/res/storage/storage-account/ @Azure/avm-res-storage-storageaccount-module-owners-bicep -#/avm/res/synapse/private-link-hub/ @Azure/avm-res-synapse-privatelinkhub-module-owners-bicep -#/avm/res/synapse/workspace/ @Azure/avm-res-synapse-workspace-module-owners-bicep -#/avm/res/virtual-machine-images/image-template/ @Azure/avm-res-virtualmachineimages-imagetemplate-module-owners-bicep -#/avm/res/web/connection/ @Azure/avm-res-web-connection-module-owners-bicep -#/avm/res/web/hosting-environment/ @Azure/avm-res-web-hostingenvironment-module-owners-bicep -#/avm/res/web/serverfarm/ @Azure/avm-res-web-serverfarm-module-owners-bicep -#/avm/res/web/site/ @Azure/avm-res-web-site-module-owners-bicep -#/avm/res/web/static-site/ @Azure/avm-res-web-staticsite-module-owners-bicep -#/avm/ptn/avd-lza/insights/ @Azure/avm-ptn-avd-lza-insights-module-owners-bicep -#/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep -#/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep -#/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep -#/avm/ptn/security/security-center/ @Azure/avm-ptn-securitycenter-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 +#/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 +#/avm/res/authorization/role-assignment/ @Azure/avm-res-authorization-roleassignment-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/authorization/role-definition/ @Azure/avm-res-authorization-roledefinition-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/automation/automation-account/ @Azure/avm-res-automation-automationaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/batch/batch-account/ @Azure/avm-res-batch-batchaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/cache/redis/ @Azure/avm-res-cache-redis-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/cdn/profile/ @Azure/avm-res-cdn-profile-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/cognitive-services/account/ @Azure/avm-res-cognitiveservices-account-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/availability-set/ @Azure/avm-res-compute-availabilityset-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/disk/ @Azure/avm-res-compute-disk-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/disk-encryption-set/ @Azure/avm-res-compute-diskencryptionset-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/gallery/ @Azure/avm-res-compute-gallery-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/image/ @Azure/avm-res-compute-image-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/proximity-placement-group/ @Azure/avm-res-compute-proximityplacementgroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/compute/ssh-public-key/ @Azure/avm-res-compute-sshpublickey-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/virtual-machine/ @Azure/avm-res-compute-virtualmachine-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/compute/virtual-machine-scale-set/ @Azure/avm-res-compute-virtualmachinescaleset-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/consumption/budget/ @Azure/avm-res-consumption-budget-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/container-instance/container-group/ @Azure/avm-res-containerinstance-containergroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/container-registry/registry/ @Azure/avm-res-containerregistry-registry-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/container-service/managed-cluster/ @Azure/avm-res-containerservice-managedcluster-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/databricks/access-connector/ @Azure/avm-res-databricks-accessconnector-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/databricks/workspace/ @Azure/avm-res-databricks-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/data-factory/factory/ @Azure/avm-res-datafactory-factory-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/data-protection/backup-vault/ @Azure/avm-res-dataprotection-backupvault-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/db-for-my-sql/flexible-server/ @Azure/avm-res-dbformysql-flexibleserver-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/db-for-postgre-sql/flexible-server/ @Azure/avm-res-dbforpostgresql-flexibleserver-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/desktop-virtualization/application-group/ @Azure/avm-res-desktopvirtualization-applicationgroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/desktop-virtualization/host-pool/ @Azure/avm-res-desktopvirtualization-hostpool-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/desktop-virtualization/scaling-plan/ @Azure/avm-res-desktopvirtualization-scalingplan-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/desktop-virtualization/workspace/ @Azure/avm-res-desktopvirtualization-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/dev-center/devcenter/ @Azure/avm-res-devcenter-devcenter-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/event-grid/domain/ @Azure/avm-res-eventgrid-domain-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/event-grid/system-topic/ @Azure/avm-res-eventgrid-systemtopic-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/event-grid/topic/ @Azure/avm-res-eventgrid-topic-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/event-hub/namespace/ @Azure/avm-res-eventhub-namespace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/health-bot/health-bot/ @Azure/avm-res-healthbot-healthbot-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/healthcare-apis/workspace/ @Azure/avm-res-healthcareapis-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/insights/action-group/ @Azure/avm-res-insights-actiongroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/activity-log-alert/ @Azure/avm-res-insights-activitylogalert-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/insights/component/ @Azure/avm-res-insights-component-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/data-collection-endpoint/ @Azure/avm-res-insights-datacollectionendpoint-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/data-collection-rule/ @Azure/avm-res-insights-datacollectionrule-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/insights/diagnostic-setting/ @Azure/avm-res-insights-diagnosticsetting-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/metric-alert/ @Azure/avm-res-insights-metricalert-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/private-link-scope/ @Azure/avm-res-insights-privatelinkscope-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/scheduled-query-rule/ @Azure/avm-res-insights-scheduledqueryrule-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/insights/webtest/ @Azure/avm-res-insights-webtest-module-owners-bicep @Azure/avm-core-team-technical-bicep +/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/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 +#/avm/res/managed-identity/user-assigned-identity/ @Azure/avm-res-managedidentity-userassignedidentity-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/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-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 +#/avm/res/network/bastion-host/ @Azure/avm-res-network-bastionhost-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/connection/ @Azure/avm-res-network-connection-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/ddos-protection-plan/ @Azure/avm-res-network-ddosprotectionplan-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/dns-forwarding-ruleset/ @Azure/avm-res-network-dnsforwardingruleset-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/dns-resolver/ @Azure/avm-res-network-dnsresolver-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/dns-zone/ @Azure/avm-res-network-dnszone-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/express-route-circuit/ @Azure/avm-res-network-expressroutecircuit-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/express-route-gateway/ @Azure/avm-res-network-expressroutegateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/firewall-policy/ @Azure/avm-res-network-firewallpolicy-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/front-door/ @Azure/avm-res-network-frontdoor-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/front-door-web-application-firewall-policy/ @Azure/avm-res-network-frontdoorwebapplicationfirewallpolicy-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/ip-group/ @Azure/avm-res-network-ipgroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/load-balancer/ @Azure/avm-res-network-loadbalancer-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/local-network-gateway/ @Azure/avm-res-network-localnetworkgateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/nat-gateway/ @Azure/avm-res-network-natgateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/network-interface/ @Azure/avm-res-network-networkinterface-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/network-manager/ @Azure/avm-res-network-networkmanager-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/network-security-group/ @Azure/avm-res-network-networksecuritygroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/network-watcher/ @Azure/avm-res-network-networkwatcher-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/private-dns-zone/ @Azure/avm-res-network-privatednszone-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/private-endpoint/ @Azure/avm-res-network-privateendpoint-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/private-link-service/ @Azure/avm-res-network-privatelinkservice-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/network/public-ip-address/ @Azure/avm-res-network-publicipaddress-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/public-ip-prefix/ @Azure/avm-res-network-publicipprefix-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/route-table/ @Azure/avm-res-network-routetable-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/service-endpoint-policy/ @Azure/avm-res-network-serviceendpointpolicy-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/trafficmanagerprofile/ @Azure/avm-res-network-trafficmanagerprofile-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/virtual-hub/ @Azure/avm-res-network-virtualhub-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/virtual-network/ @Azure/avm-res-network-virtualnetwork-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/virtual-network-gateway/ @Azure/avm-res-network-virtualnetworkgateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/virtual-wan/ @Azure/avm-res-network-virtualwan-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/vpn-gateway/ @Azure/avm-res-network-vpngateway-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/network/vpn-site/ @Azure/avm-res-network-vpnsite-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/operational-insights/workspace/ @Azure/avm-res-operationalinsights-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/operations-management/solution/ @Azure/avm-res-operationsmanagement-solution-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/policy-insights/remediation/ @Azure/avm-res-policyinsights-remediation-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/power-bi-dedicated/capacity/ @Azure/avm-res-powerbidedicated-capacity-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/purview/account/ @Azure/avm-res-purview-account-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/recovery-services/vault/ @Azure/avm-res-recoveryservices-vault-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/relay/namespace/ @Azure/avm-res-relay-namespace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/resource-graph/query/ @Azure/avm-res-resourcegraph-query-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/resources/deployment-script/ @Azure/avm-res-resources-deploymentscript-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/resources/resource-group/ @Azure/avm-res-resources-resourcegroup-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/resources/tags/ @Azure/avm-res-resources-tags-module-owners-bicep @Azure/avm-core-team-technical-bicep +/avm/res/search/search-service/ @Azure/avm-res-search-searchservice-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/service-bus/namespace/ @Azure/avm-res-servicebus-namespace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/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/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 +#/avm/res/synapse/workspace/ @Azure/avm-res-synapse-workspace-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/virtual-machine-images/image-template/ @Azure/avm-res-virtualmachineimages-imagetemplate-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/web/connection/ @Azure/avm-res-web-connection-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/web/hosting-environment/ @Azure/avm-res-web-hostingenvironment-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/web/serverfarm/ @Azure/avm-res-web-serverfarm-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/web/site/ @Azure/avm-res-web-site-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/res/web/static-site/ @Azure/avm-res-web-staticsite-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/insights/ @Azure/avm-ptn-avd-lza-insights-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep @Azure/avm-core-team-technical-bicep +#/avm/ptn/security/security-center/ @Azure/avm-ptn-securitycenter-module-owners-bicep @Azure/avm-core-team-technical-bicep From a98b4454d3a0ef0c984a7bc77120740259699f48 Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Tue, 21 Nov 2023 11:19:40 -0700 Subject: [PATCH 7/7] feat: Avm/res/network/express route circuit (#636) ## Description Migration of CARML Bicep module for Express-Route-Circuits to AVM closes #285 I[![avm.res.network.express-route-circuit](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml/badge.svg?branch=avm%2Fres%2Fnetwork%2Fexpress-route-circuit)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml) --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: Alexander Sehr Co-authored-by: Jack Tracey <41163455+jtracey93@users.noreply.github.com> --- .../avm.res.network.express-route-circuit.yml | 81 ++ .../network/express-route-circuit/README.md | 775 ++++++++++++++++++ .../network/express-route-circuit/main.bicep | 288 +++++++ .../network/express-route-circuit/main.json | 535 ++++++++++++ .../tests/e2e/defaults/main.test.bicep | 51 ++ .../tests/e2e/max/dependencies.bicep | 13 + .../tests/e2e/max/main.test.bicep | 107 +++ .../tests/e2e/waf-aligned/dependencies.bicep | 13 + .../tests/e2e/waf-aligned/main.test.bicep | 99 +++ .../express-route-circuit/version.json | 7 + avm/res/network/load-balancer/README.md | 58 +- .../tests/e2e/defaults/main.test.bicep | 3 + .../tests/e2e/internal/main.test.bicep | 31 +- .../tests/e2e/max/main.test.bicep | 3 + .../tests/e2e/waf-aligned/main.test.bicep | 3 + 15 files changed, 2001 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/avm.res.network.express-route-circuit.yml create mode 100644 avm/res/network/express-route-circuit/README.md create mode 100644 avm/res/network/express-route-circuit/main.bicep create mode 100644 avm/res/network/express-route-circuit/main.json create mode 100644 avm/res/network/express-route-circuit/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/network/express-route-circuit/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/network/express-route-circuit/tests/e2e/max/main.test.bicep create mode 100644 avm/res/network/express-route-circuit/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/network/express-route-circuit/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/network/express-route-circuit/version.json diff --git a/.github/workflows/avm.res.network.express-route-circuit.yml b/.github/workflows/avm.res.network.express-route-circuit.yml new file mode 100644 index 0000000000..bc4e20a727 --- /dev/null +++ b/.github/workflows/avm.res.network.express-route-circuit.yml @@ -0,0 +1,81 @@ +name: "avm.res.network.express-route-circuit" + +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.network.express-route-circuit.yml" + - "avm/res/network/express-route-circuit/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/network/express-route-circuit" + workflowPath: ".github/workflows/avm.res.network.express-route-circuit.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get parameter 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 }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Module" + 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 }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/network/express-route-circuit/README.md b/avm/res/network/express-route-circuit/README.md new file mode 100644 index 0000000000..dcb88c2a2f --- /dev/null +++ b/avm/res/network/express-route-circuit/README.md @@ -0,0 +1,775 @@ +# ExpressRoute Circuits `[Microsoft.Network/expressRouteCircuits]` + +This module deploys an Express Route Circuit. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/expressRouteCircuits` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/expressRouteCircuits) | + +## 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/express-route-circuit:`. + +- [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 expressRouteCircuit 'br/public:avm/res/network/express-route-circuit:' = { + name: '${uniqueString(deployment().name, location)}-test-nercmin' + params: { + // Required parameters + bandwidthInMbps: 50 + name: 'nercmin001' + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + // 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 + "bandwidthInMbps": { + "value": 50 + }, + "name": { + "value": "nercmin001" + }, + "peeringLocation": { + "value": "Amsterdam" + }, + "serviceProviderName": { + "value": "Equinix" + }, + // 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 expressRouteCircuit 'br/public:avm/res/network/express-route-circuit:' = { + name: '${uniqueString(deployment().name, location)}-test-nercmax' + params: { + // Required parameters + bandwidthInMbps: 50 + name: 'nercmax001' + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + // Non-required parameters + allowClassicOperations: true + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + skuFamily: 'MeteredData' + skuTier: 'Standard' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } + dependsOn: [nestedDependencies] +} +``` + +
+

+ +

+ +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 + "bandwidthInMbps": { + "value": 50 + }, + "name": { + "value": "nercmax001" + }, + "peeringLocation": { + "value": "Amsterdam" + }, + "serviceProviderName": { + "value": "Equinix" + }, + // Non-required parameters + "allowClassicOperations": { + "value": true + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "skuFamily": { + "value": "MeteredData" + }, + "skuTier": { + "value": "Standard" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module expressRouteCircuit 'br/public:avm/res/network/express-route-circuit:' = { + name: '${uniqueString(deployment().name, location)}-test-nercwaf' + params: { + // Required parameters + bandwidthInMbps: 50 + name: 'nercwaf001' + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + // Non-required parameters + allowClassicOperations: true + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + skuFamily: 'MeteredData' + skuTier: 'Standard' + 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 + "bandwidthInMbps": { + "value": 50 + }, + "name": { + "value": "nercwaf001" + }, + "peeringLocation": { + "value": "Amsterdam" + }, + "serviceProviderName": { + "value": "Equinix" + }, + // Non-required parameters + "allowClassicOperations": { + "value": true + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] + }, + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "skuFamily": { + "value": "MeteredData" + }, + "skuTier": { + "value": "Standard" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`bandwidthInMbps`](#parameter-bandwidthinmbps) | int | This is the bandwidth in Mbps of the circuit being created. It must exactly match one of the available bandwidth offers List ExpressRoute Service Providers API call. | +| [`name`](#parameter-name) | string | This is the name of the ExpressRoute circuit. | +| [`peeringLocation`](#parameter-peeringlocation) | string | This is the name of the peering location and not the ARM resource location. It must exactly match one of the available peering locations from List ExpressRoute Service Providers API call. | +| [`serviceProviderName`](#parameter-serviceprovidername) | string | This is the name of the ExpressRoute Service Provider. It must exactly match one of the Service Providers from List ExpressRoute Service Providers API call. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowClassicOperations`](#parameter-allowclassicoperations) | bool | Allow classic operations. You can connect to virtual networks in the classic deployment model by setting allowClassicOperations to true. | +| [`bandwidthInGbps`](#parameter-bandwidthingbps) | int | The bandwidth of the circuit when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. Default value of 0 will set the property to null. | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable telemetry via a Globally Unique Identifier (GUID). | +| [`expressRoutePortResourceId`](#parameter-expressrouteportresourceid) | string | The reference to the ExpressRoutePort resource when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. | +| [`globalReachEnabled`](#parameter-globalreachenabled) | bool | Flag denoting global reach status. To enable ExpressRoute Global Reach between different geopolitical regions, your circuits must be Premium SKU. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`peerASN`](#parameter-peerasn) | int | The autonomous system number of the customer/connectivity provider. | +| [`peering`](#parameter-peering) | bool | Enabled BGP peering type for the Circuit. | +| [`peeringType`](#parameter-peeringtype) | string | BGP peering type for the Circuit. Choose from AzurePrivatePeering, AzurePublicPeering or MicrosoftPeering. | +| [`primaryPeerAddressPrefix`](#parameter-primarypeeraddressprefix) | string | A /30 subnet used to configure IP addresses for interfaces on Link1. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| [`secondaryPeerAddressPrefix`](#parameter-secondarypeeraddressprefix) | string | A /30 subnet used to configure IP addresses for interfaces on Link2. | +| [`sharedKey`](#parameter-sharedkey) | securestring | The shared key for peering configuration. Router does MD5 hash comparison to validate the packets sent by BGP connection. This parameter is optional and can be removed from peering configuration if not required. | +| [`skuFamily`](#parameter-skufamily) | string | Chosen SKU family of ExpressRoute circuit. Choose from MeteredData or UnlimitedData SKU families. | +| [`skuTier`](#parameter-skutier) | string | Chosen SKU Tier of ExpressRoute circuit. Choose from Local, Premium or Standard SKU tiers. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | +| [`vlanId`](#parameter-vlanid) | int | Specifies the identifier that is used to identify the customer. | + +### Parameter: `allowClassicOperations` + +Allow classic operations. You can connect to virtual networks in the classic deployment model by setting allowClassicOperations to true. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `bandwidthInGbps` + +The bandwidth of the circuit when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. Default value of 0 will set the property to null. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `bandwidthInMbps` + +This is the bandwidth in Mbps of the circuit being created. It must exactly match one of the available bandwidth offers List ExpressRoute Service Providers API call. +- Required: Yes +- Type: int + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | No | string | 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`](#parameter-diagnosticsettingseventhubname) | No | string | 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. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | No | string | Optional. 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) | No | array | Optional. 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) | No | string | Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | No | array | Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to '' to disable log collection. | +| [`name`](#parameter-diagnosticsettingsname) | No | string | Optional. The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | No | string | 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. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | No | string | 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. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +Optional. 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: `[AzureDiagnostics, Dedicated]` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +Optional. 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 + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | No | string | Optional. 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) | No | string | Optional. 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` + +Optional. 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` + +Optional. 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` + +Optional. 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` + +Optional. 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 + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | Yes | string | Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to 'AllMetrics' to collect all metrics. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Required. 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.name` + +Optional. The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +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. + +- Required: No +- Type: string + +### Parameter: `enableTelemetry` + +Enable telemetry via a Globally Unique Identifier (GUID). +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `expressRoutePortResourceId` + +The reference to the ExpressRoutePort resource when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `globalReachEnabled` + +Flag denoting global reach status. To enable ExpressRoute Global Reach between different geopolitical regions, your circuits must be Premium SKU. +- Required: No +- Type: bool +- Default: `False` + +### 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 + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`kind`](#parameter-lockkind) | No | string | Optional. Specify the type of lock. | +| [`name`](#parameter-lockname) | No | string | Optional. Specify the name of lock. | + +### Parameter: `lock.kind` + +Optional. Specify the type of lock. + +- Required: No +- Type: string +- Allowed: `[CanNotDelete, None, ReadOnly]` + +### Parameter: `lock.name` + +Optional. Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `name` + +This is the name of the ExpressRoute circuit. +- Required: Yes +- Type: string + +### Parameter: `peerASN` + +The autonomous system number of the customer/connectivity provider. +- Required: No +- Type: int +- Default: `0` + +### Parameter: `peering` + +Enabled BGP peering type for the Circuit. +- Required: No +- Type: bool +- Default: `False` + +### Parameter: `peeringLocation` + +This is the name of the peering location and not the ARM resource location. It must exactly match one of the available peering locations from List ExpressRoute Service Providers API call. +- Required: Yes +- Type: string + +### Parameter: `peeringType` + +BGP peering type for the Circuit. Choose from AzurePrivatePeering, AzurePublicPeering or MicrosoftPeering. +- Required: No +- Type: string +- Default: `'AzurePrivatePeering'` +- Allowed: + ```Bicep + [ + 'AzurePrivatePeering' + 'MicrosoftPeering' + ] + ``` + +### Parameter: `primaryPeerAddressPrefix` + +A /30 subnet used to configure IP addresses for interfaces on Link1. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `roleAssignments` + +Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. +- Required: No +- Type: array + + +| Name | Required | Type | Description | +| :-- | :-- | :--| :-- | +| [`condition`](#parameter-roleassignmentscondition) | No | string | 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`](#parameter-roleassignmentsconditionversion) | No | string | Optional. Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | No | string | Optional. The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | No | string | Optional. The description of the role assignment. | +| [`principalId`](#parameter-roleassignmentsprincipalid) | Yes | string | Required. The principal ID of the principal (user/group/identity) to assign the role to. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | No | string | Optional. The principal type of the assigned principal ID. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | Yes | string | Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. | + +### Parameter: `roleAssignments.condition` + +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" + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Optional. Version of the condition. + +- Required: No +- Type: string +- Allowed: `[2.0]` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +Optional. The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +Optional. The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalId` + +Required. The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.principalType` + +Optional. The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: `[Device, ForeignGroup, Group, ServicePrincipal, User]` + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead. + +- Required: Yes +- Type: string + +### Parameter: `secondaryPeerAddressPrefix` + +A /30 subnet used to configure IP addresses for interfaces on Link2. +- Required: No +- Type: string +- Default: `''` + +### Parameter: `serviceProviderName` + +This is the name of the ExpressRoute Service Provider. It must exactly match one of the Service Providers from List ExpressRoute Service Providers API call. +- Required: Yes +- Type: string + +### Parameter: `sharedKey` + +The shared key for peering configuration. Router does MD5 hash comparison to validate the packets sent by BGP connection. This parameter is optional and can be removed from peering configuration if not required. +- Required: No +- Type: securestring +- Default: `''` + +### Parameter: `skuFamily` + +Chosen SKU family of ExpressRoute circuit. Choose from MeteredData or UnlimitedData SKU families. +- Required: No +- Type: string +- Default: `'MeteredData'` +- Allowed: + ```Bicep + [ + 'MeteredData' + 'UnlimitedData' + ] + ``` + +### Parameter: `skuTier` + +Chosen SKU Tier of ExpressRoute circuit. Choose from Local, Premium or Standard SKU tiers. +- Required: No +- Type: string +- Default: `'Standard'` +- Allowed: + ```Bicep + [ + 'Local' + 'Premium' + 'Standard' + ] + ``` + +### Parameter: `tags` + +Tags of the resource. +- Required: No +- Type: object + +### Parameter: `vlanId` + +Specifies the identifier that is used to identify the customer. +- Required: No +- Type: int +- Default: `0` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of express route curcuit. | +| `resourceGroupName` | string | The resource group the express route curcuit was deployed into. | +| `resourceId` | string | The resource ID of express route curcuit. | +| `serviceKey` | string | The service key of the express route circuit. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/network/express-route-circuit/main.bicep b/avm/res/network/express-route-circuit/main.bicep new file mode 100644 index 0000000000..9f880c3798 --- /dev/null +++ b/avm/res/network/express-route-circuit/main.bicep @@ -0,0 +1,288 @@ +metadata name = 'ExpressRoute Circuits' +metadata description = 'This module deploys an Express Route Circuit.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. This is the name of the ExpressRoute circuit.') +param name string + +@description('Required. This is the name of the ExpressRoute Service Provider. It must exactly match one of the Service Providers from List ExpressRoute Service Providers API call.') +param serviceProviderName string + +@description('Required. This is the name of the peering location and not the ARM resource location. It must exactly match one of the available peering locations from List ExpressRoute Service Providers API call.') +param peeringLocation string + +@description('Required. This is the bandwidth in Mbps of the circuit being created. It must exactly match one of the available bandwidth offers List ExpressRoute Service Providers API call.') +param bandwidthInMbps int + +@description('Optional. Chosen SKU Tier of ExpressRoute circuit. Choose from Local, Premium or Standard SKU tiers.') +@allowed([ + 'Local' + 'Standard' + 'Premium' +]) +param skuTier string = 'Standard' + +@description('Optional. Chosen SKU family of ExpressRoute circuit. Choose from MeteredData or UnlimitedData SKU families.') +@allowed([ + 'MeteredData' + 'UnlimitedData' +]) +param skuFamily string = 'MeteredData' + +@description('Optional. Enabled BGP peering type for the Circuit.') +param peering bool = false + +@description('Optional. BGP peering type for the Circuit. Choose from AzurePrivatePeering, AzurePublicPeering or MicrosoftPeering.') +@allowed([ + 'AzurePrivatePeering' + 'MicrosoftPeering' +]) +param peeringType string = 'AzurePrivatePeering' + +@secure() +@description('Optional. The shared key for peering configuration. Router does MD5 hash comparison to validate the packets sent by BGP connection. This parameter is optional and can be removed from peering configuration if not required.') +param sharedKey string = '' + +@description('Optional. The autonomous system number of the customer/connectivity provider.') +param peerASN int = 0 + +@description('Optional. A /30 subnet used to configure IP addresses for interfaces on Link1.') +param primaryPeerAddressPrefix string = '' + +@description('Optional. A /30 subnet used to configure IP addresses for interfaces on Link2.') +param secondaryPeerAddressPrefix string = '' + +@description('Optional. Specifies the identifier that is used to identify the customer.') +param vlanId int = 0 + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Allow classic operations. You can connect to virtual networks in the classic deployment model by setting allowClassicOperations to true.') +param allowClassicOperations bool = false + +@description('Optional. The bandwidth of the circuit when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. Default value of 0 will set the property to null.') +param bandwidthInGbps int = 0 + +@description('Optional. The reference to the ExpressRoutePort resource when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct.') +param expressRoutePortResourceId string = '' + +@description('Optional. Flag denoting global reach status. To enable ExpressRoute Global Reach between different geopolitical regions, your circuits must be Premium SKU.') +param globalReachEnabled bool = false + +@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 assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments roleAssignmentType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableTelemetry bool = true + + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Network Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') + 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-expressroutecircuit.${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 expressRouteCircuits 'Microsoft.Network/expressRouteCircuits@2023-04-01' = { + name: name + location: location + tags: tags + sku: { + name: '${skuTier}_${skuFamily}' + tier: skuTier + family: skuTier == 'Local' ? 'UnlimitedData' : skuFamily + } + properties: { + allowClassicOperations: allowClassicOperations + globalReachEnabled: globalReachEnabled + bandwidthInGbps: bandwidthInGbps != 0 ? bandwidthInGbps : null + expressRoutePort: !empty(expressRoutePortResourceId) ? { + id: expressRoutePortResourceId + } : null + serviceProviderProperties: { + serviceProviderName: serviceProviderName + peeringLocation: peeringLocation + bandwidthInMbps: bandwidthInMbps + } + peerings: peering ? [ + { + name: peeringType + properties: { + peeringType: peeringType + sharedKey: sharedKey + peerASN: peerASN + primaryPeerAddressPrefix: primaryPeerAddressPrefix + secondaryPeerAddressPrefix: secondaryPeerAddressPrefix + vlanId: vlanId + } + } + ] : null + } +} + +resource expressRouteCircuits_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: expressRouteCircuits +} + +resource expressRouteCircuits_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: diagnosticSetting.?metricCategories ?? [ + { + category: 'AllMetrics' + timeGrain: null + enabled: true + } + ] + logs: diagnosticSetting.?logCategoriesAndGroups ?? [ + { + categoryGroup: 'AllLogs' + enabled: true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: expressRouteCircuits +}] + +resource expressRouteCircuits_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for (roleAssignment, index) in (roleAssignments ?? []): { + name: guid(expressRouteCircuits.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) + properties: { + roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] : roleAssignment.roleDefinitionIdOrName + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: expressRouteCircuits +}] + +@description('The resource ID of express route curcuit.') +output resourceId string = expressRouteCircuits.id + +@description('The resource group the express route curcuit was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of express route curcuit.') +output name string = expressRouteCircuits.name + +@description('The service key of the express route circuit.') +output serviceKey string = expressRouteCircuits.properties.serviceKey + +@description('The location the resource was deployed into.') +output location string = expressRouteCircuits.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to \'AllLogs\' to collect all logs.') + categoryGroup: string? + }[]? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to \'\' to disable log collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to \'AllMetrics\' to collect all metrics.') + category: string + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @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/express-route-circuit/main.json b/avm/res/network/express-route-circuit/main.json new file mode 100644 index 0000000000..99287afe22 --- /dev/null +++ b/avm/res/network/express-route-circuit/main.json @@ -0,0 +1,535 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "9887015317023929385" + }, + "name": "ExpressRoute Circuits", + "description": "This module deploys an Express Route Circuit.", + "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 name of the role to assign. If it cannot be found you can specify the role definition ID instead." + } + }, + "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." + } + } + } + }, + "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." + } + } + } + }, + "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. This is the name of the ExpressRoute circuit." + } + }, + "serviceProviderName": { + "type": "string", + "metadata": { + "description": "Required. This is the name of the ExpressRoute Service Provider. It must exactly match one of the Service Providers from List ExpressRoute Service Providers API call." + } + }, + "peeringLocation": { + "type": "string", + "metadata": { + "description": "Required. This is the name of the peering location and not the ARM resource location. It must exactly match one of the available peering locations from List ExpressRoute Service Providers API call." + } + }, + "bandwidthInMbps": { + "type": "int", + "metadata": { + "description": "Required. This is the bandwidth in Mbps of the circuit being created. It must exactly match one of the available bandwidth offers List ExpressRoute Service Providers API call." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Local", + "Standard", + "Premium" + ], + "metadata": { + "description": "Optional. Chosen SKU Tier of ExpressRoute circuit. Choose from Local, Premium or Standard SKU tiers." + } + }, + "skuFamily": { + "type": "string", + "defaultValue": "MeteredData", + "allowedValues": [ + "MeteredData", + "UnlimitedData" + ], + "metadata": { + "description": "Optional. Chosen SKU family of ExpressRoute circuit. Choose from MeteredData or UnlimitedData SKU families." + } + }, + "peering": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enabled BGP peering type for the Circuit." + } + }, + "peeringType": { + "type": "string", + "defaultValue": "AzurePrivatePeering", + "allowedValues": [ + "AzurePrivatePeering", + "MicrosoftPeering" + ], + "metadata": { + "description": "Optional. BGP peering type for the Circuit. Choose from AzurePrivatePeering, AzurePublicPeering or MicrosoftPeering." + } + }, + "sharedKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. The shared key for peering configuration. Router does MD5 hash comparison to validate the packets sent by BGP connection. This parameter is optional and can be removed from peering configuration if not required." + } + }, + "peerASN": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The autonomous system number of the customer/connectivity provider." + } + }, + "primaryPeerAddressPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. A /30 subnet used to configure IP addresses for interfaces on Link1." + } + }, + "secondaryPeerAddressPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. A /30 subnet used to configure IP addresses for interfaces on Link2." + } + }, + "vlanId": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Specifies the identifier that is used to identify the customer." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "allowClassicOperations": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Allow classic operations. You can connect to virtual networks in the classic deployment model by setting allowClassicOperations to true." + } + }, + "bandwidthInGbps": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The bandwidth of the circuit when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct. Default value of 0 will set the property to null." + } + }, + "expressRoutePortResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The reference to the ExpressRoutePort resource when the circuit is provisioned on an ExpressRoutePort resource. Available when configuring Express Route Direct." + } + }, + "globalReachEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag denoting global reach status. To enable ExpressRoute Global Reach between different geopolitical regions, your circuits must be Premium SKU." + } + }, + "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 assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable telemetry via a Globally Unique Identifier (GUID)." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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-expressroutecircuit.{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" + } + } + } + } + }, + "expressRouteCircuits": { + "type": "Microsoft.Network/expressRouteCircuits", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[format('{0}_{1}', parameters('skuTier'), parameters('skuFamily'))]", + "tier": "[parameters('skuTier')]", + "family": "[if(equals(parameters('skuTier'), 'Local'), 'UnlimitedData', parameters('skuFamily'))]" + }, + "properties": { + "allowClassicOperations": "[parameters('allowClassicOperations')]", + "globalReachEnabled": "[parameters('globalReachEnabled')]", + "bandwidthInGbps": "[if(not(equals(parameters('bandwidthInGbps'), 0)), parameters('bandwidthInGbps'), null())]", + "expressRoutePort": "[if(not(empty(parameters('expressRoutePortResourceId'))), createObject('id', parameters('expressRoutePortResourceId')), null())]", + "serviceProviderProperties": { + "serviceProviderName": "[parameters('serviceProviderName')]", + "peeringLocation": "[parameters('peeringLocation')]", + "bandwidthInMbps": "[parameters('bandwidthInMbps')]" + }, + "peerings": "[if(parameters('peering'), createArray(createObject('name', parameters('peeringType'), 'properties', createObject('peeringType', parameters('peeringType'), 'sharedKey', parameters('sharedKey'), 'peerASN', parameters('peerASN'), 'primaryPeerAddressPrefix', parameters('primaryPeerAddressPrefix'), 'secondaryPeerAddressPrefix', parameters('secondaryPeerAddressPrefix'), 'vlanId', parameters('vlanId')))), null())]" + } + }, + "expressRouteCircuits_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/expressRouteCircuits/{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": [ + "expressRouteCircuits" + ] + }, + "expressRouteCircuits_diagnosticSettings": { + "copy": { + "name": "expressRouteCircuits_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/expressRouteCircuits/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "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')]", + "metrics": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics', 'timeGrain', null(), 'enabled', true())))]", + "logs": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'AllLogs', 'enabled', true())))]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "expressRouteCircuits" + ] + }, + "expressRouteCircuits_roleAssignments": { + "copy": { + "name": "expressRouteCircuits_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/expressRouteCircuits/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/expressRouteCircuits', 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], 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": [ + "expressRouteCircuits" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of express route curcuit." + }, + "value": "[resourceId('Microsoft.Network/expressRouteCircuits', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the express route curcuit was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of express route curcuit." + }, + "value": "[parameters('name')]" + }, + "serviceKey": { + "type": "string", + "metadata": { + "description": "The service key of the express route circuit." + }, + "value": "[reference('expressRouteCircuits').serviceKey]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('expressRouteCircuits', '2023-04-01', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/network/express-route-circuit/tests/e2e/defaults/main.test.bicep b/avm/res/network/express-route-circuit/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..0854dbee8c --- /dev/null +++ b/avm/res/network/express-route-circuit/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,51 @@ +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}-network.expressroutecircuits-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nercmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + bandwidthInMbps: 50 + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + location: location + } +}] + + diff --git a/avm/res/network/express-route-circuit/tests/e2e/max/dependencies.bicep b/avm/res/network/express-route-circuit/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..a7f42aee7b --- /dev/null +++ b/avm/res/network/express-route-circuit/tests/e2e/max/dependencies.bicep @@ -0,0 +1,13 @@ +@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 diff --git a/avm/res/network/express-route-circuit/tests/e2e/max/main.test.bicep b/avm/res/network/express-route-circuit/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..ec8bdc5151 --- /dev/null +++ b/avm/res/network/express-route-circuit/tests/e2e/max/main.test.bicep @@ -0,0 +1,107 @@ +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.expressroutecircuits-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nercmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + bandwidthInMbps: 50 + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + skuFamily: 'MeteredData' + skuTier: 'Standard' + allowClassicOperations: true + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + dependsOn: [nestedDependencies] +}] + + diff --git a/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..a7f42aee7b --- /dev/null +++ b/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,13 @@ +@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 diff --git a/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..c61aec26f8 --- /dev/null +++ b/avm/res/network/express-route-circuit/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,99 @@ +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.expressroutecircuits-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nercwaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + bandwidthInMbps: 50 + peeringLocation: 'Amsterdam' + serviceProviderName: 'Equinix' + diagnosticSettings: [ + { + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + skuFamily: 'MeteredData' + skuTier: 'Standard' + allowClassicOperations: true + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +}] + + diff --git a/avm/res/network/express-route-circuit/version.json b/avm/res/network/express-route-circuit/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/res/network/express-route-circuit/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/network/load-balancer/README.md b/avm/res/network/load-balancer/README.md index 3709316986..9cbccc5c1f 100644 --- a/avm/res/network/load-balancer/README.md +++ b/avm/res/network/load-balancer/README.md @@ -30,12 +30,15 @@ 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/network/load-balancer:`. -- [Defaults](#example-1-defaults) -- [Internal](#example-2-internal) -- [Max](#example-3-max) -- [Waf-Aligned](#example-4-waf-aligned) +- [Using only defaults](#example-1-using-only-defaults) +- [Using internal load balancer parameter](#example-2-using-internal-load-balancer-parameter) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [WAF-aligned](#example-4-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. -### Example 1: _Defaults_

@@ -102,7 +105,10 @@ module loadBalancer 'br/public:avm/res/network/load-balancer:' = {

-### Example 2: _Internal_ +### Example 2: _Using internal load balancer parameter_ + +This instance deploys the module with the minimum set of required parameters to deploy an internal load balancer. +

@@ -126,20 +132,6 @@ module loadBalancer 'br/public:avm/res/network/load-balancer:' = { name: 'servers' } ] - diagnosticSettings: [ - { - eventHubAuthorizationRuleResourceId: '' - eventHubName: '' - metricCategories: [ - { - category: 'AllMetrics' - } - ] - name: 'customSetting' - storageAccountResourceId: '' - workspaceResourceId: '' - } - ] inboundNatRules: [ { backendPort: 443 @@ -233,22 +225,6 @@ module loadBalancer 'br/public:avm/res/network/load-balancer:' = { } ] }, - "diagnosticSettings": { - "value": [ - { - "eventHubAuthorizationRuleResourceId": "", - "eventHubName": "", - "metricCategories": [ - { - "category": "AllMetrics" - } - ], - "name": "customSetting", - "storageAccountResourceId": "", - "workspaceResourceId": "" - } - ] - }, "inboundNatRules": { "value": [ { @@ -327,7 +303,10 @@ module loadBalancer 'br/public:avm/res/network/load-balancer:' = {

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

@@ -612,7 +591,10 @@ module loadBalancer 'br/public:avm/res/network/load-balancer:' = {

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

diff --git a/avm/res/network/load-balancer/tests/e2e/defaults/main.test.bicep b/avm/res/network/load-balancer/tests/e2e/defaults/main.test.bicep index 1429f34eca..713916b673 100644 --- a/avm/res/network/load-balancer/tests/e2e/defaults/main.test.bicep +++ b/avm/res/network/load-balancer/tests/e2e/defaults/main.test.bicep @@ -1,5 +1,8 @@ targetScope = 'subscription' +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + // ========== // // Parameters // // ========== // diff --git a/avm/res/network/load-balancer/tests/e2e/internal/main.test.bicep b/avm/res/network/load-balancer/tests/e2e/internal/main.test.bicep index 5c8e8f45b2..291f24a444 100644 --- a/avm/res/network/load-balancer/tests/e2e/internal/main.test.bicep +++ b/avm/res/network/load-balancer/tests/e2e/internal/main.test.bicep @@ -1,5 +1,8 @@ targetScope = 'subscription' +metadata name = 'Using internal load balancer parameter' +metadata description = 'This instance deploys the module with the minimum set of required parameters to deploy an internal load balancer.' + // ========== // // Parameters // // ========== // @@ -38,20 +41,6 @@ module nestedDependencies 'dependencies.bicep' = { } } -// Diagnostics -// =========== -module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { - scope: resourceGroup - name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' - params: { - storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' - logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' - eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' - eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' - location: location - } -} - // ============== // // Test Execution // // ============== // @@ -74,20 +63,6 @@ module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' name: 'servers' } ] - diagnosticSettings: [ - { - name: 'customSetting' - metricCategories: [ - { - category: 'AllMetrics' - } - ] - eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId - storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId - workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId - } - ] inboundNatRules: [ { backendPort: 443 diff --git a/avm/res/network/load-balancer/tests/e2e/max/main.test.bicep b/avm/res/network/load-balancer/tests/e2e/max/main.test.bicep index ac870810cb..6d2a068fea 100644 --- a/avm/res/network/load-balancer/tests/e2e/max/main.test.bicep +++ b/avm/res/network/load-balancer/tests/e2e/max/main.test.bicep @@ -1,5 +1,8 @@ targetScope = 'subscription' +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + // ========== // // Parameters // // ========== // diff --git a/avm/res/network/load-balancer/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/load-balancer/tests/e2e/waf-aligned/main.test.bicep index 3864b9653c..69b43753ad 100644 --- a/avm/res/network/load-balancer/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/network/load-balancer/tests/e2e/waf-aligned/main.test.bicep @@ -1,5 +1,8 @@ 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 // // ========== //