Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new modules for MGs & Subscription Alias with targetScope = MG (Non-Breaking) #651

Merged
merged 11 commits into from
Oct 9, 2023
38 changes: 29 additions & 9 deletions accelerator/pipeline-scripts/Deploy-ALZManagementGroups.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
param (
[Parameter()]
[String]$NonRootParentManagementGroupId = "$($env:NONROOTPARENTMANAGEMENTGROUPID)",

[Parameter()]
[String]$Location = "$($env:LOCATION)",

[Parameter()]
[String]$TemplateFile = "upstream-releases\$($env:UPSTREAM_RELEASE_VERSION)\infra-as-code\bicep\modules\managementGroups\managementGroups.bicep",
[String]$TemplateFile = "upstream-releases\$($env:UPSTREAM_RELEASE_VERSION)\infra-as-code\bicep\modules\managementGroups\",

[Parameter()]
[String]$TemplateParameterFile = "config\custom-parameters\managementGroups.parameters.all.json",
Expand All @@ -13,13 +16,30 @@ param (
)

# Parameters necessary for deployment
$inputObject = @{
DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
Location = $Location
TemplateFile = $TemplateFile
TemplateParameterFile = $TemplateParameterFile
WhatIf = $WhatIfEnabled
Verbose = $true

if ($NonRootParentManagementGroupId -eq '') {
$inputObject = @{
DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
Location = $Location
TemplateFile = $TemplateFile + "managementGroups.bicep"
TemplateParameterFile = $TemplateParameterFile
WhatIf = $WhatIfEnabled
Verbose = $true
}

New-AzTenantDeployment @inputObject
}

New-AzTenantDeployment @inputObject
if ($NonRootParentManagementGroupId -ne '') {
$inputObject = @{
ManagementGroupId = $NonRootParentManagementGroupId
DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
Location = $Location
TemplateFile = $TemplateFile + "managementGroupsScopeEscape.bicep"
TemplateParameterFile = $TemplateParameterFile
WhatIf = $WhatIfEnabled
Verbose = $true
}

New-AzManagementGroupDeployment @inputObject
}
43 changes: 39 additions & 4 deletions infra-as-code/bicep/CRML/subscriptionAlias/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Module: Subscription Alias

> **IMPORTANT:** We recommend moving to using the [Bicep Subscription Vending Module](https://aka.ms/sub-vending/bicep) instead of this module!
> ⚠️⚠️ **IMPORTANT:** We recommend moving to using the [Bicep Subscription Vending Module](https://aka.ms/sub-vending/bicep) instead of this module! ⚠️⚠️

The Subscription Alias module deploys an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA as documented in [Create Azure subscriptions programmatically](https://learn.microsoft.com/azure/cost-management-billing/manage/programmatically-create-subscription).

Expand All @@ -10,7 +10,8 @@ The Subscription will be created and placed under the Tenant Root Group, unless

## Parameters

- [Parameters for Azure Commercial Cloud](generateddocs/subscriptionAlias.bicep.md)
- [Parameters for `subscriptionAlias.bicep` Azure Commercial Cloud](generateddocs/subscriptionAlias.bicep.md)
- [Parameters for `subscriptionAliasScopeEscape.bicep` Azure Commercial Cloud](generateddocs/subscriptionAliasScopeEscape.bicep.md)

## Outputs

Expand All @@ -29,7 +30,8 @@ In this example, the Subscription is created upon an EA Account through a tenant

> For the below examples we assume you have downloaded or cloned the Git repo as-is and are in the root of the repository as your selected directory in your terminal of choice.

### Azure CLI
### Azure CLI - `subscriptionAlias.bicep`

```bash

dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
Expand All @@ -41,7 +43,23 @@ TEMPLATEFILE="infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep
az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS
```

### PowerShell
### Azure CLI - `subscriptionAliasScopeEscape.bicep`

Use this module if you do not want to grant Tenant Root Management Group Deployment permissions.

```bash

dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
NAME="alz-SubscriptionAlias-${dateYMD}"
LOCATION="eastus"
PARAMETERS="@infra-as-code/bicep/CRML/subscriptionAlias/parameters/subscriptionAlias.parameters.all.json"
TEMPLATEFILE="infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep"
MGID="alz"

az deployment mg create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID
```

### PowerShell - `subscriptionAlias.bicep`

```powershell

Expand All @@ -55,6 +73,23 @@ $inputObject = @{
New-AzTenantDeployment @inputObject
```

### PowerShell - `subscriptionAliasScopeEscape.bicep`

Use this module if you do not want to grant Tenant Root Management Group Deployment permissions.

```powershell

$inputObject = @{
DeploymentName = 'alz-SubscriptionAlias-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
TemplateParameterFile = 'infra-as-code/bicep/CRML/subscriptionAlias/parameters/subscriptionAlias.parameters.all.json'
Location = 'EastUS'
TemplateFile = "infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep"
ManagementGroupId = 'alz'
}

New-AzManagementGroupDeployment @inputObject
```

### Output Screenshot

![Example Deployment Output](media/exampleDeploymentOutput.png "Example Deployment Output")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# ALZ Bicep CRML - Subscription Alias Module with Scope Escape

Module to deploy an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access.

## Parameters

Parameter name | Required | Description
-------------- | -------- | -----------
parSubscriptionName | Yes | Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only.
parSubscriptionBillingScope | Yes | The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in.
parTags | No | Tags you would like to be applied.
parManagementGroupId | No | The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional)
parSubscriptionOwnerId | No | The object ID of a responsible user, Microsoft Entra group or service principal. (Optional)
parSubscriptionOfferType | No | The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production
parTenantId | No | The ID of the tenant. Defaults to = tenant().tenantId

### parSubscriptionName

![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)

Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only.

### parSubscriptionBillingScope

![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)

The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in.

### parTags

![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)

Tags you would like to be applied.

### parManagementGroupId

![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)

The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional)

### parSubscriptionOwnerId

![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)

The object ID of a responsible user, Microsoft Entra group or service principal. (Optional)

### parSubscriptionOfferType

![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)

The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production

- Default value: `Production`

- Allowed values: `DevTest`, `Production`

### parTenantId

![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)

The ID of the tenant. Defaults to = tenant().tenantId

- Default value: `[tenant().tenantId]`

## Outputs

Name | Type | Description
---- | ---- | -----------
outSubscriptionName | string |
outSubscriptionId | string |

## Snippets

### Parameter file

```json
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"template": "infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.json"
},
"parameters": {
"parSubscriptionName": {
"value": ""
},
"parSubscriptionBillingScope": {
"value": ""
},
"parTags": {
"value": {}
},
"parManagementGroupId": {
"value": ""
},
"parSubscriptionOwnerId": {
"value": ""
},
"parSubscriptionOfferType": {
"value": "Production"
},
"parTenantId": {
"value": "[tenant().tenantId]"
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ resource resSubscription 'Microsoft.Subscription/aliases@2021-10-01' = {
properties: {
additionalProperties: {
tags: parTags
managementGroupId: empty(parManagementGroupId) ? null : managementGroup(parManagementGroupId)
managementGroupId: empty(parManagementGroupId) ? null : '/providers/Microsoft.Management/managementGroups/${parManagementGroupId}'
subscriptionOwnerId: empty(parSubscriptionOwnerId) ? null : parSubscriptionOwnerId
subscriptionTenantId: parTenantId
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
targetScope = 'managementGroup'

metadata name = 'ALZ Bicep CRML - Subscription Alias Module with Scope Escape'
metadata description = 'Module to deploy an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access.'

@sys.description('Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only.')
param parSubscriptionName string

@sys.description('The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in.')
param parSubscriptionBillingScope string

@sys.description('Tags you would like to be applied.')
param parTags object = {}

@sys.description('The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional)')
param parManagementGroupId string = ''

@sys.description('The object ID of a responsible user, Microsoft Entra group or service principal. (Optional)')
param parSubscriptionOwnerId string = ''

@allowed([
'DevTest'
'Production'
])
@sys.description('The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production')
param parSubscriptionOfferType string = 'Production'

@sys.description('The ID of the tenant. Defaults to = tenant().tenantId')
param parTenantId string = tenant().tenantId

resource resSubscription 'Microsoft.Subscription/aliases@2021-10-01' = {
scope: tenant()
name: parSubscriptionName
properties: {
additionalProperties: {
tags: parTags
managementGroupId: empty(parManagementGroupId) ? null : '/providers/Microsoft.Management/managementGroups/${parManagementGroupId}'
subscriptionOwnerId: empty(parSubscriptionOwnerId) ? null : parSubscriptionOwnerId
subscriptionTenantId: parTenantId
}
displayName: parSubscriptionName
billingScope: parSubscriptionBillingScope
workload: parSubscriptionOfferType
}
}

output outSubscriptionName string = resSubscription.name
output outSubscriptionId string = resSubscription.properties.subscriptionId
67 changes: 64 additions & 3 deletions infra-as-code/bicep/modules/managementGroups/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ The Management Groups module deploys a management group hierarchy in a customer'

## Parameters

- [Link to Parameters](generateddocs/managementGroups.bicep.md)
- [Link to `managementGroup.bicep` Parameters](generateddocs/managementGroups.bicep.md)
- [Link to `managementGroupsScopeEscape.bicep` Parameters](generateddocs/managementGroupsScopeEscape.bicep.md)

### Child Platform & Landing Zone Management Groups Flexibility

Expand Down Expand Up @@ -141,7 +142,7 @@ In this example, the management groups are created at the `Tenant Root Group` th

> For the examples below we assume you have downloaded or cloned the Git repo as-is and are in the root of the repository as your selected directory in your terminal of choice.

### Azure CLI
### Azure CLI - `managementGroups.bicep`

```bash
# For Azure global regions
Expand All @@ -167,7 +168,37 @@ PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementG
az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS
```

### PowerShell
### Azure CLI - `managementGroupsScopeEscape.bicep`

Use this module if you do not want to grant Tenant Root Management Group Deployment permissions.

```bash
# For Azure global regions

dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
NAME="alz-MGDeployment-${dateYMD}"
LOCATION="eastus"
TEMPLATEFILE="infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep"
PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json"
MGID="alz"

az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID
```
OR
```bash
# For Azure China regions

dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
NAME="alz-MGDeployment-${dateYMD}"
LOCATION="chinaeast2"
TEMPLATEFILE="infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep"
PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json"
MGID="alz"

az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID
```

### PowerShell - `managementGroups.bicep`

```powershell
# For Azure global regions
Expand All @@ -193,6 +224,36 @@ $inputObject = @{
New-AzTenantDeployment @inputObject
```

### PowerShell - `managementGroupsScopeEscape.bicep`

Use this module if you do not want to grant Tenant Root Management Group Deployment permissions.

```powershell
# For Azure global regions

$inputObject = @{
DeploymentName = 'alz-MGDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
Location = 'EastUS'
TemplateFile = "infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep"
TemplateParameterFile = 'infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json'
ManagementGroupId = 'alz'
}
New-AzManagementGroupDeployment @inputObject
```
OR
```powershell
# For Azure China regions

$inputObject = @{
DeploymentName = 'alz-MGDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
Location = 'chinaeast2'
TemplateFile = "infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep"
TemplateParameterFile = 'infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json'
ManagementGroupId = 'alz'
}
New-AzManagementGroupDeployment @inputObject
```

![Example Deployment Output](media/exampleDeploymentOutput.png "Example Deployment Output")

## Bicep Visualizer
Expand Down
Loading
Loading