I want to build a few components of the Azure landing zone architecture using Bicep. The components I am trying to build are the following from here:
When i run a what-if, I get the following error:
InvalidTemplateDeployment - The template deployment failed with error: 'Subscription id: '/providers/Microsoft.Subscription/aliases/93cce83f-cae0-402e-8d0d-f4e9b4c12ec2' does not exist. Please make sure nested deployments are referencing a valid 'subscriptionId' property.'.
I'm sure it has something to do with the line
scope:subscription(managementSubscription.id)
but I am not sure how else to specify this. I know the subscription doesn't exist yet, but it is being created in the script so I am expecting Bicep to understand that.
All I really want to do is create a resource group in a subscription using Bicep. The Azure Landing Zone part is just for information. It is not an Azure Landing Zone issue and I am only giving that information because of the already existing documentation and diagrams describing what I am trying to achieve.
main.bicep
targetScope = 'tenant'
@description('The display name for the management management group')
param managementManagementGroupDisplayName string
@description('A unique identifier for the organization management group')
param managementManagementGroupIdentifier string
@description('The display name for the management subscription')
param managementSubscriptionDisplayName string
@description('The display name for the organization management group')
param organizationManagementGroupDisplayName string
@description('A unique identifier for the organization management group')
param organizationManagementGroupIdentifier string
@description('The display name for the platform management group')
param platformManagementGroupDisplayName string
@description('A unique identifier for the platform management group')
param platformManagementGroupIdentifier string
@description('The unique identifier for the tenant root group')
param tenantRootGroupIdentifier string
@description('The Azure region in which the terraform support resource group will be provisioned.')
param terraformSupportResourceGroupLocation string
@description('The name of the resource group that will contain terraform support resources.')
param terraformSupportResourceGroupName string
// create organization management group directly under tenant root group
module organizationManagementGroup './modules/managementgroup.bicep' = {
name: 'managementGroupDeploy-${organizationManagementGroupIdentifier}'
params: {
displayName: organizationManagementGroupDisplayName
identifier: organizationManagementGroupIdentifier
parentGroupIdentifier: tenantResourceId('Microsoft.Management/managementGroups', tenantRootGroupIdentifier)
}
}
// create platform management group directly under organization group
module platformManagementGroup './modules/managementgroup.bicep' = {
name: 'managementGroupDeploy-${platformManagementGroupIdentifier}'
params: {
displayName: platformManagementGroupDisplayName
identifier: platformManagementGroupIdentifier
parentGroupIdentifier: organizationManagementGroup.outputs.groupIdentifier
}
}
// create management management group directly under platform group
module managementManagementGroup './modules/managementgroup.bicep' = {
name: 'managementGroupDeploy-${managementManagementGroupIdentifier}'
params: {
displayName: managementManagementGroupDisplayName
identifier: managementManagementGroupIdentifier
parentGroupIdentifier: platformManagementGroup.outputs.groupIdentifier
}
}
// create management subscription
resource managementSubscription 'Microsoft.Subscription/aliases@2021-10-01' = {
name: '93cce83f-cae0-402e-8d0d-f4e9b4c12ec2'
properties: {
workload: 'Production '
displayName: managementSubscriptionDisplayName
billingScope: '/billingAccounts/{mybillingaccountid}/enrollmentAccounts/{myenrollmentaccountid}'
}
}
// resource group for terraform remote state storage account
module terraformSupportResourceGroup './modules/resourcegroup.bicep' = {
name: 'resourceGroupDeploy-${terraformSupportResourceGroupName}'
scope:subscription(managementSubscription.id)
params: {
location: terraformSupportResourceGroupLocation
name: terraformSupportResourceGroupName
}
}
managementgroup.bicep
targetScope = 'tenant'
param displayName string
param identifier string
param parentGroupIdentifier string
resource managementGroup 'Microsoft.Management/managementGroups@2020-02-01' = {
name: identifier
scope: tenant()
properties: {
displayName: displayName
details: {
parent: {
id: parentGroupIdentifier
}
}
}
}
resourcegroup.bicep
targetScope = 'subscription'
param location string
param name string
resource rg 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: name
location: location
}
You understood right that the subscription is not created, but the deployment order is not guaranteed, when Bicep performs a "what-if" operation, it evaluates the template without actually creating the resources, which means it's treating managementSubscription as if it hasn't been created yet. Its always best to create the sub before anything else, in a stateful way. Or properly set dependencies (external or internal) as the below document indicates.
You either separate the deployment of terraformSupportResourceGroup: Move the deployment of the terraformSupportResourceGroup to a separate Bicep template. This will ensure that the resource group deployment doesn't depend on the managementSubscription resource in the same template.
Or you use different bicep files for different components, i.e. split your components into different bicep files (one for management groups, one for subscriptions, etc.) Its good to do this in bicep scenarios when there is a dependency. I would always have my subscription script separate and run first.
For dependencies, refer here: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/resource-dependencies (This doc talks about how to set dependencies if you want to deploy them in one file)