Skip to content

Bicep Snapshots does not allow generation of subscription scoped template with management group command #19887

Description

@slavizh

Bicep version
Bicep CLI version 0.44.1 (28275db)

Describe the bug
With Azure PowerShell module you can create Bicep template/module that is scoped to subscription and deploy that template to subscription or management group. The code in the template is structured that way so it does not give errors when you deploy it at management group scope as long as you input values that are applicable for that scope. For example you can have one template that deploys role assignment. With one parameter you define the role assignments at either subscription or management group scope. You can also deploy role assignments at resource group scope. When you deploy at management group scope the logic in the code does not initiates deployment of role assignments at resource group scope if they are not defined in the input. Basically the only differences in input for deploying role assignments at management group scope instead of subscription scope is to provide the management group ID as I cannot take that dynamically within the template. So snapshots throws error if you try to execute template that is scoped to subscription but with management group command:

bicep snapshot "{bicep file}" --management-group-id 'MGMT-GROUP-00' --location westeurope --mode overwrite
Unhandled exception: System.InvalidOperationException: Management group ID cannot be specified for a template of scope Subscription
   at Bicep.Core.Utils.Snapshots.SnapshotHelper.GetDeploymentMetadata(String tenantId, String managementGroupId, String subscriptionId, String resourceGroup, String deploymentName, String location, TemplateDeploymentScope scope, Template template) in C:\__w\1\s\bicep\src\Bicep.Core\Utils\Snapshots\SnapshotHelper.cs:line 219
   at Bicep.Core.Utils.Snapshots.SnapshotHelper.GetSnapshot(ResourceScope targetScope, String templateContent, String parametersContent, String tenantId, String managementGroupId, String subscriptionId, String resourceGroup, String deploymentName, String location, ImmutableArray`1 externalInputs, CancellationToken cancellationToken) in C:\__w\1\s\bicep\src\Bicep.Core\Utils\Snapshots\SnapshotHelper.cs:line 60
   at Bicep.Cli.Commands.SnapshotCommand.GetSnapshot(SnapshotArguments arguments, IOUri inputUri, Boolean noRestore, CancellationToken cancellationToken) in C:\__w\1\s\bicep\src\Bicep.Cli\Commands\SnapshotCommand.cs:line 110
   at Bicep.Cli.Commands.SnapshotCommand.RunAsync(SnapshotArguments args, CancellationToken cancellationToken) in C:\__w\1\s\bicep\src\Bicep.Cli\Commands\SnapshotCommand.cs:line 55
   at Bicep.Cli.Commands.SnapshotCommand.<>c__DisplayClass11_1.<<CreateCommand>b__2>d.MoveNext() in C:\__w\1\s\bicep\src\Bicep.Cli\Commands\SnapshotCommand.cs:line 211
--- End of stack trace from previous location ---
   at Bicep.Cli.Commands.CommandLineBuilderContext.RunCommandAsync(Func`1 action) in C:\__w\1\s\bicep\src\Bicep.Cli\Commands\CommandLineBuilderContext.cs:line 21
   at System.CommandLine.Invocation.InvocationPipeline.InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken)

To Reproduce
Steps to reproduce the behavior:

Below is simplified but reproducible version. From there I have removed the option to provide management group name as parameter as it is not needed for this simplified version:

foo.bicep

targetScope = 'subscription'

@description('PrincipalId to assign role at subscription scope or management group scope or resource group scope.')
param principalId string

param resourceGroupName string = ''

module roleAssignment 'ra.bicep' = if (empty(resourceGroupName)) {
  name: 'roleAssignment'
  params: {
    principalId: principalId
  }
}


module resourceGroupsRoleAssignments 'ra-rg.bicep' = if (!empty(resourceGroupName)) {
  name: 'roleAssignments-${uniqueString(deployment().location)}'
  scope: resourceGroup(resourceGroupName)
  params: {
    principalId: principalId
  }
}

ra.bicep

targetScope = 'subscription'

param principalId string

resource roleAssignmentRes 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: 'a2b04138-d1eb-4b07-9a7b-5b7e3cffd0f9'
  properties: {
    roleDefinitionId: roleDefinitions('Reader').id
    principalId: principalId
  }
}

ra-rg.bicep

param principalId string

resource roleAssignmentRes 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: '0215df3e-aef1-476b-914d-152481051023'
  properties: {
    roleDefinitionId: roleDefinitions('Reader').id
    principalId: principalId
  }
}

You can execute the following commands with the template:

New-AzSubscriptionDeployment -Name test9 -Location 'West Europe' -TemplateFile .\foo.bicep -Verbose -principalId d8f5abbb-1427-4bbe-b599-5430a62f01a0 
New-AzSubscriptionDeployment -Name test9 -Location 'West Europe' -TemplateFile .\foo.bicep -Verbose -principalId d8f5abbb-1427-4bbe-b599-5430a62f01a0 -resourceGroupName identities
New-AzManagementGroupDeployment -Name test3 -ManagementGroupId Sponsor -Location 'West Europe' -TemplateFile .\foo.bicep -principalId d8f5abbb-1427-4bbe-b599-5430a62f01a0

Note you need to change the resource name in rg.bicep before doing deployment at management group scope if you have done deployment at subscription scope as the resource provider does not allow the same name to be used even if the scope is different.

Overall all 3 commands execute fine with the same template. Due to that Bicep Snapshots should also allow that and basically override/ignore the scope based on the scope used in the command.

Note also that official documentation (https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-cli?tabs=bicep-cli#snapshot) lists --management-group as argument but the actual argument is --management-group-id

Additional context
Add any other context about the problem here.

Metadata

Metadata

Labels

Type

No type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions