BCPRMNFR4 - Multi-scope modules

ID: BCPRMNFR4 - Implementing multi-scope modules

Several resource types in Azure (e.g., role-assignments, budgets, etc.) may be deployed to more than one scope (e.g., subscription, management-group, etc.).
In AVM, such modules can be implemented in one of two ways:

  1. As pattern modules with one ‘orchestrating’ parent module using scoped sub-modules based on the input parameters provided

Note: Only the parent module is published. I.e., it is not possible to target e.g., the resource-group scoped sub-module directly.

  1. As resource modules where each scope is implemented as a child-module of a non-published parent.

Note: Each child module is published, but not the parent. I.e., it is possible to target e.g., the resource-group scoped sub-module directly.

Tip

It is highly recommended to publish multi-scoped modules as resource modules as the solution provides the best user experience.

Considerations when published as a pattern module

Example: avm/ptn/authorization/role-assignment

Note

The following instructions consider all deployment scopes. Your module may only deploy to a subset of the same and you should map the conventions to your case.

To successfully implement a multi-scoped module as a pattern modules you have to adhere to the following convention:

  • The parent module MUST be implemented in the highest scope the resource provider supports (e.g., management-group)
  • The parent module MUST have one sub-module for each scope that the resource provider supports (e.g., management-group, subscription & resource-group)
  • Each sub-module MUST be implemented for the scope it is intended
  • The parent module MUST invoke each sub-module in the scope it is written for, using input parameters needed to target set scope (e.g., a subscription-id to invoke a module for set scope)
  • The parent module MUST have test cases to validate each sub-module
  • The parent module is the one that is versioned, published and maintains a changelog

The full folder structure may look like

📄main.bicep                 [Orchestrating module]
📄main.json                  [ARM JSON file of the module]
📄version.json               [Version file of the module]
📄README.md                  [Readme of the module]
📄CHANGELOG.md               [The changelog of the module]
┣ 📂modules
┃ ┣ 📄management-group.bicep [Sub-module deploying to the mgmt-group scope (if applicable)]
┃ ┣ 📄subscription.bicep     [Sub-module deploying to the subscription scope (if applicable)]
┃ ┗ 📄resource-group.bicep   [Sub-module deploying to the resource-group scope (if applicable)]
┗ 📂tests/e2e
  ┣ 📂 mg.defaults
  ┃ ┗ 📄main.test.bicep      [deploys parent template]
  ┣ 📂 mg.waf-aligned
  ┃ ┗ 📄main.test.bicep      [deploys parent template]
  ┣ 📂 sub.defaults
  ┃ ┗ 📄main.test.bicep      [deploys parent template with `subscriptionId` param]
  ┣ 📂 sub.waf-aligned
  ┃ ┗ 📄main.test.bicep      [deploys parent template with `subscriptionId` param]
  ┣ 📂 rg.defaults
  ┃ ┗ 📄main.test.bicep      [deploys parent template with `subscriptionId` & `resourceGroupName` params]
  ┗ 📂 rg.waf-aligned
    ┗ 📄main.test.bicep      [deploys parent template with `subscriptionId` & `resourceGroupName` params]
Warning

Even if a consumer wants to deploy to one of the sub-scopes (e.g., subscription), the module must be deployed via its parent (e.g., management-group). This can be confusing for consumers at first and should be considered when implementing the solution.

Example: To use a role-assignment pattern module (which would be written for all scopes, with the parent targeting the management-group scope) to deploy role assignments to a resource group, a user would need to invoke New-AzManagementGroupDeployment and provide the parameters for both the subscription & resource-group to target. I.e., the user must have permissions to deploy to each scope.

Considerations when published as a resource module

Example: avm/res/authorization/role-assignment

Note

The following instructions consider all deployment scopes. Your module may only deploy to a subset of the same and you should map the conventions to your case.

