From 0e0c6dfcec13cfa524bbf90b7f1a7bdf66952dec Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Tue, 16 Sep 2025 08:04:53 +0100 Subject: [PATCH 01/23] mcp --- .vscode/mcp.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.vscode/mcp.json b/.vscode/mcp.json index e4d5107fa93..1e1cd99775f 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -1,9 +1,9 @@ { - "inputs": [], - "servers": { - "github": { - "url": "https://api.githubcopilot.com/mcp/", - "type": "http" - } + "inputs": [], + "servers": { + "github": { + "url": "https://api.githubcopilot.com/mcp/", + "type": "http" } -} + } +} \ No newline at end of file From 81f912a73b490c0dfdb843319ecd8142db74e637 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:04:23 +0100 Subject: [PATCH 02/23] commit sg workflow --- .../avm.res.management.service-group.yml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/avm.res.management.service-group.yml diff --git a/.github/workflows/avm.res.management.service-group.yml b/.github/workflows/avm.res.management.service-group.yml new file mode 100644 index 00000000000..adb25aa332c --- /dev/null +++ b/.github/workflows/avm.res.management.service-group.yml @@ -0,0 +1,89 @@ +name: "avm.res.management.service-group" + +on: + 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 + customLocation: + type: string + description: "Default location overwrite (e.g., eastus)" + required: false + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.management.service-group.yml" + - "avm/res/management/service-group/**" + - "utilities/pipelines/**" + - "!utilities/pipelines/platform/**" + - "!*/**/child-module-publish-allowed-list.json" + - "!*/**/README.md" + +env: + modulePath: "avm/res/management/service-group" + workflowPath: ".github/workflows/avm.res.management.service-group.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get module test file paths" + id: get-module-test-file-paths + uses: ./.github/actions/templates/avm-getModuleTestFiles + with: + modulePath: "${{ env.modulePath }}" + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Run" + permissions: + id-token: write # For OIDC + contents: write # For release tags + needs: + - job_initialize_pipeline + uses: ./.github/workflows/avm.template.module.yml + with: + workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}" + moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}" + psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit From 0dc7edcbf7ffff875e1806e6185b1b35865ae569 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:06:12 +0100 Subject: [PATCH 03/23] add sg module to issue template --- .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index cf89d1163a0..e6830aa015d 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -177,6 +177,7 @@ body: - "avm/res/managed-identity/user-assigned-identity" - "avm/res/managed-services/registration-definition" - "avm/res/management/management-group" + - "avm/res/management/service-group" - "avm/res/maps/account" - "avm/res/net-app/net-app-account" - "avm/res/network/application-gateway" From dba4b52b036e2613e899da8716ff151993edc15c Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:08:40 +0100 Subject: [PATCH 04/23] set-avm to draft --- avm/res/management/service-group/README.md | 194 ++++++++++++++++++ avm/res/management/service-group/main.bicep | 64 ++++++ avm/res/management/service-group/main.json | 57 +++++ .../tests/e2e/defaults/main.test.bicep | 48 +++++ .../tests/e2e/waf-aligned/main.test.bicep | 48 +++++ avm/res/management/service-group/version.json | 4 + 6 files changed, 415 insertions(+) create mode 100644 avm/res/management/service-group/README.md create mode 100644 avm/res/management/service-group/main.bicep create mode 100644 avm/res/management/service-group/main.json create mode 100644 avm/res/management/service-group/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/management/service-group/version.json diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md new file mode 100644 index 00000000000..27d58371ae1 --- /dev/null +++ b/avm/res/management/service-group/README.md @@ -0,0 +1,194 @@ +# `[Microsoft.Management/serviceGroups]` + + + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Data Collection](#Data-Collection) + +## Resource Types + +_None_ + +## 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/management/service-group:`. + +- [Defaults](#example-1-defaults) +- [Waf-Aligned](#example-2-waf-aligned) + +### Example 1: _Defaults_ + +
+ +via Bicep module + +```bicep +module serviceGroup 'br/public:avm/res/management/service-group:' = { + name: 'serviceGroupDeployment' + params: { + // Required parameters + name: 'msgdef001' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

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

+ +

+ +via Bicep parameters file + +```bicep-params +using 'br/public:avm/res/management/service-group:' + +// Required parameters +param name = 'msgdef001' +// Non-required parameters +param location = '' +``` + +
+

+ +### Example 2: _Waf-Aligned_ + +

+ +via Bicep module + +```bicep +module serviceGroup 'br/public:avm/res/management/service-group:' = { + name: 'serviceGroupDeployment' + params: { + // Required parameters + name: 'msgwaf001' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

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

+ +

+ +via Bicep parameters file + +```bicep-params +using 'br/public:avm/res/management/service-group:' + +// Required parameters +param name = 'msgwaf001' +// Non-required parameters +param location = '' +``` + +
+

+ +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-name) | string | Name of the resource to create. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | Location for all Resources. | + +### Parameter: `name` + +Name of the resource to create. + +- Required: Yes +- Type: string + +### 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]` + +## Outputs + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep new file mode 100644 index 00000000000..dfc902643b3 --- /dev/null +++ b/avm/res/management/service-group/main.bicep @@ -0,0 +1,64 @@ +metadata name = '' +metadata description = '' + +@description('Required. Name of the resource to create.') +param name string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// +// Add your parameters here +// + +// ============== // +// Resources // +// ============== // + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].${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' + } + } + } + } +} + +// +// Add your resources here +// + +// ============ // +// Outputs // +// ============ // + +// Add your outputs here + +// @description('The resource ID of the resource.') +// output resourceId string = .id + +// @description('The name of the resource.') +// output name string = .name + +// @description('The location the resource was deployed into.') +// output location string = .location + +// ================ // +// Definitions // +// ================ // +// +// Add your User-defined-types here, if any +// diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json new file mode 100644 index 00000000000..644fa2d431e --- /dev/null +++ b/avm/res/management/service-group/main.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "7056251884645231704" + }, + "name": "", + "description": "" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the resource to create." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].{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" + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..1aebc225a59 --- /dev/null +++ b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,48 @@ +targetScope = 'subscription' + +// ========== // +// 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}---${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'msgdef' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + } + } +] diff --git a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 00000000000..fce87b96a06 --- /dev/null +++ b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,48 @@ +targetScope = 'subscription' + +// ========== // +// 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}---${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'msgwaf' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + } + } +] diff --git a/avm/res/management/service-group/version.json b/avm/res/management/service-group/version.json new file mode 100644 index 00000000000..f077f1c7fe8 --- /dev/null +++ b/avm/res/management/service-group/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1" +} From bb487c762a9ee2b2fb9cee3292250ff2060dde75 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:40:18 +0100 Subject: [PATCH 05/23] draft v1 --- avm/res/management/service-group/README.md | 250 +++++++++++++++-- avm/res/management/service-group/main.bicep | 132 ++++++--- avm/res/management/service-group/main.json | 255 +++++++++++++++++- .../tests/e2e/defaults/main.test.bicep | 27 +- .../tests/e2e/waf-aligned/main.test.bicep | 32 +-- 5 files changed, 576 insertions(+), 120 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 27d58371ae1..6fe7303ce44 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -1,6 +1,6 @@ -# `[Microsoft.Management/serviceGroups]` +# Service Groups `[Microsoft.Management/serviceGroups]` - +This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources. ## Navigation @@ -8,11 +8,16 @@ - [Usage examples](#Usage-examples) - [Parameters](#Parameters) - [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) - [Data Collection](#Data-Collection) ## Resource Types -_None_ +| Resource Type | API Version | References | +| :-- | :-- | :-- | +| `Microsoft.Authorization/locks` | 2020-05-01 |

  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.authorization_locks.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks)
