TL; DR– Deployment Stacks is a new resource type for managing a collection of Azure resources as a single unit for faster update and delete (cleanup), while also preventing unwanted changes to those resources. Now Generally Available!
The Problem: managing the lifecycle (creates, updates, deletes) of resources across multiple Azure scopes (Resource Group, Management Groups, Subscription) is both complex and time consuming. On top of that, ensuring resources have the proper guard rails in place adds more complexity to the deployment and management of those resources.
First let's review common scenarios of where this added complexity is seen today:
Deployment Stacks will enable users to deploy a collection of resources across scopes as a single atomic unit (Bicep or ARM Template). The deployment stack protects its managed resources against unwanted changes.
The Solution: 1st Party resource enabling 1-to-many CRUD operations and resource change prevention.
A deployment stack is a method of deploy an ARM Template or Bicep file which tracks the resources deployed in a "managedResources" list. Beyond the capabilities of conventional ARM Template or Bicep deployments, there are two main capabilities that deployment stacks bring to Azure:
A deployment stack can be created at different scopes, such as, Resource Group, Subscription, and Management Group scope. To create a deployment stack, we need the following information:
targetScope = 'subscription'
param resourceGroupName1 string = 'testapp-storage'
param resourceGroupName2 string = 'testapp-network'
param resourceGroupLocation string = deployment().location
//Create Resource Groups
resource testrg1 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: resourceGroupName1
location: resourceGroupLocation
}
resource testrg2 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: resourceGroupName2
location: resourceGroupLocation
}
//Create Storage Accounts
module firstStorage 'multistorage.bicep' = if (resourceGroupName1 == 'testapp-storage') {
name: uniqueString(resourceGroupName1)
scope: testrg1
params: {
location: resourceGroupLocation
}
}
//Create Virtual Networks
module firstVnet 'multinetwork.bicep' = if (resourceGroupName2 == 'testapp-network') {
name: uniqueString(resourceGroupName2)
scope: testrg2
params: {
location: resourceGroupLocation
}
}
This file deploys storage account and virtual network to different resource groups.
To help us start visualizing this, let's look at what an Azure CLI command to create deployment stack at subscription scope looks like:
az stack sub create --name "DevTestEnvStack" --template-file "mainAppInfra.bicep" --location "westus2" --action-on-unmanage "deleteResources" --deny-settings-mode "denyDelete"
Here is the response from that command (some stack properties removed for simplicity of example):
{
"actionOnUnmanage": {
"managementGroups": "detach",
"resourceGroups": "detach",
"resources": "delete"
},
"deletedResources": [],
"denySettings": {
"applyToChildScopes": false,
"excludedActions": [],
"excludedPrincipals": null,
"mode": "denyDelete"
},
"deploymentId": "/subscriptions/***/providers/Microsoft.Resources/deployments/DevTestEnvStack-24052002bd5h1",
"detachedResources": [],
"failedResources": [],
"id": "/subscriptions/***/providers/Microsoft.Resources/deploymentStacks/DevTestEnvStack",
"location": "westus2",
"name": "DevTestEnvStack",
"provisioningState": "succeeded",
"resources": [
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-network",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-network/providers/Microsoft.Network/virtualNetworks/testnetbjildrqs4q6ve",
"resourceGroup": "testapp-network",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage/providers/Microsoft.Storage/storageAccounts/teststore1ic7t5vnieyika",
"resourceGroup": "testapp-storage",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage/providers/Microsoft.Storage/storageAccounts/teststore2ic7t5vnieyika",
"resourceGroup": "testapp-storage",
"status": "managed"
}
],
"type": "Microsoft.Resources/deploymentStacks"
}
In this example, a deployment stack named "DevTestEnvStack" was created, and the resulting output of the command shows the details about the deployment stack resource and its managed resources. Note the "status" of each resource as "managed". To refer back to those details, you can use the show command in CLI:
az sub stack show --name "DevTestEnvStack"
The result will contain all information on the specified deployment stacks object, such as, resource ID of the deployment stack, array of managed resources, deny setting configurations, and actionOnUnmanage settings. Let's take a look at "actionOnUnmanage" in particular:
"actionOnUnmanage": {
"managementGroups": "detach",
"resourceGroups": "detach",
"resources": "delete"
}
Given the current configuration, if we were to remove a resource from the mainAppInfra.bicep template, that resource will be deleted by the Deployment Stack. In our example, let's remove the virtual network resource from our template:
targetScope = 'subscription'
param resourceGroupName1 string = 'testapp-storage'
param resourceGroupName2 string = 'testapp-network'
param resourceGroupLocation string = deployment().location
//Create Resource Groups
resource testrg1 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: resourceGroupName1
location: resourceGroupLocation
}
resource testrg2 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: resourceGroupName2
location: resourceGroupLocation
}
//Create Storage Accounts
module firstStorage 'multistorage.bicep' = if (resourceGroupName1 == 'testapp-storage') {
name: uniqueString(resourceGroupName1)
scope: testrg1
params: {
location: resourceGroupLocation
}
}
Now let's we redeploy the deployment stack with the same command:
az stack sub create --name "DevTestEnvStack" --template-file "mainAppInfra.bicep" --location "westus2" --action-on-unmanage "deleteResources" --deny-settings-mode "denyDelete"
Here is the response from that command (some stack properties removed for simplicity of example):
{
"actionOnUnmanage": {
"managementGroups": "detach",
"resourceGroups": "detach",
"resources": "delete"
},
"deletedResources": [
{
"id": "/subscriptions/***/resourceGroups/testapp-network/providers/Microsoft.Network/virtualNetworks/testnet1bjildrqs4q6ve",
"resourceGroup": "testapp-network"
},
{
"id": "/subscriptions/***/resourceGroups/testapp-network/providers/Microsoft.Network/virtualNetworks/testnet2bjildrqs4q6ve",
"resourceGroup": "testapp-network"
}
],
"denySettings": {
"applyToChildScopes": false,
"excludedActions": [],
"excludedPrincipals": null,
"mode": "denyDelete"
},
"deploymentId": "/subscriptions/***/providers/Microsoft.Resources/deployments/DevTestEnvStack-24052317bd010",
"deploymentScope": null,
"description": null,
"detachedResources": [],
"failedResources": [],
"id": "/subscriptions/***/providers/Microsoft.Resources/deploymentStacks/DevTestEnvStack",
"location": "westus2",
"name": "DevTestEnvStack",
"provisioningState": "succeeded",
"resources": [
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-network",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage/providers/Microsoft.Storage/storageAccounts/teststore1ic7t5vnieyika",
"resourceGroup": "testapp-storage",
"status": "managed"
},
{
"denyStatus": "denyDelete",
"id": "/subscriptions/***/resourceGroups/testapp-storage/providers/Microsoft.Storage/storageAccounts/teststore2ic7t5vnieyika",
"resourceGroup": "testapp-storage",
"status": "managed"
}
],
"type": "Microsoft.Resources/deploymentStacks"
}
Note that the virtual network resource is no longer "managed" and can now be seen in the "deletedResources" array property of the deployment stack response. This shows how deployment stacks can be used to easily delete resources by removing them from the template with the appropriate "--actionOnUnmanage" behavior defined.
You can also view the Deployment Stack and its contents in portal by navigating to the specified scope (Subscription for this example) > settings > deployment stacks.
Select the deployment stack "DevTestEnvStack" to view:
Beyond deciding on the behavior for "actionOnUnmanage" it is also important to define what deny settings mode should the deployment stack use. This enables guard-rails to help protect your "managedResources" against unwanted changes. In our initial example, we specified DenyDelete for our deny settings mode. Behind the scenes, our deployment stack resource created a deny assignment for each of its managedResources. This means that other users (not our deployment stack) can make updates/writes to the provisions test storage accounts, but can't delete them, even if they have owner access to that resource and scope.
In some cases, you might need some flexibility or stop gap measure for the deny settings mode. For example, you may want to exclude a particular admin user from the deny assignment, such that they can go and delete the resource manually (outside of the context of a deployment stack), or maybe you want to exclude specific actions for all users (e.g. Still allow all users to perform Writes and Deletes to storage accounts and virtual network resource types). This can be done with the following exclusion parameters for deny settings:
In our example, if we were to decide to exclude a specific user ID and also exclude the ability to delete storage accounts from the deployment stack's deny settings, the command will now look like this:
az stack sub create --name "DevTestEnvStack" --location "westus2" --template-file "mainAppInfra.bicep" --actionOnUnmanage "DeleteAll" --deny-settings-mode "DenyDelete" --deny-settings-excluded-principals "12304812408-2148124081" --deny-settings-excluded-actions "Microsoft.Storage/storageAccounts/delete"
These exclusion flags give you the flexibility to enable access to "managedResources" for specific users or specific actions, while keeping all other "managedResources" secured with deny settings. For more information on deployment stacks, please visit our docs and our GitHub.
Deployment Stacks Docs Reference:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.