To successfully implement a multi-scoped module as a resource modules you have to adhere to the following convention:

  • The parent folder MUST contain a

    • main.bicep file
    • main.json file
    • README.md file
    • tests/e2e folder
    • One folder per each scope the resource provider can deploy to (either mg-scope, sub-scope or rg-scope).
  • Each child-module folder MUST be implemented as a proper child module, with a

    • main.bicep
    • main.json
    • version.json
    • README.md
    • CHANGELOG.md

    file. Each child-module is maintained and versioned independently of the others.

  • The parent main.bicep MUST contain the following information

    metadata name = '<Module Name> (Multi-Scope)'
    metadata description = '''
    This module's child-modules deploy a <Placeholder> at a Management Group (mg-scope), Subscription (sub-scope) or   Resource Group (rg-scope) scope.
    
    > While this template is **not** published, you can find the actual published modules in the subfolders
    > - `mg-scope`
    > - `sub-scope`
    > - `rg-scope`
    '''
    targetScope = 'managementGroup'

    updated with your module’s specifics

  • The tests/e2e folder MUST contain one instance of the require test cases per each scope, and MAY contain any additional test you see fit. In each case, the scope MUST be a prefix for the folder name. Each test case MUST reference the corresponding child module directly.

The full folder structure may look like

📄main.bicep                [Skeleton module with disclaimer referring to the child-modules]
📄main.json                 [ARM JSON file of the module]
📄README.md                 [The baseline readme, surfacing the metadata of the main.bicep file]
┣ 📂mg-scope
┃ ┣📄main.bicep             [Module deploying to mg-scope]
┃ ┣📄main.json              [ARM JSON file of the module]
┃ ┣📄README.md              [Readme of the module]
┃ ┣📄version.json           [Version file of the module]
┃ ┗📄CHANGELOG.md           [The changelog of the module]
┣ 📂sub-scope
┃ ┣📄main.bicep             [Module deploying to sub-scope]
┃ ┣📄main.json              [ARM JSON file of the module]
┃ ┣📄README.md              [Readme of the module]
┃ ┣📄version.json           [Version file of the module]
┃ ┗📄CHANGELOG.md           [The changelog of the module]
┣ 📂rg-scope
┃ ┣📄main.bicep             [Module deploying to rg-scope]
┃ ┣📄main.json              [ARM JSON file of the module]
┃ ┣📄README.md              [Readme of the module]
┃ ┣📄version.json           [Version file of the module]
┃ ┗📄CHANGELOG.md           [The changelog of the module]
┗ 📂tests/e2e
  ┣ 📂mg-scope.defaults
  ┃ ┗📄main.test.bicep      [references the 'mg-scope' child module template: '../../../mg-scope/main.bicep']
  ┣ 📂mg-scope.waf-aligned
  ┃ ┗📄main.test.bicep      [references the 'mg-scope' child module template: '../../../mg-scope/main.bicep']
  ┣ 📂mg-scope.max
  ┃ ┗📄main.test.bicep      [references the 'mg-scope' child module template: '../../../mg-scope/main.bicep']
  ┣ 📂sub-scope.defaults
  ┃ ┗📄main.test.bicep      [references the 'sub-scope' child module template: '../../../sub-scope/main.bicep']
  ┣ 📂sub-scope.waf-aligned
  ┃ ┗📄main.test.bicep      [references the 'sub-scope' child module template: '../../../sub-scope/main.bicep']
  ┣ 📂rg-scope.defaults
  ┃ ┗📄main.test.bicep      [references the 'rg-scope' child module template: '../../../rg-scope/main.bicep']
  ┗ 📂rg-scope.waf-aligned
    ┗📄main.test.bicep      [references the 'rg-scope' child module template: '../../../rg-scope/main.bicep']
Important

Because each child-module is published on its own, you must ensure that each is registered in the MAR file before the modules can be published. The MAR file can only be accessed by Microsoft FTEs.

Please highlight the nature of your module in the issue when proposing it to AVM.