Reputation: 125
According to the Microsoft Documentation, it is now possible to create Resource Groups and deploy resources to the newly created resource group. There is a small catch though, at the very beginning, we have this disclaimer -
Subscription level deployment is different from resource group deployment in the following aspects:
Schema and commands
The schema and commands you use for subscription-level deployments are different than resource group deployments.
For the schema, use https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#
This throws a major issue, the azuredeploy.json is no longer recognized as the deployment template as it is not using the resource deployment schema (https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#).
So, the other option was to create the Resource Group as a Nested Template and put a dependsOn for the child resources that will be created, this now allowed me to Deploy/Validate the file. However, this possesses a new issue. Even though a dependsOn dictates that the resource group is created, it still fails to recognize this and comes back with an error - resource group could not be found, hence the resources could not be deployed. I tried using a Linked Template (I know this does not make any difference, but still)
Anyone, managed to do this by any chance?
Adding my code.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "North Europe"
},
"FirstResourceGroupName": {
"type": "string",
"defaultValue": "myFirstRG"
},
"FirstBlobStorageName": {
"type": "string",
"defaultValue": "North Europe"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "ResourceGroupDeployment",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('location')]",
"name": "[parameters('FirstResourceGroupName')]",
"properties": {}
}
],
"outputs" : {}
}
}
},
{
//ResourceDeployment
"type": "Microsoft.Resources/deployments",
"name": "StorageDeployment",
"apiVersion": "2017-05-10",
"dependsOn": [
"[concat('Microsoft.Resources/deployments/', 'ResourceGroupDeployment')]"
//"ResourceGroupDeployment"
],
"resourceGroup": "[parameters('FirstResourceGroupName')]",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2017-10-01",
"name": "[parameters('FirstBlobStorageName')]",
"location": "[parameters('location')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
}
}
],
"outputs": {}
}
}
}
],
"outputs": {}
}
Upvotes: 0
Views: 2271
Reputation: 125
The solution proposed by MS is good, when you are not using Visual Studio or Portal for deployment. My major issue was the validation of the template which again will not work for subscription level deployment, as it uses a schema that is not recogonised as an ARM.
It may work as @4c74356b41 suggested through any other means i.e. cli\sdks\rest api, but I did not go down that path.
The other solution I had was to run a powershell script by adding a step on the Azure DevOps pipeline. Which was the closest I came to making this work, but again the validation to check if my deployment would succeed, still was up in the air. I did not want my release pipeline to fail because of an invalid template.
Here is what I have gathered, the reason why the validation failed (even with deploying the RG and using a dependsOn) was because the Resource Groups will not be created until you deploy the template. The template deployment will not happen unless it passes validation as the Resource Groups does not exist. So we are stuck in a loop. The two options are either create them manually on the portal before validating (this defies the point of automation) or use a simple powershell step before validating them. The latter is what I have gone with. I know this is unorthodoxed, but works.... and also validates my template.
NOTE - The solution is different from the original problem, as I have used multiple resource group creation. According to MS documentation, you can have up to 5 RG deployed this way.
First, create a resource group file that will hold the resource groups you'd want to create. It will be just a simple JSON file like,
{
"rgNames":
{
"rg1": { "rg": "resource-group-main" },
"rg2": { "rg": "resource-group-backup" }
}
}
Use the same values you have added to this file as a parameter, so you can use them to deploy resources to.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"ResourceGroups": {
"type": "object",
//If you are changing this value !!!! Please make sure you are also updating the same in the ResourceGroups.ARM.json !!!!
"allowedValues": [
{
"rgNames":
{
"rg1": { "rg": "resource-group-main" },
"rg2": { "rg": "resource-group-backup" }
}
}
]
}
}
Second, change the PS script to include the code where it will loop through the list of resource groups it need to deploy.
# Set '$RGTemplateFile' parameter to be the name of the file you added to your project
$rgFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $RGTemplateFile))
$rgString = Get-Content -Raw -Path $rgFile | ConvertFrom-Json
# helper to turn PSCustomObject into a list of key/value pairs
function Get-ObjectMembers {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[PSCustomObject]$obj
)
$obj | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]@{Key = $key; Value = $obj."$key"}
}
}
$rgValues = $jsonParam | Get-ObjectMembers | foreach {
$_.Value | Get-ObjectMembers | foreach {
[PSCustomObject]@{
RGName = $_.value.rgNames | select -First 1
}
}
}
foreach ($values in $rgValues)
{
New-AzureRmResourceGroup -Name $values.RGName -Location $ResourceGroupLocation -Verbose -Force
}
add the above code, just before where it performs a validation -
if ($ValidateOnly) {
$ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
@OptionalParameters)
:
Finally, change the deployment template file (azuredeploy.json) to do either a nested template deployment or a linked template to deploy resources on the RG you have declared.(I have used Linked, as it looks neater)
"variables": {
"rg1Name": "[parameters('ResourceGroups')['rgNames']['rg1'].rg]",
"rg2Name": "[parameters('ResourceGroups')['rgNames']['rg2'].rg]",
"blob1Name": "[parameters('blob1')]",
"blob2Name": "[parameters('blob2')]",
"arm1": "[concat(parameters('_artifactsLocation'), 'rg1/rg1.ARM.json', parameters('_artifactsLocationSasToken'))]",
"arm2": "[concat(parameters('_artifactsLocation'), 'rg2/rg2.ARM.json', parameters('_artifactsLocationSasToken'))]"
},
"resources": [
{
//RG1 Resources Deployment
"type": "Microsoft.Resources/deployments",
"name": "RG1Resources",
"apiVersion": "2017-05-10",
"resourceGroup": "[variables('rg1Name')]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('arm1')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"blob1Name": {
"value": "[variables('blob1Name')]"
}
}
}
},
{
//RG2 Resources Deployment
"type": "Microsoft.Resources/deployments",
"name": "RG2Resources",
"apiVersion": "2017-05-10",
"resourceGroup": "[variables('rg2Name')]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('arm2')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"blobName": {
"value": "[variables('blob2Name')]"
}
}
}
}
],
"outputs": {}
}
Your rg1.ARM.json and rg2.ARM.json files looks like, obviously one could have more than one resource.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"blobName": {
"type": "string"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('blobName')]",
"kind": "StorageV2",
"apiVersion": "2018-07-01",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"properties": {}
}
],
"outputs": {
}
}
Once this is set up, you will be able to validate the file as the PS script will create the RG's for you before it passes through validation.
Upvotes: 1
Reputation: 72141
Example taken from official documentation:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"parameters": {
"rgName": {
"type": "string"
},
"rgLocation": {
"type": "string"
},
"storagePrefix": {
"type": "string",
"maxLength": 11
}
},
"variables": {
"storageName": "[concat(parameters('storagePrefix'), uniqueString(subscription().id, parameters('rgName')))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('rgLocation')]",
"name": "[parameters('rgName')]",
"properties": {}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('rgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2017-10-01",
"name": "[variables('storageName')]",
"location": "[parameters('rgLocation')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
}
}
]
}
}
}
]
}
does exactly what you need.
Upvotes: 1