| +| `Microsoft.Authorization/roleAssignments` | 2022-04-01 |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.authorization_roleassignments.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments)
| +| `Microsoft.Management/serviceGroups` | 2024-02-01-preview |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.management_servicegroups.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Management/2024-02-01-preview/serviceGroups)
| ## Usage examples @@ -35,10 +40,7 @@ The following section provides usage examples for the module, which were used to module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { - // Required parameters name: 'msgdef001' - // Non-required parameters - location: '' } } ``` @@ -55,13 +57,8 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { - // Required parameters "name": { "value": "msgdef001" - }, - // Non-required parameters - "location": { - "value": "" } } } @@ -77,10 +74,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { ```bicep-params using 'br/public:avm/res/management/service-group:' -// Required parameters param name = 'msgdef001' -// Non-required parameters -param location = '' ``` @@ -97,9 +91,15 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { // Required parameters - name: 'msgwaf001' + name: 'sg-msgwaf-001' // Non-required parameters - location: '' + displayName: 'Service Group E2E Test WAF Aligned' + parentResourceId: '' + tags: { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'waf-aligned' + } } } ``` @@ -118,11 +118,21 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "parameters": { // Required parameters "name": { - "value": "msgwaf001" + "value": "sg-msgwaf-001" }, // Non-required parameters - "location": { - "value": "" + "displayName": { + "value": "Service Group E2E Test WAF Aligned" + }, + "parentResourceId": { + "value": "" + }, + "tags": { + "value": { + "environment": "e2e", + "module": "service-group", + "test-scenario": "waf-aligned" + } } } } @@ -139,9 +149,15 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { using 'br/public:avm/res/management/service-group:' // Required parameters -param name = 'msgwaf001' +param name = 'sg-msgwaf-001' // Non-required parameters -param location = '' +param displayName = 'Service Group E2E Test WAF Aligned' +param parentResourceId = '' +param tags = { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'waf-aligned' +} ``` @@ -153,22 +169,33 @@ param location = '' | Parameter | Type | Description | | :-- | :-- | :-- | -| [`name`](#parameter-name) | string | Name of the resource to create. | +| [`name`](#parameter-name) | string | Name of the service group to create. | **Optional parameters** | Parameter | Type | Description | | :-- | :-- | :-- | +| [`displayName`](#parameter-displayname) | string | Display name of the service group to create. If not provided, the name parameter input value will be used. | | [`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. | +| [`parentResourceId`](#parameter-parentresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | ### Parameter: `name` -Name of the resource to create. +Name of the service group to create. - Required: Yes - Type: string +### Parameter: `displayName` + +Display name of the service group to create. If not provided, the name parameter input value will be used. + +- Required: No +- Type: string + ### Parameter: `enableTelemetry` Enable/Disable usage telemetry for module. @@ -177,17 +204,184 @@ Enable/Disable usage telemetry for module. - Type: bool - Default: `True` -### Parameter: `location` +### Parameter: `lock` + +The lock settings of the service. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | +| [`name`](#parameter-lockname) | string | Specify the name of lock. | +| [`notes`](#parameter-locknotes) | string | Specify the notes of the lock. | + +### Parameter: `lock.kind` -Location for all Resources. +Specify the type of lock. - Required: No - Type: string -- Default: `[resourceGroup().location]` +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `lock.notes` + +Specify the notes of the lock. + +- Required: No +- Type: string + +### Parameter: `parentResourceId` + +The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level. + +- Required: No +- Type: string + +### Parameter: `roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` + - `'Service Group Administrator'` + - `'Service Group Contributor'` + - `'Service Group Reader'` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-roleassignmentscondition) | string | The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". | +| [`conditionVersion`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | +| [`name`](#parameter-roleassignmentsname) | string | The name (as GUID) of the role assignment. If not provided, a GUID will be generated. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object ## Outputs -_None_ +| Output | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the service group. | +| `resourceId` | string | The resource ID of the service group. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/utl/types/avm-common-types:0.6.1` | Remote reference | ## Data Collection diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index dfc902643b3..dd22dfed5ff 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -1,26 +1,72 @@ -metadata name = '' -metadata description = '' +targetScope = 'tenant' -@description('Required. Name of the resource to create.') +metadata name = 'Service Groups' +metadata description = 'This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources.' + +@description('Required. Name of the service group to create.') param name string -@description('Optional. Location for all Resources.') -param location string = resourceGroup().location +@description('Optional. Display name of the service group to create. If not provided, the name parameter input value will be used.') +param displayName string? + +@description('Optional. The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level.') +param parentResourceId string? @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true -// -// Add your parameters here -// +@description('Optional. Tags of the resource.') +param tags object? + +import { lockType } from 'br/public:avm/utl/types/avm-common-types:0.6.1' +@description('Optional. The lock settings of the service.') +param lock lockType? -// ============== // -// Resources // -// ============== // +import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.6.1' +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType[]? + +var builtInRoleNames = { + // Add other relevant built-in roles here for your resource as per BCPNFR5 + Contributor: tenantResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: tenantResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: tenantResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': tenantResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': tenantResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) + 'Service Group Administrator': tenantResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4e50c84c-c78e-4e37-b47e-e60ffea0a775' + ) + 'Service Group Contributor': tenantResourceId( + 'Microsoft.Authorization/roleDefinitions', + '32e6a4ec-6095-4e37-b54b-12aa350ba81f' + ) + 'Service Group Reader': tenantResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'de754d53-652d-4c75-a67f-1e48d8b49c97' + ) +} + +var formattedRoleAssignments = [ + for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, { + roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains( + roleAssignment.roleDefinitionIdOrName, + '/providers/Microsoft.Authorization/roleDefinitions/' + ) + ? roleAssignment.roleDefinitionIdOrName + : tenantResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName)) + }) +] #disable-next-line no-deployments-resources -resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { - name: '46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' +resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableTelemetry) { + name: '46d3xbcp.res.management-servicegroup.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, deployment().location), 0, 4)}' properties: { mode: 'Incremental' template: { @@ -37,28 +83,46 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -// -// Add your resources here -// - -// ============ // -// Outputs // -// ============ // - -// Add your outputs here +resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { + name: name + tags: tags + properties: { + displayName: displayName ?? name + parent: { + resourceId: parentResourceId ?? null + } + } +} -// @description('The resource ID of the resource.') -// output resourceId string = .id +resource serviceGroup_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (formattedRoleAssignments ?? []): { + name: roleAssignment.?name ?? guid(serviceGroup.id, roleAssignment.principalId, roleAssignment.roleDefinitionId) + properties: { + roleDefinitionId: roleAssignment.roleDefinitionId + 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 condition is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: serviceGroup + } +] -// @description('The name of the resource.') -// output name string = .name +resource serviceGroup_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?notes ?? (lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.') + } + scope: serviceGroup +} -// @description('The location the resource was deployed into.') -// output location string = .location +@description('The resource ID of the service group.') +output resourceId string = serviceGroup.id -// ================ // -// Definitions // -// ================ // -// -// Add your User-defined-types here, if any -// +@description('The name of the service group.') +output name string = serviceGroup.name diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 644fa2d431e..3d193f8c070 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -1,27 +1,149 @@ { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "7056251884645231704" + "templateHash": "10177553848302611371" }, - "name": "", - "description": "" + "name": "Service Groups", + "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." + }, + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } }, "parameters": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the resource to create." + "description": "Required. Name of the service group to create." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Display name of the service group to create. If not provided, the name parameter input value will be used." } }, - "location": { + "parentResourceId": { "type": "string", - "defaultValue": "[resourceGroup().location]", + "nullable": true, "metadata": { - "description": "Optional. Location for all Resources." + "description": "Optional. The parent service group resource ID, e.g. \"/providers/Microsoft.Management/serviceGroups/\", of the service group to create. If not provided, the service group will be created at the tenant root level." } }, "enableTelemetry": { @@ -30,14 +152,57 @@ "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } } }, - "resources": [ - { + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, tenantResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", + "Service Group Administrator": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', '4e50c84c-c78e-4e37-b47e-e60ffea0a775')]", + "Service Group Contributor": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', '32e6a4ec-6095-4e37-b54b-12aa350ba81f')]", + "Service Group Reader": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', 'de754d53-652d-4c75-a67f-1e48d8b49c97')]" + } + }, + "resources": { + "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.management-servicegroup.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, deployment().location), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -52,6 +217,70 @@ } } } + }, + "serviceGroup": { + "type": "Microsoft.Management/serviceGroups", + "apiVersion": "2024-02-01-preview", + "name": "[parameters('name')]", + "tags": "[parameters('tags')]", + "properties": { + "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", + "parent": { + "resourceId": "[coalesce(parameters('parentResourceId'), null())]" + } + } + }, + "serviceGroup_roleAssignments": { + "copy": { + "name": "serviceGroup_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Management/serviceGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(tenantResourceId('Microsoft.Management/serviceGroups', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "serviceGroup" + ] + }, + "serviceGroup_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.Management/serviceGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "serviceGroup" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the service group." + }, + "value": "[tenantResourceId('Microsoft.Management/serviceGroups', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the service group." + }, + "value": "[parameters('name')]" } - ] + } } \ No newline at end of file diff --git a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep index 1aebc225a59..9146f62f4af 100644 --- a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep @@ -1,35 +1,15 @@ -targetScope = 'subscription' +targetScope = 'tenant' // ========== // // 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}---${serviceShort}-rg' - -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test param serviceShort string = 'msgdef' @description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') param namePrefix string = '#_namePrefix_#' -// ============ // -// Dependencies // -// ============ // - -// General resources -// ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { - name: resourceGroupName - location: resourceLocation -} - // ============== // // Test Execution // // ============== // @@ -37,12 +17,9 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { @batchSize(1) module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' params: { - // You parameters go here name: '${namePrefix}${serviceShort}001' - location: resourceLocation } } ] diff --git a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep index fce87b96a06..a1e3dc2f98c 100644 --- a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep @@ -1,19 +1,10 @@ -targetScope = 'subscription' +targetScope = 'tenant' // ========== // // 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}---${serviceShort}-rg' - -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test param serviceShort string = 'msgwaf' @description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') @@ -23,11 +14,8 @@ param namePrefix string = '#_namePrefix_#' // Dependencies // // ============ // -// General resources -// ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { - name: resourceGroupName - location: resourceLocation +resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { + name: 'sg-${namePrefix}-${serviceShort}-dep-001' } // ============== // @@ -37,12 +25,16 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { @batchSize(1) module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { - scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' params: { - // You parameters go here - name: '${namePrefix}${serviceShort}001' - location: resourceLocation + name: 'sg-${namePrefix}-${serviceShort}-001' + displayName: 'Service Group E2E Test WAF Aligned' + parentResourceId: serviceGroupDependency.id + tags: { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'waf-aligned' + } } } ] From 4ee61854c683c4590606750b6262f41257568f9d Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:46:08 +0100 Subject: [PATCH 06/23] mcp sync --- .vscode/mcp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 1e1cd99775f..92109054115 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -6,4 +6,4 @@ "type": "http" } } -} \ No newline at end of file +} From 2d6d853214d9bd290a1b6b28dc30fe3049f569b8 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:58:51 +0100 Subject: [PATCH 07/23] fixes to tests --- .github/CODEOWNERS | 1 + avm/res/management/service-group/CHANGELOG.md | 13 ++ avm/res/management/service-group/README.md | 132 ++++++++++++++++-- .../tests/e2e/defaults/main.test.bicep | 5 +- .../tests/e2e/max/main.test.bicep | 49 +++++++ .../tests/e2e/waf-aligned/main.test.bicep | 12 +- 6 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 avm/res/management/service-group/CHANGELOG.md create mode 100644 avm/res/management/service-group/tests/e2e/max/main.test.bicep diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5cfc2c6a05c..784a1b188ee 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -142,6 +142,7 @@ /avm/res/managed-identity/user-assigned-identity/ @Azure/avm-res-managedidentity-userassignedidentity-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/managed-services/registration-definition/ @Azure/avm-res-managedservices-registrationdefinition-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/management/management-group/ @Azure/avm-res-management-managementgroup-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/res/management/service-group/ @Azure/avm-res-management-servicegroup-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/maps/account/ @Azure/avm-res-maps-account-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/net-app/net-app-account/ @Azure/avm-res-netapp-netappaccount-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/network/application-gateway/ @Azure/avm-res-network-applicationgateway-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/avm/res/management/service-group/CHANGELOG.md b/avm/res/management/service-group/CHANGELOG.md new file mode 100644 index 00000000000..b3f6512bd35 --- /dev/null +++ b/avm/res/management/service-group/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +The latest version of the changelog can be found [here](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/management/service-group/CHANGELOG.md). + +## 0.1.0 + +### Changes + +- Initial version + +### Breaking Changes + +- None diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 6fe7303ce44..592029399f9 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -27,10 +27,14 @@ 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/management/service-group:`. -- [Defaults](#example-1-defaults) -- [Waf-Aligned](#example-2-waf-aligned) +- [Using only defaults](#example-1-using-only-defaults) +- [Maximum configuration](#example-2-maximum-configuration) +- [WAF-aligned](#example-3-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. -### Example 1: _Defaults_
@@ -40,7 +44,7 @@ The following section provides usage examples for the module, which were used to module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { - name: 'msgdef001' + name: 'msgmin001' } } ``` @@ -58,7 +62,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "contentVersion": "1.0.0.0", "parameters": { "name": { - "value": "msgdef001" + "value": "msgmin001" } } } @@ -74,13 +78,122 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { ```bicep-params using 'br/public:avm/res/management/service-group:' -param name = 'msgdef001' +param name = 'msgmin001' ```

-### Example 2: _Waf-Aligned_ +### Example 2: _Maximum configuration_ + +This instance deploys the module with the maximum set of parameters supported. + + +

+ +via Bicep module + +```bicep +module serviceGroup 'br/public:avm/res/management/service-group:' = { + name: 'serviceGroupDeployment' + params: { + // Required parameters + name: 'sg-msgmax-001' + // Non-required parameters + displayName: 'Service Group E2E Test Maximum Configuration' + parentResourceId: '' + roleAssignments: [ + { + principalId: '' + roleDefinitionIdOrName: 'Service Group Administrator' + } + ] + tags: { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'max' + } + } +} +``` + +
+

+ +

+ +via JSON parameters file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "sg-msgmax-001" + }, + // Non-required parameters + "displayName": { + "value": "Service Group E2E Test Maximum Configuration" + }, + "parentResourceId": { + "value": "" + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "roleDefinitionIdOrName": "Service Group Administrator" + } + ] + }, + "tags": { + "value": { + "environment": "e2e", + "module": "service-group", + "test-scenario": "max" + } + } + } +} +``` + +
+

+ +

+ +via Bicep parameters file + +```bicep-params +using 'br/public:avm/res/management/service-group:' + +// Required parameters +param name = 'sg-msgmax-001' +// Non-required parameters +param displayName = 'Service Group E2E Test Maximum Configuration' +param parentResourceId = '' +param roleAssignments = [ + { + principalId: '' + roleDefinitionIdOrName: 'Service Group Administrator' + } +] +param tags = { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'max' +} +``` + +
+

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

@@ -94,7 +207,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'sg-msgwaf-001' // Non-required parameters displayName: 'Service Group E2E Test WAF Aligned' - parentResourceId: '' tags: { environment: 'e2e' module: 'service-group' @@ -124,9 +236,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "displayName": { "value": "Service Group E2E Test WAF Aligned" }, - "parentResourceId": { - "value": "" - }, "tags": { "value": { "environment": "e2e", @@ -152,7 +261,6 @@ using 'br/public:avm/res/management/service-group:' param name = 'sg-msgwaf-001' // Non-required parameters param displayName = 'Service Group E2E Test WAF Aligned' -param parentResourceId = '' param tags = { environment: 'e2e' module: 'service-group' diff --git a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep index 9146f62f4af..eb0ee3c6e9c 100644 --- a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep @@ -1,11 +1,14 @@ targetScope = 'tenant' +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + // ========== // // Parameters // // ========== // @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -param serviceShort string = 'msgdef' +param serviceShort string = 'msgmin' @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_#' diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep new file mode 100644 index 00000000000..dfd1aa894ef --- /dev/null +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -0,0 +1,49 @@ +targetScope = 'tenant' + +metadata name = 'Maximum configuration' +metadata description = 'This instance deploys the module with the maximum set of parameters supported.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'msgmax' + +@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 // +// ============ // + +resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { + name: 'sg-${namePrefix}-${serviceShort}-dep-001' +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' + params: { + name: 'sg-${namePrefix}-${serviceShort}-001' + displayName: 'Service Group E2E Test Maximum Configuration' + parentResourceId: serviceGroupDependency.id + roleAssignments: [ + { + principalId: deployer().objectId + roleDefinitionIdOrName: 'Service Group Administrator' + } + ] + tags: { + environment: 'e2e' + module: 'service-group' + 'test-scenario': 'max' + } + } + } +] diff --git a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep index a1e3dc2f98c..4ecb9fc30ce 100644 --- a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep @@ -1,5 +1,8 @@ targetScope = 'tenant' +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' + // ========== // // Parameters // // ========== // @@ -10,14 +13,6 @@ param serviceShort string = 'msgwaf' @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 // -// ============ // - -resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { - name: 'sg-${namePrefix}-${serviceShort}-dep-001' -} - // ============== // // Test Execution // // ============== // @@ -29,7 +24,6 @@ module testDeployment '../../../main.bicep' = [ params: { name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test WAF Aligned' - parentResourceId: serviceGroupDependency.id tags: { environment: 'e2e' module: 'service-group' From aa1b5b9d9b0d386b5c6cc3c82e25d59066cb94fd Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:09:37 +0100 Subject: [PATCH 08/23] fixes to telem --- avm/res/management/service-group/main.bicep | 1 + avm/res/management/service-group/main.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index dd22dfed5ff..3ad2884fd36 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -67,6 +67,7 @@ var formattedRoleAssignments = [ #disable-next-line no-deployments-resources resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableTelemetry) { name: '46d3xbcp.res.management-servicegroup.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, deployment().location), 0, 4)}' + location: deployment().location properties: { mode: 'Incremental' template: { diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 3d193f8c070..8fe54ca1d93 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "10177553848302611371" + "templateHash": "17333057274113694998" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -203,6 +203,7 @@ "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", "name": "[format('46d3xbcp.res.management-servicegroup.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, deployment().location), 0, 4))]", + "location": "[deployment().location]", "properties": { "mode": "Incremental", "template": { From 9ba6b6db077c22dc6fbda64714b702483b0c8d70 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:24:57 +0100 Subject: [PATCH 09/23] fix parent ID when null --- avm/res/management/service-group/README.md | 4 ++-- avm/res/management/service-group/main.bicep | 4 ++-- avm/res/management/service-group/main.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 592029399f9..418009a43d7 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -286,7 +286,7 @@ param tags = { | [`displayName`](#parameter-displayname) | string | Display name of the service group to create. If not provided, the name parameter input value will be used. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | -| [`parentResourceId`](#parameter-parentresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level. | +| [`parentResourceId`](#parameter-parentresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`tags`](#parameter-tags) | object | Tags of the resource. | @@ -358,7 +358,7 @@ Specify the notes of the lock. ### Parameter: `parentResourceId` -The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level. +The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". - Required: No - Type: string diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 3ad2884fd36..69d81b0e3ac 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -9,7 +9,7 @@ param name string @description('Optional. Display name of the service group to create. If not provided, the name parameter input value will be used.') param displayName string? -@description('Optional. The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created at the tenant root level.') +@description('Optional. The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/".') param parentResourceId string? @description('Optional. Enable/Disable usage telemetry for module.') @@ -90,7 +90,7 @@ resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = properties: { displayName: displayName ?? name parent: { - resourceId: parentResourceId ?? null + resourceId: parentResourceId ?? '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' } } } diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 8fe54ca1d93..abf92908c2f 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "17333057274113694998" + "templateHash": "4705899720521679224" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -143,7 +143,7 @@ "type": "string", "nullable": true, "metadata": { - "description": "Optional. The parent service group resource ID, e.g. \"/providers/Microsoft.Management/serviceGroups/\", of the service group to create. If not provided, the service group will be created at the tenant root level." + "description": "Optional. The parent service group resource ID, e.g. \"/providers/Microsoft.Management/serviceGroups/\", of the service group to create. If not provided, the service group will be created under the root service group, e.g. \"/providers/Microsoft.Management/serviceGroups/\"." } }, "enableTelemetry": { @@ -227,7 +227,7 @@ "properties": { "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", "parent": { - "resourceId": "[coalesce(parameters('parentResourceId'), null())]" + "resourceId": "[coalesce(parameters('parentResourceId'), format('/providers/Microsoft.Management/serviceGroups/{0}', tenant().tenantId))]" } } }, From 5f748feb9301db98af7b87cbb62070554d8f576c Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:37:13 +0100 Subject: [PATCH 10/23] fix max --- avm/res/management/service-group/tests/e2e/max/main.test.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index dfd1aa894ef..13470e1d5e9 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -32,7 +32,7 @@ module testDeployment '../../../main.bicep' = [ params: { name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test Maximum Configuration' - parentResourceId: serviceGroupDependency.id + parentResourceId: '/providers/Microsoft.Management/serviceGroups/${serviceGroupDependency.name}' roleAssignments: [ { principalId: deployer().objectId From cb292c5dc4bdb3b8e3542b40d8cea3ef8381ad7b Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:07:45 +0100 Subject: [PATCH 11/23] fixed locally --- avm/res/management/service-group/README.md | 6 +++--- avm/res/management/service-group/main.bicep | 6 +++++- avm/res/management/service-group/main.json | 10 ++++++++-- .../service-group/tests/e2e/max/main.test.bicep | 10 ++++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 418009a43d7..98e5ddc87a0 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -105,7 +105,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { roleAssignments: [ { principalId: '' - roleDefinitionIdOrName: 'Service Group Administrator' + roleDefinitionIdOrName: 'Service Group Reader' } ] tags: { @@ -144,7 +144,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "value": [ { "principalId": "", - "roleDefinitionIdOrName": "Service Group Administrator" + "roleDefinitionIdOrName": "Service Group Reader" } ] }, @@ -177,7 +177,7 @@ param parentResourceId = '' param roleAssignments = [ { principalId: '' - roleDefinitionIdOrName: 'Service Group Administrator' + roleDefinitionIdOrName: 'Service Group Reader' } ] param tags = { diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 69d81b0e3ac..fb1b8a2d874 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -84,13 +84,17 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableT } } +resource serviceGroup_tenantRoot 'Microsoft.Management/serviceGroups@2024-02-01-preview' existing = { + name: tenant().tenantId +} + resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { name: name tags: tags properties: { displayName: displayName ?? name parent: { - resourceId: parentResourceId ?? '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' + resourceId: parentResourceId ?? serviceGroup_tenantRoot.id } } } diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index abf92908c2f..e0c019e49ad 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "4705899720521679224" + "templateHash": "16877755236988653138" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -219,6 +219,12 @@ } } }, + "serviceGroup_tenantRoot": { + "existing": true, + "type": "Microsoft.Management/serviceGroups", + "apiVersion": "2024-02-01-preview", + "name": "[tenant().tenantId]" + }, "serviceGroup": { "type": "Microsoft.Management/serviceGroups", "apiVersion": "2024-02-01-preview", @@ -227,7 +233,7 @@ "properties": { "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", "parent": { - "resourceId": "[coalesce(parameters('parentResourceId'), format('/providers/Microsoft.Management/serviceGroups/{0}', tenant().tenantId))]" + "resourceId": "[coalesce(parameters('parentResourceId'), tenantResourceId('Microsoft.Management/serviceGroups', tenant().tenantId))]" } } }, diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 13470e1d5e9..4767572ba39 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -19,6 +19,12 @@ param namePrefix string = '#_namePrefix_#' resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { name: 'sg-${namePrefix}-${serviceShort}-dep-001' + properties: { + displayName: 'Service Group E2E Test Dependency' + parent: { + resourceId: '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' + } + } } // ============== // @@ -32,11 +38,11 @@ module testDeployment '../../../main.bicep' = [ params: { name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test Maximum Configuration' - parentResourceId: '/providers/Microsoft.Management/serviceGroups/${serviceGroupDependency.name}' + parentResourceId: serviceGroupDependency.id roleAssignments: [ { principalId: deployer().objectId - roleDefinitionIdOrName: 'Service Group Administrator' + roleDefinitionIdOrName: 'Service Group Reader' } ] tags: { From 42d7b345bd2eebbff32681e015d14e64690f9f80 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:17:10 +0100 Subject: [PATCH 12/23] revert existing due to odd perms issue --- avm/res/management/service-group/main.bicep | 6 +----- avm/res/management/service-group/main.json | 10 ++-------- .../service-group/tests/e2e/max/main.test.bicep | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index fb1b8a2d874..69d81b0e3ac 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -84,17 +84,13 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableT } } -resource serviceGroup_tenantRoot 'Microsoft.Management/serviceGroups@2024-02-01-preview' existing = { - name: tenant().tenantId -} - resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { name: name tags: tags properties: { displayName: displayName ?? name parent: { - resourceId: parentResourceId ?? serviceGroup_tenantRoot.id + resourceId: parentResourceId ?? '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' } } } diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index e0c019e49ad..abf92908c2f 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "16877755236988653138" + "templateHash": "4705899720521679224" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -219,12 +219,6 @@ } } }, - "serviceGroup_tenantRoot": { - "existing": true, - "type": "Microsoft.Management/serviceGroups", - "apiVersion": "2024-02-01-preview", - "name": "[tenant().tenantId]" - }, "serviceGroup": { "type": "Microsoft.Management/serviceGroups", "apiVersion": "2024-02-01-preview", @@ -233,7 +227,7 @@ "properties": { "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", "parent": { - "resourceId": "[coalesce(parameters('parentResourceId'), tenantResourceId('Microsoft.Management/serviceGroups', tenant().tenantId))]" + "resourceId": "[coalesce(parameters('parentResourceId'), format('/providers/Microsoft.Management/serviceGroups/{0}', tenant().tenantId))]" } } }, diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 4767572ba39..bf1252af878 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -20,7 +20,7 @@ param namePrefix string = '#_namePrefix_#' resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { name: 'sg-${namePrefix}-${serviceShort}-dep-001' properties: { - displayName: 'Service Group E2E Test Dependency' + displayName: 'Service Group E2E Test Maximum Dependency' parent: { resourceId: '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' } From 49e7aa47e4aa506c72c426a2d5a2863dc2384ce8 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:42:37 +0100 Subject: [PATCH 13/23] remove tags --- avm/res/management/service-group/README.md | 42 ------------------- avm/res/management/service-group/main.bicep | 4 -- avm/res/management/service-group/main.json | 10 +---- .../tests/e2e/max/main.test.bicep | 5 --- .../tests/e2e/waf-aligned/main.test.bicep | 5 --- 5 files changed, 1 insertion(+), 65 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 98e5ddc87a0..81fca4ed12d 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -108,11 +108,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { roleDefinitionIdOrName: 'Service Group Reader' } ] - tags: { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'max' - } } } ``` @@ -147,13 +142,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "roleDefinitionIdOrName": "Service Group Reader" } ] - }, - "tags": { - "value": { - "environment": "e2e", - "module": "service-group", - "test-scenario": "max" - } } } } @@ -180,11 +168,6 @@ param roleAssignments = [ roleDefinitionIdOrName: 'Service Group Reader' } ] -param tags = { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'max' -} ```
@@ -207,11 +190,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'sg-msgwaf-001' // Non-required parameters displayName: 'Service Group E2E Test WAF Aligned' - tags: { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'waf-aligned' - } } } ``` @@ -235,13 +213,6 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { // Non-required parameters "displayName": { "value": "Service Group E2E Test WAF Aligned" - }, - "tags": { - "value": { - "environment": "e2e", - "module": "service-group", - "test-scenario": "waf-aligned" - } } } } @@ -261,11 +232,6 @@ using 'br/public:avm/res/management/service-group:' param name = 'sg-msgwaf-001' // Non-required parameters param displayName = 'Service Group E2E Test WAF Aligned' -param tags = { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'waf-aligned' -} ``` @@ -288,7 +254,6 @@ param tags = { | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`parentResourceId`](#parameter-parentresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | -| [`tags`](#parameter-tags) | object | Tags of the resource. | ### Parameter: `name` @@ -469,13 +434,6 @@ The principal type of the assigned principal ID. ] ``` -### Parameter: `tags` - -Tags of the resource. - -- Required: No -- Type: object - ## Outputs | Output | Type | Description | diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 69d81b0e3ac..a9755fb710c 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -15,9 +15,6 @@ param parentResourceId string? @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true -@description('Optional. Tags of the resource.') -param tags object? - import { lockType } from 'br/public:avm/utl/types/avm-common-types:0.6.1' @description('Optional. The lock settings of the service.') param lock lockType? @@ -86,7 +83,6 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableT resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { name: name - tags: tags properties: { displayName: displayName ?? name parent: { diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index abf92908c2f..28e0e850ecf 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "4705899720521679224" + "templateHash": "11102961847246225187" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -153,13 +153,6 @@ "description": "Optional. Enable/Disable usage telemetry for module." } }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - }, "lock": { "$ref": "#/definitions/lockType", "nullable": true, @@ -223,7 +216,6 @@ "type": "Microsoft.Management/serviceGroups", "apiVersion": "2024-02-01-preview", "name": "[parameters('name')]", - "tags": "[parameters('tags')]", "properties": { "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", "parent": { diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index bf1252af878..5a431d9b3bf 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -45,11 +45,6 @@ module testDeployment '../../../main.bicep' = [ roleDefinitionIdOrName: 'Service Group Reader' } ] - tags: { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'max' - } } } ] diff --git a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep index 4ecb9fc30ce..0ed6e29a64d 100644 --- a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep @@ -24,11 +24,6 @@ module testDeployment '../../../main.bicep' = [ params: { name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test WAF Aligned' - tags: { - environment: 'e2e' - module: 'service-group' - 'test-scenario': 'waf-aligned' - } } } ] From 0072b150a5dd1d607af39119093b69fff5884c67 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:36:22 +0100 Subject: [PATCH 14/23] add sub members --- avm/res/management/service-group/README.md | 22 ++++-- avm/res/management/service-group/main.bicep | 17 ++++- avm/res/management/service-group/main.json | 70 ++++++++++++++++++- .../modules/subscriptionMember.bicep | 12 ++++ .../tests/e2e/max/main.test.bicep | 3 +- 5 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 avm/res/management/service-group/modules/subscriptionMember.bicep diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 81fca4ed12d..497dd1999c9 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -18,6 +18,7 @@ This module will allow you to create a service group and also associate resource | `Microsoft.Authorization/locks` | 2020-05-01 |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.authorization_locks.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks)
| | `Microsoft.Authorization/roleAssignments` | 2022-04-01 |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.authorization_roleassignments.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments)
| | `Microsoft.Management/serviceGroups` | 2024-02-01-preview |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.management_servicegroups.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Management/2024-02-01-preview/serviceGroups)
| +| `Microsoft.Relationships/serviceGroupMember` | 2023-09-01-preview |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.relationships_servicegroupmember.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Relationships/2023-09-01-preview/serviceGroupMember)
| ## Usage examples @@ -101,7 +102,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'sg-msgmax-001' // Non-required parameters displayName: 'Service Group E2E Test Maximum Configuration' - parentResourceId: '' + parentServiceGroupResourceId: '' roleAssignments: [ { principalId: '' @@ -132,8 +133,8 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "displayName": { "value": "Service Group E2E Test Maximum Configuration" }, - "parentResourceId": { - "value": "" + "parentServiceGroupResourceId": { + "value": "" }, "roleAssignments": { "value": [ @@ -161,7 +162,7 @@ using 'br/public:avm/res/management/service-group:' param name = 'sg-msgmax-001' // Non-required parameters param displayName = 'Service Group E2E Test Maximum Configuration' -param parentResourceId = '' +param parentServiceGroupResourceId = '' param roleAssignments = [ { principalId: '' @@ -252,8 +253,9 @@ param displayName = 'Service Group E2E Test WAF Aligned' | [`displayName`](#parameter-displayname) | string | Display name of the service group to create. If not provided, the name parameter input value will be used. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | -| [`parentResourceId`](#parameter-parentresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | +| [`parentServiceGroupResourceId`](#parameter-parentservicegroupresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`subscriptionIdsToAssociateToServiceGroup`](#parameter-subscriptionidstoassociatetoservicegroup) | array | An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. | ### Parameter: `name` @@ -321,7 +323,7 @@ Specify the notes of the lock. - Required: No - Type: string -### Parameter: `parentResourceId` +### Parameter: `parentServiceGroupResourceId` The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". @@ -434,6 +436,14 @@ The principal type of the assigned principal ID. ] ``` +### Parameter: `subscriptionIdsToAssociateToServiceGroup` + +An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. + +- Required: No +- Type: array +- Default: `[]` + ## Outputs | Output | Type | Description | diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index a9755fb710c..a7fe409b95f 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -10,7 +10,10 @@ param name string param displayName string? @description('Optional. The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/".') -param parentResourceId string? +param parentServiceGroupResourceId string? + +@description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions.') +param subscriptionIdsToAssociateToServiceGroup array = [] @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @@ -24,7 +27,6 @@ import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.6 param roleAssignments roleAssignmentType[]? var builtInRoleNames = { - // Add other relevant built-in roles here for your resource as per BCPNFR5 Contributor: tenantResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: tenantResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: tenantResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') @@ -86,11 +88,20 @@ resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = properties: { displayName: displayName ?? name parent: { - resourceId: parentResourceId ?? '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' + resourceId: parentServiceGroupResourceId ?? '/providers/Microsoft.Management/serviceGroups/${tenant().tenantId}' } } } +module serviceGroup_subscriptionMember 'modules/subscriptionMember.bicep' = [ + for sub in subscriptionIdsToAssociateToServiceGroup: { + scope: subscription(sub) + params: { + serviceGroupResourceId: serviceGroup.id + } + } +] + resource serviceGroup_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (formattedRoleAssignments ?? []): { name: roleAssignment.?name ?? guid(serviceGroup.id, roleAssignment.principalId, roleAssignment.roleDefinitionId) diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 28e0e850ecf..a6f30d9513d 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "11102961847246225187" + "templateHash": "15699628318962356618" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -139,13 +139,20 @@ "description": "Optional. Display name of the service group to create. If not provided, the name parameter input value will be used." } }, - "parentResourceId": { + "parentServiceGroupResourceId": { "type": "string", "nullable": true, "metadata": { "description": "Optional. The parent service group resource ID, e.g. \"/providers/Microsoft.Management/serviceGroups/\", of the service group to create. If not provided, the service group will be created under the root service group, e.g. \"/providers/Microsoft.Management/serviceGroups/\"." } }, + "subscriptionIdsToAssociateToServiceGroup": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions." + } + }, "enableTelemetry": { "type": "bool", "defaultValue": true, @@ -219,7 +226,7 @@ "properties": { "displayName": "[coalesce(parameters('displayName'), parameters('name'))]", "parent": { - "resourceId": "[coalesce(parameters('parentResourceId'), format('/providers/Microsoft.Management/serviceGroups/{0}', tenant().tenantId))]" + "resourceId": "[coalesce(parameters('parentServiceGroupResourceId'), format('/providers/Microsoft.Management/serviceGroups/{0}', tenant().tenantId))]" } } }, @@ -258,6 +265,63 @@ "dependsOn": [ "serviceGroup" ] + }, + "serviceGroup_subscriptionMember": { + "copy": { + "name": "serviceGroup_subscriptionMember", + "count": "[length(parameters('subscriptionIdsToAssociateToServiceGroup'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('serviceGroup_subscriptionMember-{0}-{1}', copyIndex(), uniqueString('serviceGroup_subscriptionMember', deployment().name))]", + "subscriptionId": "[parameters('subscriptionIdsToAssociateToServiceGroup')[copyIndex()]]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serviceGroupResourceId": { + "value": "[tenantResourceId('Microsoft.Management/serviceGroups', parameters('name'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "12154916338204024650" + } + }, + "parameters": { + "serviceGroupResourceId": { + "type": "string", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Relationships/serviceGroupMember@2023-09-01-preview#properties/properties/properties/targetId" + }, + "description": "Required. The resource ID of the service group to which the subscription will be associated to." + } + } + }, + "resources": [ + { + "type": "Microsoft.Relationships/serviceGroupMember", + "apiVersion": "2023-09-01-preview", + "name": "[guid(subscription().subscriptionId, parameters('serviceGroupResourceId'))]", + "properties": { + "targetId": "[parameters('serviceGroupResourceId')]" + } + } + ] + } + }, + "dependsOn": [ + "serviceGroup" + ] } }, "outputs": { diff --git a/avm/res/management/service-group/modules/subscriptionMember.bicep b/avm/res/management/service-group/modules/subscriptionMember.bicep new file mode 100644 index 00000000000..271a449d835 --- /dev/null +++ b/avm/res/management/service-group/modules/subscriptionMember.bicep @@ -0,0 +1,12 @@ +targetScope = 'subscription' + +@description('Required. The resource ID of the service group to which the subscription will be associated to.') +param serviceGroupResourceId resourceInput<'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview'>.properties.targetId + +resource serviceGroup_subscriptionMember 'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview' = { + name: guid(subscription().subscriptionId, serviceGroupResourceId) + properties: { + targetId: serviceGroupResourceId + } + scope: subscription() +} diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 5a431d9b3bf..2490a33809c 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -38,7 +38,8 @@ module testDeployment '../../../main.bicep' = [ params: { name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test Maximum Configuration' - parentResourceId: serviceGroupDependency.id + parentServiceGroupResourceId: serviceGroupDependency.id + subscriptionIdsToAssociateToServiceGroup: [] roleAssignments: [ { principalId: deployer().objectId From 778a0f242a29e6285a10362d8bc03b766c8b0d2b Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:47:57 +0100 Subject: [PATCH 15/23] fix max e2e --- avm/res/management/service-group/README.md | 11 +++++++++++ .../service-group/tests/e2e/max/main.test.bicep | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 497dd1999c9..505ba87fdcb 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -109,6 +109,9 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { roleDefinitionIdOrName: 'Service Group Reader' } ] + subscriptionIdsToAssociateToServiceGroup: [ + '' + ] } } ``` @@ -143,6 +146,11 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "roleDefinitionIdOrName": "Service Group Reader" } ] + }, + "subscriptionIdsToAssociateToServiceGroup": { + "value": [ + "" + ] } } } @@ -169,6 +177,9 @@ param roleAssignments = [ roleDefinitionIdOrName: 'Service Group Reader' } ] +param subscriptionIdsToAssociateToServiceGroup = [ + '' +] ``` diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 2490a33809c..7921522201a 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -13,6 +13,9 @@ param serviceShort string = 'msgmax' @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_#' +@description('Optional. Subscription ID of the subscription to add to the service group for the test execution. This is passed into the modules subscriptionIdsToAssociateToServiceGroup parameter. The deployment principal must have the necessary permissions to perform this action on the target subscription.') +param subscriptionId string = '#_subscriptionId_#' + // ============ // // Dependencies // // ============ // @@ -39,7 +42,9 @@ module testDeployment '../../../main.bicep' = [ name: 'sg-${namePrefix}-${serviceShort}-001' displayName: 'Service Group E2E Test Maximum Configuration' parentServiceGroupResourceId: serviceGroupDependency.id - subscriptionIdsToAssociateToServiceGroup: [] + subscriptionIdsToAssociateToServiceGroup: [ + subscriptionId + ] roleAssignments: [ { principalId: deployer().objectId From b4075fe156802867d41dfd50eeeaf429f70ece46 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:55:53 +0100 Subject: [PATCH 16/23] ensure tests are tenant specific --- avm/res/management/service-group/README.md | 18 +++++++++--------- .../tests/e2e/defaults/main.test.bicep | 2 +- .../tests/e2e/max/main.test.bicep | 4 ++-- .../tests/e2e/waf-aligned/main.test.bicep | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 505ba87fdcb..6db49cfa47f 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -45,7 +45,7 @@ This instance deploys the module with the minimum set of required parameters. module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { - name: 'msgmin001' + name: '' } } ``` @@ -63,7 +63,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "contentVersion": "1.0.0.0", "parameters": { "name": { - "value": "msgmin001" + "value": "" } } } @@ -79,7 +79,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { ```bicep-params using 'br/public:avm/res/management/service-group:' -param name = 'msgmin001' +param name = '' ``` @@ -99,7 +99,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { // Required parameters - name: 'sg-msgmax-001' + name: '' // Non-required parameters displayName: 'Service Group E2E Test Maximum Configuration' parentServiceGroupResourceId: '' @@ -130,7 +130,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "parameters": { // Required parameters "name": { - "value": "sg-msgmax-001" + "value": "" }, // Non-required parameters "displayName": { @@ -167,7 +167,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { using 'br/public:avm/res/management/service-group:' // Required parameters -param name = 'sg-msgmax-001' +param name = '' // Non-required parameters param displayName = 'Service Group E2E Test Maximum Configuration' param parentServiceGroupResourceId = '' @@ -199,7 +199,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { name: 'serviceGroupDeployment' params: { // Required parameters - name: 'sg-msgwaf-001' + name: '' // Non-required parameters displayName: 'Service Group E2E Test WAF Aligned' } @@ -220,7 +220,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "parameters": { // Required parameters "name": { - "value": "sg-msgwaf-001" + "value": "" }, // Non-required parameters "displayName": { @@ -241,7 +241,7 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { using 'br/public:avm/res/management/service-group:' // Required parameters -param name = 'sg-msgwaf-001' +param name = '' // Non-required parameters param displayName = 'Service Group E2E Test WAF Aligned' ``` diff --git a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep index eb0ee3c6e9c..e2acc94d714 100644 --- a/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/defaults/main.test.bicep @@ -22,7 +22,7 @@ module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' params: { - name: '${namePrefix}${serviceShort}001' + name: 'sg-${namePrefix}-${serviceShort}-${uniqueString(tenant().tenantId)}-001' } } ] diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 7921522201a..6baddb98fe0 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -21,7 +21,7 @@ param subscriptionId string = '#_subscriptionId_#' // ============ // resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-preview' = { - name: 'sg-${namePrefix}-${serviceShort}-dep-001' + name: 'sg-${namePrefix}-${serviceShort}-${uniqueString(tenant().tenantId)}-dep-001' properties: { displayName: 'Service Group E2E Test Maximum Dependency' parent: { @@ -39,7 +39,7 @@ module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' params: { - name: 'sg-${namePrefix}-${serviceShort}-001' + name: 'sg-${namePrefix}-${serviceShort}-${uniqueString(tenant().tenantId)}-001' displayName: 'Service Group E2E Test Maximum Configuration' parentServiceGroupResourceId: serviceGroupDependency.id subscriptionIdsToAssociateToServiceGroup: [ diff --git a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep index 0ed6e29a64d..cac290f57f8 100644 --- a/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/waf-aligned/main.test.bicep @@ -22,7 +22,7 @@ module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { name: '${uniqueString(deployment().name, deployment().location)}-test-${serviceShort}-${iteration}' params: { - name: 'sg-${namePrefix}-${serviceShort}-001' + name: 'sg-${namePrefix}-${serviceShort}-${uniqueString(tenant().tenantId)}-001' displayName: 'Service Group E2E Test WAF Aligned' } } From c3a9397b79117974bdc2a4b0865921c381598197 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:57:17 +0100 Subject: [PATCH 17/23] change name input to mention globlly unique --- avm/res/management/service-group/README.md | 4 ++-- avm/res/management/service-group/main.bicep | 2 +- avm/res/management/service-group/main.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 6db49cfa47f..83736fd2c25 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -255,7 +255,7 @@ param displayName = 'Service Group E2E Test WAF Aligned' | Parameter | Type | Description | | :-- | :-- | :-- | -| [`name`](#parameter-name) | string | Name of the service group to create. | +| [`name`](#parameter-name) | string | Name of the service group to create. Must be globally unique. | **Optional parameters** @@ -270,7 +270,7 @@ param displayName = 'Service Group E2E Test WAF Aligned' ### Parameter: `name` -Name of the service group to create. +Name of the service group to create. Must be globally unique. - Required: Yes - Type: string diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index a7fe409b95f..2513d3cafd0 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -3,7 +3,7 @@ targetScope = 'tenant' metadata name = 'Service Groups' metadata description = 'This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources.' -@description('Required. Name of the service group to create.') +@description('Required. Name of the service group to create. Must be globally unique.') param name string @description('Optional. Display name of the service group to create. If not provided, the name parameter input value will be used.') diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index a6f30d9513d..731c060fc45 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "15699628318962356618" + "templateHash": "213898377458315722" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -129,7 +129,7 @@ "name": { "type": "string", "metadata": { - "description": "Required. Name of the service group to create." + "description": "Required. Name of the service group to create. Must be globally unique." } }, "displayName": { From 46e5771ea60e6e3792d5c848a22788b9b75197f0 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:07:12 +0100 Subject: [PATCH 18/23] use just sub id for relationship ID --- avm/res/management/service-group/main.json | 6 +++--- .../service-group/modules/subscriptionMember.bicep | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 731c060fc45..80c865a6191 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "213898377458315722" + "templateHash": "9549123238452447574" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -293,7 +293,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "12154916338204024650" + "templateHash": "6424628369773341153" } }, "parameters": { @@ -311,7 +311,7 @@ { "type": "Microsoft.Relationships/serviceGroupMember", "apiVersion": "2023-09-01-preview", - "name": "[guid(subscription().subscriptionId, parameters('serviceGroupResourceId'))]", + "name": "[subscription().subscriptionId]", "properties": { "targetId": "[parameters('serviceGroupResourceId')]" } diff --git a/avm/res/management/service-group/modules/subscriptionMember.bicep b/avm/res/management/service-group/modules/subscriptionMember.bicep index 271a449d835..6833c3f980b 100644 --- a/avm/res/management/service-group/modules/subscriptionMember.bicep +++ b/avm/res/management/service-group/modules/subscriptionMember.bicep @@ -4,7 +4,7 @@ targetScope = 'subscription' param serviceGroupResourceId resourceInput<'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview'>.properties.targetId resource serviceGroup_subscriptionMember 'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview' = { - name: guid(subscription().subscriptionId, serviceGroupResourceId) + name: subscription().subscriptionId properties: { targetId: serviceGroupResourceId } From 156e3832c6adbeb90aad750a66bb96df235502d5 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:24:43 +0100 Subject: [PATCH 19/23] add rg scope --- avm/res/management/service-group/README.md | 20 ++++++ avm/res/management/service-group/main.bicep | 12 ++++ avm/res/management/service-group/main.json | 66 ++++++++++++++++++- .../modules/resourceGroupMember.bicep | 10 +++ .../tests/e2e/max/main.test.bicep | 10 +++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 avm/res/management/service-group/modules/resourceGroupMember.bicep diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 83736fd2c25..fc8596e6c47 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -103,6 +103,9 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { // Non-required parameters displayName: 'Service Group E2E Test Maximum Configuration' parentServiceGroupResourceId: '' + resourceGroupResourceIdsToAssociateToServiceGroup: [ + '' + ] roleAssignments: [ { principalId: '' @@ -139,6 +142,11 @@ module serviceGroup 'br/public:avm/res/management/service-group:' = { "parentServiceGroupResourceId": { "value": "" }, + "resourceGroupResourceIdsToAssociateToServiceGroup": { + "value": [ + "" + ] + }, "roleAssignments": { "value": [ { @@ -171,6 +179,9 @@ param name = '' // Non-required parameters param displayName = 'Service Group E2E Test Maximum Configuration' param parentServiceGroupResourceId = '' +param resourceGroupResourceIdsToAssociateToServiceGroup = [ + '' +] param roleAssignments = [ { principalId: '' @@ -265,6 +276,7 @@ param displayName = 'Service Group E2E Test WAF Aligned' | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`parentServiceGroupResourceId`](#parameter-parentservicegroupresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | +| [`resourceGroupResourceIdsToAssociateToServiceGroup`](#parameter-resourcegroupresourceidstoassociatetoservicegroup) | array | An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`subscriptionIdsToAssociateToServiceGroup`](#parameter-subscriptionidstoassociatetoservicegroup) | array | An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. | @@ -341,6 +353,14 @@ The parent service group resource ID, e.g. "/providers/Microsoft.Management/serv - Required: No - Type: string +### Parameter: `resourceGroupResourceIdsToAssociateToServiceGroup` + +An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. + +- Required: No +- Type: array +- Default: `[]` + ### Parameter: `roleAssignments` Array of role assignments to create. diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 2513d3cafd0..d612b3ddfc5 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -15,6 +15,9 @@ param parentServiceGroupResourceId string? @description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions.') param subscriptionIdsToAssociateToServiceGroup array = [] +@description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups.') +param resourceGroupResourceIdsToAssociateToServiceGroup array = [] + @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @@ -102,6 +105,15 @@ module serviceGroup_subscriptionMember 'modules/subscriptionMember.bicep' = [ } ] +module serviceGroup_resourceGroupMember 'modules/resourceGroupMember.bicep' = [ + for rg in resourceGroupResourceIdsToAssociateToServiceGroup: { + scope: resourceGroup(split(rg, '/')[2], split(rg, '/')[4]) + params: { + serviceGroupResourceId: serviceGroup.id + } + } +] + resource serviceGroup_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (formattedRoleAssignments ?? []): { name: roleAssignment.?name ?? guid(serviceGroup.id, roleAssignment.principalId, roleAssignment.roleDefinitionId) diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 80c865a6191..82ca1596d56 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "9549123238452447574" + "templateHash": "10528563407430659965" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -153,6 +153,13 @@ "description": "Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions." } }, + "resourceGroupResourceIdsToAssociateToServiceGroup": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups." + } + }, "enableTelemetry": { "type": "bool", "defaultValue": true, @@ -322,6 +329,63 @@ "dependsOn": [ "serviceGroup" ] + }, + "serviceGroup_resourceGroupMember": { + "copy": { + "name": "serviceGroup_resourceGroupMember", + "count": "[length(parameters('resourceGroupResourceIdsToAssociateToServiceGroup'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('serviceGroup_resourceGroupMember-{0}-{1}', copyIndex(), uniqueString('serviceGroup_resourceGroupMember', deployment().name))]", + "subscriptionId": "[split(parameters('resourceGroupResourceIdsToAssociateToServiceGroup')[copyIndex()], '/')[2]]", + "resourceGroup": "[split(parameters('resourceGroupResourceIdsToAssociateToServiceGroup')[copyIndex()], '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serviceGroupResourceId": { + "value": "[tenantResourceId('Microsoft.Management/serviceGroups', 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.37.4.10188", + "templateHash": "6922340949649296049" + } + }, + "parameters": { + "serviceGroupResourceId": { + "type": "string", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Relationships/serviceGroupMember@2023-09-01-preview#properties/properties/properties/targetId" + }, + "description": "Required. The resource ID of the service group to which the resource group will be associated to." + } + } + }, + "resources": [ + { + "type": "Microsoft.Relationships/serviceGroupMember", + "apiVersion": "2023-09-01-preview", + "name": "[uniqueString(resourceGroup().id)]", + "properties": { + "targetId": "[parameters('serviceGroupResourceId')]" + } + } + ] + } + }, + "dependsOn": [ + "serviceGroup" + ] } }, "outputs": { diff --git a/avm/res/management/service-group/modules/resourceGroupMember.bicep b/avm/res/management/service-group/modules/resourceGroupMember.bicep new file mode 100644 index 00000000000..e4cc7f3f4d9 --- /dev/null +++ b/avm/res/management/service-group/modules/resourceGroupMember.bicep @@ -0,0 +1,10 @@ +@description('Required. The resource ID of the service group to which the resource group will be associated to.') +param serviceGroupResourceId resourceInput<'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview'>.properties.targetId + +resource serviceGroup_subscriptionMember 'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview' = { + name: uniqueString(resourceGroup().id) + properties: { + targetId: serviceGroupResourceId + } + scope: resourceGroup() +} diff --git a/avm/res/management/service-group/tests/e2e/max/main.test.bicep b/avm/res/management/service-group/tests/e2e/max/main.test.bicep index 6baddb98fe0..1d3cab246bf 100644 --- a/avm/res/management/service-group/tests/e2e/max/main.test.bicep +++ b/avm/res/management/service-group/tests/e2e/max/main.test.bicep @@ -30,6 +30,13 @@ resource serviceGroupDependency 'Microsoft.Management/serviceGroups@2024-02-01-p } } +module serviceGroupDependency_resourceGroupMember 'br/public:avm/res/resources/resource-group:0.4.1' = { + scope: subscription(subscriptionId) + params: { + name: 'rg-${namePrefix}-${serviceShort}-${uniqueString(tenant().tenantId)}-dep-001' + } +} + // ============== // // Test Execution // // ============== // @@ -45,6 +52,9 @@ module testDeployment '../../../main.bicep' = [ subscriptionIdsToAssociateToServiceGroup: [ subscriptionId ] + resourceGroupResourceIdsToAssociateToServiceGroup: [ + serviceGroupDependency_resourceGroupMember.outputs.resourceId + ] roleAssignments: [ { principalId: deployer().objectId From ba192bb3ea058b359929808797a08e8c0be10a60 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:30:38 +0100 Subject: [PATCH 20/23] add note to module around member limitation --- avm/res/management/service-group/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index fc8596e6c47..2b83911eb83 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -9,6 +9,7 @@ This module will allow you to create a service group and also associate resource - [Parameters](#Parameters) - [Outputs](#Outputs) - [Cross-referenced modules](#Cross-referenced-modules) +- [Notes](#Notes) - [Data Collection](#Data-Collection) ## Resource Types @@ -490,6 +491,10 @@ This section gives you an overview of all local-referenced module files (i.e., o | :-- | :-- | | `br/public:avm/utl/types/avm-common-types:0.6.1` | Remote reference | +## Notes + +This module only supports creating a service group and optionally adding members that are of types: `Subscription` and `Resource Group`. Resource member types, e.g. a storage account or virtual machine, are not supported in this module at this time due to limitations of Bicep. However, the AVM team are planning to add a new shared interface for all resource modules to implement, which will allow you to optionally associate the resource to a service group as part of the resource module itself; you can track the progress of this feature in the issue [2324](https://github.com/Azure/Azure-Verified-Modules/issues/2324). + ## Data Collection The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. From f5d5d6c15c845209031b87e46ad94e105e0d4905 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:50:39 +0100 Subject: [PATCH 21/23] enforce unique string for relationship names for members as must be unique to service group and scope --- avm/res/management/service-group/README.md | 8 ++++---- avm/res/management/service-group/main.bicep | 4 ++-- avm/res/management/service-group/main.json | 14 +++++++------- .../modules/resourceGroupMember.bicep | 2 +- .../service-group/modules/subscriptionMember.bicep | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 2b83911eb83..1868405bb87 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -277,9 +277,9 @@ param displayName = 'Service Group E2E Test WAF Aligned' | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`parentServiceGroupResourceId`](#parameter-parentservicegroupresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | -| [`resourceGroupResourceIdsToAssociateToServiceGroup`](#parameter-resourcegroupresourceidstoassociatetoservicegroup) | array | An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. | +| [`resourceGroupResourceIdsToAssociateToServiceGroup`](#parameter-resourcegroupresourceidstoassociatetoservicegroup) | array | An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | -| [`subscriptionIdsToAssociateToServiceGroup`](#parameter-subscriptionidstoassociatetoservicegroup) | array | An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. | +| [`subscriptionIdsToAssociateToServiceGroup`](#parameter-subscriptionidstoassociatetoservicegroup) | array | An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs. | ### Parameter: `name` @@ -356,7 +356,7 @@ The parent service group resource ID, e.g. "/providers/Microsoft.Management/serv ### Parameter: `resourceGroupResourceIdsToAssociateToServiceGroup` -An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. +An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs - Required: No - Type: array @@ -470,7 +470,7 @@ The principal type of the assigned principal ID. ### Parameter: `subscriptionIdsToAssociateToServiceGroup` -An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. +An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs. - Required: No - Type: array diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index d612b3ddfc5..26e53f4e8aa 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -12,10 +12,10 @@ param displayName string? @description('Optional. The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/".') param parentServiceGroupResourceId string? -@description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions.') +@description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs.') param subscriptionIdsToAssociateToServiceGroup array = [] -@description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups.') +@description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs') param resourceGroupResourceIdsToAssociateToServiceGroup array = [] @description('Optional. Enable/Disable usage telemetry for module.') diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 82ca1596d56..b120e770f2b 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "10528563407430659965" + "templateHash": "18335052659343733882" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -150,14 +150,14 @@ "type": "array", "defaultValue": [], "metadata": { - "description": "Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions." + "description": "Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs." } }, "resourceGroupResourceIdsToAssociateToServiceGroup": { "type": "array", "defaultValue": [], "metadata": { - "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups." + "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs" } }, "enableTelemetry": { @@ -300,7 +300,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "6424628369773341153" + "templateHash": "16921417674676465264" } }, "parameters": { @@ -318,7 +318,7 @@ { "type": "Microsoft.Relationships/serviceGroupMember", "apiVersion": "2023-09-01-preview", - "name": "[subscription().subscriptionId]", + "name": "[uniqueString(subscription().subscriptionId, parameters('serviceGroupResourceId'))]", "properties": { "targetId": "[parameters('serviceGroupResourceId')]" } @@ -357,7 +357,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "6922340949649296049" + "templateHash": "10634720568086308362" } }, "parameters": { @@ -375,7 +375,7 @@ { "type": "Microsoft.Relationships/serviceGroupMember", "apiVersion": "2023-09-01-preview", - "name": "[uniqueString(resourceGroup().id)]", + "name": "[uniqueString(resourceGroup().id, parameters('serviceGroupResourceId'))]", "properties": { "targetId": "[parameters('serviceGroupResourceId')]" } diff --git a/avm/res/management/service-group/modules/resourceGroupMember.bicep b/avm/res/management/service-group/modules/resourceGroupMember.bicep index e4cc7f3f4d9..511d3f3dc1d 100644 --- a/avm/res/management/service-group/modules/resourceGroupMember.bicep +++ b/avm/res/management/service-group/modules/resourceGroupMember.bicep @@ -2,7 +2,7 @@ param serviceGroupResourceId resourceInput<'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview'>.properties.targetId resource serviceGroup_subscriptionMember 'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview' = { - name: uniqueString(resourceGroup().id) + name: uniqueString(resourceGroup().id, serviceGroupResourceId) properties: { targetId: serviceGroupResourceId } diff --git a/avm/res/management/service-group/modules/subscriptionMember.bicep b/avm/res/management/service-group/modules/subscriptionMember.bicep index 6833c3f980b..1de9dd10ad5 100644 --- a/avm/res/management/service-group/modules/subscriptionMember.bicep +++ b/avm/res/management/service-group/modules/subscriptionMember.bicep @@ -4,7 +4,7 @@ targetScope = 'subscription' param serviceGroupResourceId resourceInput<'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview'>.properties.targetId resource serviceGroup_subscriptionMember 'Microsoft.Relationships/serviceGroupMember@2023-09-01-preview' = { - name: subscription().subscriptionId + name: uniqueString(subscription().subscriptionId, serviceGroupResourceId) properties: { targetId: serviceGroupResourceId } From ca7169c062a92747a6aab2d959c8c38a4dde4854 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Fri, 26 Sep 2025 17:03:35 +0100 Subject: [PATCH 22/23] add missing 'dot' to description for resourceGroupResourceIdsToAssociateToServiceGroup --- avm/res/management/service-group/README.md | 4 ++-- avm/res/management/service-group/main.bicep | 2 +- avm/res/management/service-group/main.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 1868405bb87..14a34bcdeeb 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -277,7 +277,7 @@ param displayName = 'Service Group E2E Test WAF Aligned' | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | | [`parentServiceGroupResourceId`](#parameter-parentservicegroupresourceid) | string | The parent service group resource ID, e.g. "/providers/Microsoft.Management/serviceGroups/", of the service group to create. If not provided, the service group will be created under the root service group, e.g. "/providers/Microsoft.Management/serviceGroups/". | -| [`resourceGroupResourceIdsToAssociateToServiceGroup`](#parameter-resourcegroupresourceidstoassociatetoservicegroup) | array | An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs | +| [`resourceGroupResourceIdsToAssociateToServiceGroup`](#parameter-resourcegroupresourceidstoassociatetoservicegroup) | array | An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`subscriptionIdsToAssociateToServiceGroup`](#parameter-subscriptionidstoassociatetoservicegroup) | array | An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs. | @@ -356,7 +356,7 @@ The parent service group resource ID, e.g. "/providers/Microsoft.Management/serv ### Parameter: `resourceGroupResourceIdsToAssociateToServiceGroup` -An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs +An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs. - Required: No - Type: array diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 26e53f4e8aa..8903e1c3695 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -15,7 +15,7 @@ param parentServiceGroupResourceId string? @description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs.') param subscriptionIdsToAssociateToServiceGroup array = [] -@description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs') +@description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs.') param resourceGroupResourceIdsToAssociateToServiceGroup array = [] @description('Optional. Enable/Disable usage telemetry for module.') diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index b120e770f2b..1582420d244 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "18335052659343733882" + "templateHash": "18094953411067250954" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -157,7 +157,7 @@ "type": "array", "defaultValue": [], "metadata": { - "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs" + "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs." } }, "enableTelemetry": { From 3317b4e39db52c571781706912cf0348b8b677f1 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:07:20 +0000 Subject: [PATCH 23/23] fixes from AS review --- avm/res/management/service-group/README.md | 2 -- avm/res/management/service-group/main.bicep | 8 ++++---- avm/res/management/service-group/main.json | 22 +++++++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/avm/res/management/service-group/README.md b/avm/res/management/service-group/README.md index 14a34bcdeeb..0ffc1e3b345 100644 --- a/avm/res/management/service-group/README.md +++ b/avm/res/management/service-group/README.md @@ -360,7 +360,6 @@ An array of resource group resource IDs to associate to the service group. The d - Required: No - Type: array -- Default: `[]` ### Parameter: `roleAssignments` @@ -474,7 +473,6 @@ An array of subscription IDs to associate to the service group. The deployment p - Required: No - Type: array -- Default: `[]` ## Outputs diff --git a/avm/res/management/service-group/main.bicep b/avm/res/management/service-group/main.bicep index 8903e1c3695..64b8a093d9a 100644 --- a/avm/res/management/service-group/main.bicep +++ b/avm/res/management/service-group/main.bicep @@ -13,10 +13,10 @@ param displayName string? param parentServiceGroupResourceId string? @description('Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs.') -param subscriptionIdsToAssociateToServiceGroup array = [] +param subscriptionIdsToAssociateToServiceGroup string[]? @description('Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs.') -param resourceGroupResourceIdsToAssociateToServiceGroup array = [] +param resourceGroupResourceIdsToAssociateToServiceGroup string[]? @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @@ -97,7 +97,7 @@ resource serviceGroup 'Microsoft.Management/serviceGroups@2024-02-01-preview' = } module serviceGroup_subscriptionMember 'modules/subscriptionMember.bicep' = [ - for sub in subscriptionIdsToAssociateToServiceGroup: { + for sub in (subscriptionIdsToAssociateToServiceGroup ?? []): { scope: subscription(sub) params: { serviceGroupResourceId: serviceGroup.id @@ -106,7 +106,7 @@ module serviceGroup_subscriptionMember 'modules/subscriptionMember.bicep' = [ ] module serviceGroup_resourceGroupMember 'modules/resourceGroupMember.bicep' = [ - for rg in resourceGroupResourceIdsToAssociateToServiceGroup: { + for rg in (resourceGroupResourceIdsToAssociateToServiceGroup ?? []): { scope: resourceGroup(split(rg, '/')[2], split(rg, '/')[4]) params: { serviceGroupResourceId: serviceGroup.id diff --git a/avm/res/management/service-group/main.json b/avm/res/management/service-group/main.json index 1582420d244..8624fadb205 100644 --- a/avm/res/management/service-group/main.json +++ b/avm/res/management/service-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "18094953411067250954" + "templateHash": "2483188741245687532" }, "name": "Service Groups", "description": "This module will allow you to create a service group and also associate resource to this service group, if you have permissions upon those resources." @@ -148,14 +148,20 @@ }, "subscriptionIdsToAssociateToServiceGroup": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { "description": "Optional. An array of subscription IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target subscriptions. The relationship name is generated using uniqueString() function with the service group ID and the subscription ID as inputs." } }, "resourceGroupResourceIdsToAssociateToServiceGroup": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { "description": "Optional. An array of resource group resource IDs to associate to the service group. The deployment principal must have the necessary permissions to perform this action on the target resource groups. The relationship name is generated using uniqueString() function with the service group ID and the resource group resource ID as inputs." } @@ -276,12 +282,12 @@ "serviceGroup_subscriptionMember": { "copy": { "name": "serviceGroup_subscriptionMember", - "count": "[length(parameters('subscriptionIdsToAssociateToServiceGroup'))]" + "count": "[length(coalesce(parameters('subscriptionIdsToAssociateToServiceGroup'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('serviceGroup_subscriptionMember-{0}-{1}', copyIndex(), uniqueString('serviceGroup_subscriptionMember', deployment().name))]", - "subscriptionId": "[parameters('subscriptionIdsToAssociateToServiceGroup')[copyIndex()]]", + "subscriptionId": "[coalesce(parameters('subscriptionIdsToAssociateToServiceGroup'), createArray())[copyIndex()]]", "location": "[deployment().location]", "properties": { "expressionEvaluationOptions": { @@ -333,13 +339,13 @@ "serviceGroup_resourceGroupMember": { "copy": { "name": "serviceGroup_resourceGroupMember", - "count": "[length(parameters('resourceGroupResourceIdsToAssociateToServiceGroup'))]" + "count": "[length(coalesce(parameters('resourceGroupResourceIdsToAssociateToServiceGroup'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('serviceGroup_resourceGroupMember-{0}-{1}', copyIndex(), uniqueString('serviceGroup_resourceGroupMember', deployment().name))]", - "subscriptionId": "[split(parameters('resourceGroupResourceIdsToAssociateToServiceGroup')[copyIndex()], '/')[2]]", - "resourceGroup": "[split(parameters('resourceGroupResourceIdsToAssociateToServiceGroup')[copyIndex()], '/')[4]]", + "subscriptionId": "[split(coalesce(parameters('resourceGroupResourceIdsToAssociateToServiceGroup'), createArray())[copyIndex()], '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('resourceGroupResourceIdsToAssociateToServiceGroup'), createArray())[copyIndex()], '/')[4]]", "properties": { "expressionEvaluationOptions": { "scope": "inner"