diff --git a/.github/scripts/Register-SubResourceProviders.ps1 b/.github/scripts/Register-SubResourceProviders.ps1 new file mode 100644 index 00000000..58b7dcde --- /dev/null +++ b/.github/scripts/Register-SubResourceProviders.ps1 @@ -0,0 +1,89 @@ +param( + [string]$subscriptionId, + [string]$resourceProviders, + [string]$resourceProvidersFeatures +) + +$ErrorActionPreference = "SilentlyContinue" +# Selecting the right subscription +Select-AzSubscription -SubscriptionId $subscriptionId + +# Defining variables +$providers = $resourceProviders | ConvertFrom-Json +$features = $resourceProvidersFeatures | ConvertFrom-Json +$failedProviders = "" +$failedFeatures = "" +$DeploymentScriptOutputs = @{} + +######################################### +## Registering the resource providers +######################################### + +foreach ($provider in $providers ) { + try { + $providerStatus = (Get-AzResourceProvider -ListAvailable | Where-Object ProviderNamespace -eq $provider).registrationState + # Check if the providered is registered + if ($providerStatus -ne 'Registered') { + Write-Output "`n Registering the '$provider' provider" + if (Register-AzResourceProvider -ProviderNamespace $provider) { + Write-Output "`n The '$provider' has been registered successfully" + } + else { + Write-Output "`n The '$provider' provider has not been registered successfully" + $failedProviders += ",$provider" + } + } + if ($failedProviders.length -gt 0) { + $output = $failedProviders.substring(1) + } + else { + $output = "N/A" + } + $DeploymentScriptOutputs["failedProviderRegistrations"] = $output + } + catch { + Write-Output "`n There was a problem registering the '$provider' provider. Please make sure this provider namespace is valid" + } +} + +################################################## +## Registering the resource providers features +################################################## + +if ($features.length -gt 0) { + foreach ($feature in $features) { + # Define variables + try { + $feature = (Get-AzProviderFeature -ListAvailable | Where-Object FeatureName -eq $feature) + $featureName = $feature.FeatureName + $featureStatus = $feature.RegistrationState + $featureProvider = $feature.ProviderName + # Check if the feature is registered + if ($featureStatus -eq 'NotRegistered') { + Write-Output "`n Registering the '$featureName' feature" + # Check if the feature's resource provider is registered, if not then register first + $providerStatus = (Get-AzResourceProvider -ListAvailable | Where-Object ProviderNamespace -eq $featureProvider).RegistrationState + if ($providerStatus -ne 'Registered') { + if (Register-AzResourceProvider -ProviderNamespace $featureProvider) { + Write-Output "`n The '$featureProvider' has been registered successfully" + Register-AzProviderFeature -FeatureName $featureName -ProviderNamespace $featureProvider + } + else { + Write-Output "`n The '$featureName' feature has not been registered successfully" + $failedFeatures += ",$featureName" + } + } + } + if ($failedFeatures.length -gt 0) { + $output = $failedFeatures.substring(1) + } + else { + $output = "N/A" + } + $DeploymentScriptOutputs["failedFeaturesRegistrations"] = $output + } + catch { + Write-Output "`n There was a problem registering the '$featureName' feature. Please make sure this feature name is valid" + } + } +} diff --git a/README.md b/README.md index 96a5ec97..0b860419 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ This is currently split logically into the following capabilities: - Specify Custom DNS Servers - Role assignments - Tags +- Resource providers and resource providers features registration > When creating Virtual Network peerings, be aware of the [limit of peerings per Virtual Network.](https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits?toc=%2Fazure%2Fvirtual-network%2Ftoc.json#azure-resource-manager-virtual-networking-limits) diff --git a/docs/wiki/Example-5-Hub-and-Spoke-With-RP-registration.md b/docs/wiki/Example-5-Hub-and-Spoke-With-RP-registration.md new file mode 100644 index 00000000..1e3fea01 --- /dev/null +++ b/docs/wiki/Example-5-Hub-and-Spoke-With-RP-registration.md @@ -0,0 +1,190 @@ + +## Example 5 - Landing Zone (Subscription) with a spoke Virtual Network peered to a Hub Virtual Network and resource providers and features registration + +### Bicep Module Registry + +Here is a simple example Bicep file for deploying a landing zone (Subscription) with a spoke Virtual Network peered to a Hub Virtual Network, resource providers and features registration using the [Bicep Module Registry](https://github.com/Azure/bicep-registry-modules): + +```bicep +targetScope = 'managementGroup' + +@description('Specifies the location for resources.') +param location string = 'uksouth' + +module sub003 'br/public:lz/sub-vending:1.4.1' = { + name: 'sub-bicep-lz-vending-example-001' + params: { + subscriptionAliasEnabled: true + subscriptionBillingScope: '/providers/Microsoft.Billing/billingAccounts/1234567/enrollmentAccounts/123456' + subscriptionAliasName: 'sub-bicep-lz-vending-example-001' + subscriptionDisplayName: 'sub-bicep-lz-vending-example-001' + subscriptionTags: { + test: 'true' + } + subscriptionWorkload: 'Production' + subscriptionManagementGroupAssociationEnabled: true + subscriptionManagementGroupId: 'alz-landingzones-corp' + virtualNetworkEnabled: true + virtualNetworkLocation: location + virtualNetworkResourceGroupName: 'rsg-${location}-net-001' + virtualNetworkName: 'vnet-${location}-001' + virtualNetworkAddressSpace: [ + '10.0.0.0/16' + ] + virtualNetworkResourceGroupLockEnabled: false + virtualNetworkPeeringEnabled: true + hubNetworkResourceId: '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rsg-uks-net-hub-001/providers/Microsoft.Network/virtualNetworks/vnet-uks-hub-001' + resourceProviders : [ + 'Microsoft.Compute' + 'Microsoft.AVS' + ] + resourceProvidersFeatures: [ + 'AzureServicesVm' + 'InGuestHotPatchVMPreview' + ] + } +} +``` + +### ARM JSON Parameter File + +Here is a simple example parameter file for deploying a landing zone (Subscription) with a spoke Virtual Network peered to a Hub Virtual Network, resource providers and features registration: + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "subscriptionAliasEnabled": { + "value": true + }, + "subscriptionDisplayName": { + "value": "sub-bicep-lz-vending-example-001" + }, + "subscriptionAliasName": { + "value": "sub-bicep-lz-vending-example-001" + }, + "subscriptionBillingScope": { + "value": "providers/Microsoft.Billing/billingAccounts/1234567/enrollmentAccounts/123456" + }, + "subscriptionWorkload": { + "value": "Production" + }, + "existingSubscriptionId": { + "value": "" + }, + "subscriptionManagementGroupAssociationEnabled": { + "value": true + }, + "subscriptionManagementGroupId": { + "value": "alz-landingzones-corp" + }, + "subscriptionTags": { + "value": { + "Cost-Center": "ABC123", + "Usage": "Example" + } + }, + "virtualNetworkEnabled": { + "value": true + }, + "virtualNetworkResourceGroupName": { + "value": "rg-networking-001" + }, + "virtualNetworkResourceGroupTags": { + "value": { + "Cost-Center": "ABC123", + "Usage": "Example", + "Managed-By": "Platform Team" + } + }, + "virtualNetworkResourceGroupLockEnabled": { + "value": true + }, + "virtualNetworkLocation": { + "value": "uksouth" + }, + "virtualNetworkName": { + "value": "vnet-example-001" + }, + "virtualNetworkTags": { + "value": { + "Cost-Center": "ABC123", + "Usage": "Example", + "Managed-By": "Platform Team" + } + }, + "virtualNetworkAddressSpace": { + "value": [ + "10.0.0.0/16" + ] + }, + "virtualNetworkDnsServers": { + "value": [ + "10.4.1.4", + "10.2.1.5" + ] + }, + "virtualNetworkDdosPlanId": { + "value": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-hub-network-001/providers/Microsoft.Network/ddosProtectionPlans/ddos-001" + }, + "virtualNetworkPeeringEnabled": { + "value": true + }, + "hubNetworkResourceId": { + "value": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-hub-network-001/providers/Microsoft.Network/virtualNetworks/vnet-hub-001" + }, + "virtualNetworkUseRemoteGateways": { + "value": true + }, + "virtualNetworkVwanAssociatedRouteTableResourceId": { + "value": "" + }, + "virtualNetworkVwanPropagatedRouteTablesResourceIds": { + "value": [] + }, + "virtualNetworkVwanPropagatedLabels": { + "value": [] + }, + "roleAssignmentEnabled": { + "value": true + }, + "roleAssignments": { + "value": [ + { + "principalId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "definition": "Contributor", + "relativeScope": "" + }, + { + "principalId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", + "definition": "/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "relativeScope": "" + }, + { + "principalId": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz", + "definition": "Reader", + "relativeScope": "/resourceGroups/rg-networking-001" + } + ] + }, + "resourceProviders":{ + "value":[ + "Microsoft.Compute", + "Microsoft.AVS" + ] + }, + "resourceProvidersFeatures":{ + "value":[ + "AzureServicesVm", + "InGuestHotPatchVMPreview" + ] + }, + "disableTelemetry": { + "value": false + } + } +} +``` + +Back to [Examples](Examples) diff --git a/docs/wiki/Examples.md b/docs/wiki/Examples.md index 8d2fcbf1..8e09f8e0 100644 --- a/docs/wiki/Examples.md +++ b/docs/wiki/Examples.md @@ -5,9 +5,10 @@ Here are some example configurations that demonstrate the module usage. | Example | Description | | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| [Hub & Spoke](Example-1-Hub-and-Spoke) | Example of how to create a landing zone (Subscription) with with a spoke Virtual Network peered to a Hub Virtual Network | +| [Hub & Spoke](Example-1-Hub-and-Spoke) | Example of how to create a landing zone (Subscription) with a spoke Virtual Network peered to a Hub Virtual Network | | [Virtual WAN](Example-2-Virtual-WAN) | Example of how to create a landing zone (Subscription) with a spoke Virtual Network connected to a Virtual WAN Hub | | [Use with existing subscriptions](Example-3-Use-With-Existing-Subscriptions) | Example of how to use this module with existing landing zone Subscriptions | | [Multiple Virtual Networks in Single Subscription](Example-4-Multiple-VNets-In-Same-Subscription) | Example of how to create a landing zone (Subscription) with multiple spoke Virtual Networks | +| [Hub & Spoke with resource providers and resource providers features registration](Example-5-Hub-and-Spoke-With-RP-registration) | Example of how to create a landing zone (Subscription) with a spoke Virtual Network peered to a Hub Virtual Network, register resource providers and resource providers features | Before deploying, review the [Consumer Guide](https://github.com/azure/bicep-lz-vending/wiki/consumerguide) for guidance on how to consume this module. diff --git a/main.bicep b/main.bicep index 56f00602..5c872956 100644 --- a/main.bicep +++ b/main.bicep @@ -441,6 +441,177 @@ For more information on the telemetry collected by this module, that is controll ''') param disableTelemetry bool = false +@sys.description('Random guid for the resources names.') +param guid string = substring(newGuid(),0,4) + +@sys.description('The name of the resource group to create the deployment script for resource providers registration.') +param deploymentScriptResourceGroupName string = 'rsg-${deployment().location}-ds-${guid}' + +@sys.description('The name of the deployment script to register resource providers') +param deploymentScriptName string = 'ds-${deployment().location}-${guid}' + +@sys.description('The name of the user managed identity for the resource providers registration deployment script.') +param deploymentScriptManagedIdentityName string = 'id-${deployment().location}-${guid}' + + +@metadata({ + example: [ + 'Microsoft.Compute' + 'Microsoft.Storage' + ] +}) +@sys.description('''An array of resource providers features to register. If left blank/empty, the default resource providers will be registered. +- Type: `[]` Array +- Default value: `[ + 'Microsoft.ApiManagement' + 'Microsoft.AppPlatform' + 'Microsoft.Authorization' + 'Microsoft.Automation' + 'Microsoft.AVS' + 'Microsoft.Blueprint' + 'Microsoft.BotService' + 'Microsoft.Cache' + 'Microsoft.Cdn' + 'Microsoft.CognitiveServices' + 'Microsoft.Compute' + 'Microsoft.ContainerInstance' + 'Microsoft.ContainerRegistry' + 'Microsoft.ContainerService' + 'Microsoft.CostManagement' + 'Microsoft.CustomProviders' + 'Microsoft.Databricks' + 'Microsoft.DataLakeAnalytics' + 'Microsoft.DataLakeStore' + 'Microsoft.DataMigration' + 'Microsoft.DataProtection' + 'Microsoft.DBforMariaDB' + 'Microsoft.DBforMySQL' + 'Microsoft.DBforPostgreSQL' + 'Microsoft.DesktopVirtualization' + 'Microsoft.Devices' + 'Microsoft.DevTestLab' + 'Microsoft.DocumentDB' + 'Microsoft.EventGrid' + 'Microsoft.EventHub' + 'Microsoft.HDInsight' + 'Microsoft.HealthcareApis' + 'Microsoft.GuestConfiguration' + 'Microsoft.KeyVault' + 'Microsoft.Kusto' + 'microsoft.insights' + 'Microsoft.Logic' + 'Microsoft.MachineLearningServices' + 'Microsoft.Maintenance' + 'Microsoft.ManagedIdentity' + 'Microsoft.ManagedServices' + 'Microsoft.Management' + 'Microsoft.Maps' + 'Microsoft.MarketplaceOrdering' + 'Microsoft.Media' + 'Microsoft.MixedReality' + 'Microsoft.Network' + 'Microsoft.NotificationHubs' + 'Microsoft.OperationalInsights' + 'Microsoft.OperationsManagement' + 'Microsoft.PolicyInsights' + 'Microsoft.PowerBIDedicated' + 'Microsoft.Relay' + 'Microsoft.RecoveryServices' + 'Microsoft.Resources' + 'Microsoft.Search' + 'Microsoft.Security' + 'Microsoft.SecurityInsights' + 'Microsoft.ServiceBus' + 'Microsoft.ServiceFabric' + 'Microsoft.Sql' + 'Microsoft.Storage' + 'Microsoft.StreamAnalytics' + 'Microsoft.TimeSeriesInsights' + 'Microsoft.Web' +]` +''') +@sys.description('Supply an array of resource providers to register.') +param resourceProviders array = [ + 'Microsoft.ApiManagement' + 'Microsoft.AppPlatform' + 'Microsoft.Authorization' + 'Microsoft.Automation' + 'Microsoft.AVS' + 'Microsoft.Blueprint' + 'Microsoft.BotService' + 'Microsoft.Cache' + 'Microsoft.Cdn' + 'Microsoft.CognitiveServices' + 'Microsoft.Compute' + 'Microsoft.ContainerInstance' + 'Microsoft.ContainerRegistry' + 'Microsoft.ContainerService' + 'Microsoft.CostManagement' + 'Microsoft.CustomProviders' + 'Microsoft.Databricks' + 'Microsoft.DataLakeAnalytics' + 'Microsoft.DataLakeStore' + 'Microsoft.DataMigration' + 'Microsoft.DataProtection' + 'Microsoft.DBforMariaDB' + 'Microsoft.DBforMySQL' + 'Microsoft.DBforPostgreSQL' + 'Microsoft.DesktopVirtualization' + 'Microsoft.Devices' + 'Microsoft.DevTestLab' + 'Microsoft.DocumentDB' + 'Microsoft.EventGrid' + 'Microsoft.EventHub' + 'Microsoft.HDInsight' + 'Microsoft.HealthcareApis' + 'Microsoft.GuestConfiguration' + 'Microsoft.KeyVault' + 'Microsoft.Kusto' + 'microsoft.insights' + 'Microsoft.Logic' + 'Microsoft.MachineLearningServices' + 'Microsoft.Maintenance' + 'Microsoft.ManagedIdentity' + 'Microsoft.ManagedServices' + 'Microsoft.Management' + 'Microsoft.Maps' + 'Microsoft.MarketplaceOrdering' + 'Microsoft.Media' + 'Microsoft.MixedReality' + 'Microsoft.Network' + 'Microsoft.NotificationHubs' + 'Microsoft.OperationalInsights' + 'Microsoft.OperationsManagement' + 'Microsoft.PolicyInsights' + 'Microsoft.PowerBIDedicated' + 'Microsoft.Relay' + 'Microsoft.RecoveryServices' + 'Microsoft.Resources' + 'Microsoft.Search' + 'Microsoft.Security' + 'Microsoft.SecurityInsights' + 'Microsoft.ServiceBus' + 'Microsoft.ServiceFabric' + 'Microsoft.Sql' + 'Microsoft.Storage' + 'Microsoft.StreamAnalytics' + 'Microsoft.TimeSeriesInsights' + 'Microsoft.Web' +] + + +@metadata({ + example: [ + 'InGuestPatchVMPreview' + 'LiveResize' + ] +}) +@sys.description('''An array of resource providers features to register. If left blank/empty, no features will be registered. +- Type: `[]` Array +- Default value: `[]` *(empty array)* +''') +param resourceProvidersFeatures array = [] + // VARIABLES var existingSubscriptionIDEmptyCheck = empty(existingSubscriptionId) ? 'No Subscription ID Provided' : existingSubscriptionId @@ -508,6 +679,11 @@ module createSubscriptionResources 'src/self/subResourceWrapper/deploy.bicep' = roleAssignmentEnabled: roleAssignmentEnabled roleAssignments: roleAssignments disableTelemetry: disableTelemetry + deploymentScriptResourceGroupName: deploymentScriptResourceGroupName + deploymentScriptName: deploymentScriptName + deploymentScriptManagedIdentityName: deploymentScriptManagedIdentityName + resourceProviders: resourceProviders + resourceProvidersFeatures: resourceProvidersFeatures } } @@ -524,3 +700,9 @@ output subscriptionAcceptOwnershipState string = (subscriptionAliasEnabled && em @sys.description('The Subscription Ownership URL. Only used when creating MCA Subscriptions across tenants') output subscriptionAcceptOwnershipUrl string = (subscriptionAliasEnabled && empty(existingSubscriptionId) && !empty(subscriptionTenantId) && !empty(subscriptionOwnerId)) ? createSubscription.outputs.subscriptionAcceptOwnershipUrl : 'N/A' + +@sys.description('The resource providers that filed to register') +output failedResourceProviders string = createSubscriptionResources.outputs.failedProviders + +@sys.description('The resource providers features that filed to register') +output failedResourceProvidersFeatures string = createSubscriptionResources.outputs.failedFeatures diff --git a/main.bicep.parameters.md b/main.bicep.parameters.md index 833dcb9e..68669911 100644 --- a/main.bicep.parameters.md +++ b/main.bicep.parameters.md @@ -42,6 +42,8 @@ virtualNetworkVwanPropagatedRouteTablesResourceIds | No | An array of of o virtualNetworkVwanPropagatedLabels | No | An array of virtual hub route table labels to propagate routes to. If left blank/empty the default label will be propagated to only. - Type: `[]` Array - Default value: `[]` *(empty array)* roleAssignmentEnabled | No | Whether to create role assignments or not. If true, supply the array of role assignment objects in the parameter called `roleAssignments`. - Type: Boolean roleAssignments | No | Supply an array of objects containing the details of the role assignments to create. Each object must contain the following `keys`: - `principalId` = The Object ID of the User, Group, SPN, Managed Identity to assign the RBAC role too. - `definition` = The Name of built-In RBAC Roles or a Resource ID of a Built-in or custom RBAC Role Definition. - `relativeScope` = 2 options can be provided for input value: 1. `''` *(empty string)* = Make RBAC Role Assignment to Subscription scope 2. `'/resourceGroups/'` = Make RBAC Role Assignment to specified Resource Group > See below [example in parameter file](#parameter-file) of various combinations - Type: `[]` Array - Default value: `[]` *(empty array)* +resourceProviders | No | Supply an array of strings containing the resource providers to register on the subscription, e.g. `["Microsoft.Compute","Microsoft.Storage"]` - Type: `[]` Array - Default value: `['Microsoft.ApiManagement','Microsoft.AppPlatform','Microsoft.Authorization','Microsoft.Automation','Microsoft.AVS','Microsoft.Blueprint','Microsoft.BotService','Microsoft.Cache','Microsoft.Cdn','Microsoft.CognitiveServices','Microsoft.Compute','Microsoft.ContainerInstance','Microsoft.ContainerRegistry','Microsoft.ContainerService','Microsoft.CostManagement','Microsoft.CustomProviders','Microsoft.Databricks','Microsoft.DataLakeAnalytics','Microsoft.DataLakeStore','Microsoft.DataMigration','Microsoft.DataProtection','Microsoft.DBforMariaDB','Microsoft.DBforMySQL','Microsoft.DBforPostgreSQL','Microsoft.DesktopVirtualization','Microsoft.Devices','Microsoft.DevTestLab','Microsoft.DocumentDB','Microsoft.EventGrid','Microsoft.EventHub','Microsoft.HDInsight','Microsoft.HealthcareApis','Microsoft.GuestConfiguration','Microsoft.KeyVault','Microsoft.Kusto','microsoft.insights','Microsoft.Logic','Microsoft.MachineLearningServices','Microsoft.Maintenance','Microsoft.ManagedIdentity','Microsoft.ManagedServices','Microsoft.Management','Microsoft.Maps','Microsoft.MarketplaceOrdering','Microsoft.Media','Microsoft.MixedReality','Microsoft.Network','Microsoft.NotificationHubs','Microsoft.OperationalInsights','Microsoft.OperationsManagement','Microsoft.PolicyInsights','Microsoft.PowerBIDedicated','Microsoft.Relay','Microsoft.RecoveryServices','Microsoft.Resources','Microsoft.Search','Microsoft.Security','Microsoft.SecurityInsights','Microsoft.ServiceBus','Microsoft.ServiceFabric','Microsoft.Sql','Microsoft.Storage','Microsoft.StreamAnalytics','Microsoft.TimeSeriesInsights','Microsoft.Web']` +resourceProvidersFeatures | No | Supply an array of strings containing the resource providers features to register on the subscription, e.g. `["AzureServicesVm","InGuestHotPatchVMPreview"]` - Type: `[]` Array - Default value: `[]` *(empty array)* disableTelemetry | No | Disable telemetry collection by this module. For more information on the telemetry collected by this module, that is controlled by this parameter, see this page in the wiki: [Telemetry Tracking Using Customer Usage Attribution (PID)](https://github.com/Azure/bicep-lz-vending/wiki/Telemetry) ### subscriptionAliasEnabled @@ -470,6 +472,31 @@ Each object must contain the following `keys`: - Type: `[]` Array - Default value: `[]` *(empty array)* +### resourceProviders + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Supply an array of strings containing the resource providers to register on the subscription, e.g. `["Microsoft.Compute","Microsoft.Storage"]` + +> A resource group gets created in the subscription with a deployment script and a user-assigned managed identity. This resource group needs to be manually deleted if not needed after the resource providers registration process. + +- Type: `[]` Array +- Default value: `['Microsoft.ApiManagement','Microsoft.AppPlatform','Microsoft.Authorization','Microsoft.Automation','Microsoft.AVS','Microsoft.Blueprint','Microsoft.BotService','Microsoft.Cache','Microsoft.Cdn','Microsoft.CognitiveServices','Microsoft.Compute','Microsoft.ContainerInstance','Microsoft.ContainerRegistry','Microsoft.ContainerService','Microsoft.CostManagement','Microsoft.CustomProviders','Microsoft.Databricks','Microsoft.DataLakeAnalytics','Microsoft.DataLakeStore','Microsoft.DataMigration','Microsoft.DataProtection','Microsoft.DBforMariaDB','Microsoft.DBforMySQL','Microsoft.DBforPostgreSQL','Microsoft.DesktopVirtualization','Microsoft.Devices','Microsoft.DevTestLab','Microsoft.DocumentDB','Microsoft.EventGrid','Microsoft.EventHub','Microsoft.HDInsight','Microsoft.HealthcareApis','Microsoft.GuestConfiguration','Microsoft.KeyVault','Microsoft.Kusto','microsoft.insights','Microsoft.Logic','Microsoft.MachineLearningServices','Microsoft.Maintenance','Microsoft.ManagedIdentity','Microsoft.ManagedServices','Microsoft.Management','Microsoft.Maps','Microsoft.MarketplaceOrdering','Microsoft.Media','Microsoft.MixedReality','Microsoft.Network','Microsoft.NotificationHubs','Microsoft.OperationalInsights','Microsoft.OperationsManagement','Microsoft.PolicyInsights','Microsoft.PowerBIDedicated','Microsoft.Relay','Microsoft.RecoveryServices','Microsoft.Resources','Microsoft.Search','Microsoft.Security','Microsoft.SecurityInsights','Microsoft.ServiceBus','Microsoft.ServiceFabric','Microsoft.Sql','Microsoft.Storage','Microsoft.StreamAnalytics','Microsoft.TimeSeriesInsights','Microsoft.Web']` + +### resourceProvidersFeatures + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Supply an array of strings containing the resource providers features to register on the subscription, e.g. `["AzureServicesVm","InGuestHotPatchVMPreview"]` + +> A resoure group gets created in the subscription with the format "rsg--ds-" hosting a deployment script and a user-assigned managed identity. This resource group needs to be manually deleted if not needed after the resource providers features registration process. + +> After a preview feature is registered in your subscription, you'll see one of two states: Registered or Pending. +> - For a preview feature that doesn't require approval, the state is Registered. +> - If a preview feature requires approval, the registration state is Pending. You must request approval from the Azure service offering the preview feature. Usually, you request access through a support ticket. + +- Type: `[]` Array +- Default value: `[]` *(empty array)* ### disableTelemetry @@ -642,6 +669,18 @@ subscriptionAcceptOwnershipUrl | string | The Subscription Ownership URL. Only u } ] }, + "resourceProviders": { + "value": [ + "Microsoft.Compute", + "Microsoft.Storage" + ] + }, + "resourceProvidersFeatures": { + "value": [ + "AzureServicesVm", + "InGuestHotPatchVMPreview" + ] + }, "disableTelemetry": { "value": false } diff --git a/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/.bicep/nested_roleAssignments.bicep b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/.bicep/nested_roleAssignments.bicep new file mode 100644 index 00000000..19a13565 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/.bicep/nested_roleAssignments.bicep @@ -0,0 +1,70 @@ +@sys.description('Required. The IDs of the principals to assign the role to.') +param principalIds array + +@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.') +param roleDefinitionIdOrName string + +@sys.description('Required. The resource ID of the resource to apply the role assignment to.') +param resourceId string + +@sys.description('Optional. The principal type of the assigned principal ID.') +@allowed([ + 'ServicePrincipal' + 'Group' + 'User' + 'ForeignGroup' + 'Device' + '' +]) +param principalType string = '' + +@sys.description('Optional. The description of the role assignment.') +param description string = '' + +@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') +param condition string = '' + +@sys.description('Optional. Version of the condition.') +@allowed([ + '2.0' +]) +param conditionVersion string = '2.0' + +@sys.description('Optional. Id of the delegated managed identity resource.') +param delegatedManagedIdentityResourceId string = '' + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293') + 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893') + 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e') + 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae') + 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44') + 'Managed Identity Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59') + 'Managed Identity Operator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830') + 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa') + 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') + 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') +} + +resource userMsi 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' existing = { + name: last(split(resourceId, '/'))! +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in principalIds: { + name: guid(userMsi.id, principalId, roleDefinitionIdOrName) + properties: { + description: description + roleDefinitionId: contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName + principalId: principalId + principalType: !empty(principalType) ? any(principalType) : null + condition: !empty(condition) ? condition : null + conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + } + scope: userMsi +}] diff --git a/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/README.md b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/README.md new file mode 100644 index 00000000..3febcca6 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/README.md @@ -0,0 +1,233 @@ +# User Assigned Identities `[Microsoft.ManagedIdentity/userAssignedIdentities]` + +This module deploys a User Assigned Identity. + +## Navigation + +- [User Assigned Identities `[Microsoft.ManagedIdentity/userAssignedIdentities]`](#user-assigned-identities-microsoftmanagedidentityuserassignedidentities) + - [Navigation](#navigation) + - [Resource types](#resource-types) + - [Parameters](#parameters) + - [Optional parameters](#optional-parameters) + - [Parameter Usage: `roleAssignments`](#parameter-usage-roleassignments) + - [Parameter Usage: `tags`](#parameter-usage-tags) + - [Outputs](#outputs) + - [Cross-referenced modules](#cross-referenced-modules) + - [Deployment examples](#deployment-examples) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | [2018-11-30](https://learn.microsoft.com/azure/templates/Microsoft.ManagedIdentity/2018-11-30/userAssignedIdentities) | + +## Parameters + +### Optional parameters + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `location` | string | `[resourceGroup().location]` | | Location for all resources. | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `name` | string | `[guid(resourceGroup().id)]` | | Name of the User Assigned Identity. | +| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `tags` | object | `{object}` | | Tags of the resource. | + +### Parameter Usage: `roleAssignments` + +Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure. + +
+ +Parameter JSON format + +```json +"roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Reader", + "description": "Reader Role Assignment", + "principalIds": [ + "12345678-1234-1234-1234-123456789012", // object 1 + "78945612-1234-1234-1234-123456789012" // object 2 + ] + }, + { + "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11", + "principalIds": [ + "12345678-1234-1234-1234-123456789012" // object 1 + ], + "principalType": "ServicePrincipal" + } + ] +} +``` + +
+ +
+ +Bicep format + +```bicep +roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + description: 'Reader Role Assignment' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + '78945612-1234-1234-1234-123456789012' // object 2 + ] + } + { + roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11' + principalIds: [ + '12345678-1234-1234-1234-123456789012' // object 1 + ] + principalType: 'ServicePrincipal' + } +] +``` + +
+

+ +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +

+ +Parameter JSON format + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +
+ +
+ +Bicep format + +```bicep +tags: { + Environment: 'Non-Prod' + Contact: 'test.user@testcompany.com' + PurchaseOrder: '1234' + CostCenter: '7890' + ServiceName: 'DeploymentValidation' + Role: 'DeploymentValidation' +} +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `clientId` | string | The client ID (application ID) of the user assigned identity. | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the user assigned identity. | +| `principalId` | string | The principal ID (object ID) of the user assigned identity. | +| `resourceGroupName` | string | The resource group the user assigned identity was deployed into. | +| `resourceId` | string | The resource ID of the user assigned identity. | + +## Cross-referenced modules + +_None_ + +## Deployment examples + +The following module usage examples are retrieved from the content of the files hosted in the module's `.test` folder. + >**Note**: The name of each example is based on the name of the file from which it is taken. + >**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +

Example 1: Common

+ +
+ +via Bicep module + +```bicep +module userAssignedIdentity './managed-identity/user-assigned-identity/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-miuaicom' + params: { + enableDefaultTelemetry: '' + lock: 'CanNotDelete' + name: 'miuaicom001' + roleAssignments: [ + { + principalIds: [ + '' + ] + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Reader' + } + ] + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "enableDefaultTelemetry": { + "value": "" + }, + "lock": { + "value": "CanNotDelete" + }, + "name": { + "value": "miuaicom001" + }, + "roleAssignments": { + "value": [ + { + "principalIds": [ + "" + ], + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Reader" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

diff --git a/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/deploy.bicep b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/deploy.bicep new file mode 100644 index 00000000..a4156a95 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/deploy.bicep @@ -0,0 +1,84 @@ +metadata name = 'User Assigned Identities' +metadata description = 'This module deploys a User Assigned Identity.' +metadata owner = 'Azure/module-maintainers' + +@description('Optional. Name of the User Assigned Identity.') +param name string = guid(resourceGroup().id) + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +@description('Optional. Tags of the resource.') +param tags object = {} + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource userMsi 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: name + location: location + tags: tags +} + +resource userMsi_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { + name: '${userMsi.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: userMsi +} + +module userMsi_roleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in roleAssignments: { + name: '${uniqueString(deployment().name, location)}-UserMSI-Rbac-${index}' + params: { + description: contains(roleAssignment, 'description') ? roleAssignment.description : '' + principalIds: roleAssignment.principalIds + principalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : '' + roleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName + condition: contains(roleAssignment, 'condition') ? roleAssignment.condition : '' + delegatedManagedIdentityResourceId: contains(roleAssignment, 'delegatedManagedIdentityResourceId') ? roleAssignment.delegatedManagedIdentityResourceId : '' + resourceId: userMsi.id + } +}] + +@description('The name of the user assigned identity.') +output name string = userMsi.name + +@description('The resource ID of the user assigned identity.') +output resourceId string = userMsi.id + +@description('The principal ID (object ID) of the user assigned identity.') +output principalId string = userMsi.properties.principalId + +@description('The client ID (application ID) of the user assigned identity.') +output clientId string = userMsi.properties.clientId + +@description('The resource group the user assigned identity was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = userMsi.location diff --git a/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/version.json b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/version.json new file mode 100644 index 00000000..96236a61 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4", + "pathFilters": [ + "./main.json" + ] +} diff --git a/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/min.parameters.json b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/min.parameters.json new file mode 100644 index 00000000..57fa8566 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/min.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "<>-registerRPs" + } + } +} \ No newline at end of file diff --git a/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/parameters.json b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/parameters.json new file mode 100644 index 00000000..57fa8566 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/.parameters/parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "<>-registerRPs" + } + } +} \ No newline at end of file diff --git a/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/README.md b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/README.md new file mode 100644 index 00000000..b6fdd340 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/README.md @@ -0,0 +1,380 @@ +# Deployment Scripts `[Microsoft.Resources/deploymentScripts]` + +This module deploys a Deployment Script. + +## Navigation + +- [Deployment Scripts `[Microsoft.Resources/deploymentScripts]`](#deployment-scripts-microsoftresourcesdeploymentscripts) + - [Navigation](#navigation) + - [Resource types](#resource-types) + - [Parameters](#parameters) + - [Parameter Usage: `tags`](#parameter-usage-tags) + - [Parameter Usage: `userAssignedIdentities`](#parameter-usage-userassignedidentities) + - [Outputs](#outputs) + - [Considerations](#considerations) + - [Cross-referenced modules](#cross-referenced-modules) + - [Deployment examples](#deployment-examples) + +## Resource types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Resources/deploymentScripts` | [2020-10-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2020-10-01/deploymentScripts) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | Display name of the script to be run. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Allowed Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `arguments` | string | `''` | | Command-line arguments to pass to the script. Arguments are separated by spaces. | +| `azCliVersion` | string | `''` | | Azure CLI module version to be used. | +| `azPowerShellVersion` | string | `'3.0'` | | Azure PowerShell module version to be used. | +| `cleanupPreference` | string | `'Always'` | `[Always, OnExpiration, OnSuccess]` | The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled). | +| `containerGroupName` | string | `''` | | Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed. | +| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `environmentVariables` | secureObject | `{object}` | | The environment variables to pass over to the script. The list is passed as an object with a key name "secureList" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object. | +| `kind` | string | `'AzurePowerShell'` | `[AzureCLI, AzurePowerShell]` | Type of the script. AzurePowerShell, AzureCLI. | +| `location` | string | `[resourceGroup().location]` | | Location for all resources. | +| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | +| `primaryScriptUri` | string | `''` | | Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent instead. | +| `retentionInterval` | string | `'P1D'` | | Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week). | +| `runOnce` | bool | `False` | | When set to false, script will run every time the template is deployed. When set to true, the script will only run once. | +| `scriptContent` | string | `''` | | Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead. | +| `storageAccountResourceId` | string | `''` | | The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account. | +| `supportingScriptUris` | array | `[]` | | List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent). | +| `tags` | object | `{object}` | | Tags of the resource. | +| `timeout` | string | `'PT1H'` | | Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year. | +| `userAssignedIdentities` | object | `{object}` | | The ID(s) to assign to the resource. | + +**Generated parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `baseTime` | string | `[utcNow('yyyy-MM-dd-HH-mm-ss')]` | Do not provide a value! This date value is used to make sure the script run every time the template is deployed. | + + +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +

+ +Parameter JSON format + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +
+ +
+ +Bicep format + +```bicep +tags: { + Environment: 'Non-Prod' + Contact: 'test.user@testcompany.com' + PurchaseOrder: '1234' + CostCenter: '7890' + ServiceName: 'DeploymentValidation' + Role: 'DeploymentValidation' +} +``` + +
+

+ +### Parameter Usage: `userAssignedIdentities` + +You can specify multiple user assigned identities to a resource by providing additional resource IDs using the following format: + +

+ +Parameter JSON format + +```json +"userAssignedIdentities": { + "value": { + "/subscriptions/[[subscriptionId]]/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001": {}, + "/subscriptions/[[subscriptionId]]/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002": {} + } +} +``` + +
+ +
+ +Bicep format + +```bicep +userAssignedIdentities: { + '/subscriptions/[[subscriptionId]]/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001': {} + '/subscriptions/[[subscriptionId]]/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002': {} +} +``` + +
+

+ +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployment script. | +| `outputs` | object | The output of the deployment script. | +| `resourceGroupName` | string | The resource group the deployment script was deployed into. | +| `resourceId` | string | The resource ID of the deployment script. | + +## Considerations + +This module requires a User Assigned Identity (MSI, managed service identity) to exist, and this MSI has to have contributor rights on the subscription - that allows the Deployment Script to create the required Storage Account and the Azure Container Instance. + +## Cross-referenced modules + +_None_ + +## Deployment examples + +The following module usage examples are retrieved from the content of the files hosted in the module's `.test` folder. + >**Note**: The name of each example is based on the name of the file from which it is taken. + >**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +

Example 1: Cli

+ +
+ +via Bicep module + +```bicep +module deploymentScript './resources/deployment-script/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-rdscli' + params: { + // Required parameters + name: 'rdscli001' + // Non-required parameters + azCliVersion: '2.40.0' + cleanupPreference: 'Always' + enableDefaultTelemetry: '' + environmentVariables: { + secureList: [ + { + name: 'var1' + value: 'test' + } + { + name: 'var2' + secureValue: '' + } + ] + } + kind: 'AzureCLI' + retentionInterval: 'P1D' + runOnce: false + scriptContent: 'echo \'echo echo echo\'' + storageAccountResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + timeout: 'PT30M' + userAssignedIdentities: { + '': {} + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "rdscli001" + }, + // Non-required parameters + "azCliVersion": { + "value": "2.40.0" + }, + "cleanupPreference": { + "value": "Always" + }, + "enableDefaultTelemetry": { + "value": "" + }, + "environmentVariables": { + "value": { + "secureList": [ + { + "name": "var1", + "value": "test" + }, + { + "name": "var2", + "secureValue": "" + } + ] + } + }, + "kind": { + "value": "AzureCLI" + }, + "retentionInterval": { + "value": "P1D" + }, + "runOnce": { + "value": false + }, + "scriptContent": { + "value": "echo \"echo echo echo\"" + }, + "storageAccountResourceId": { + "value": "" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + }, + "timeout": { + "value": "PT30M" + }, + "userAssignedIdentities": { + "value": { + "": {} + } + } + } +} +``` + +
+

+ +

Example 2: Ps

+ +
+ +via Bicep module + +```bicep +module deploymentScript './resources/deployment-script/main.bicep' = { + name: '${uniqueString(deployment().name, location)}-test-rdsps' + params: { + // Required parameters + name: 'rdsps001' + // Non-required parameters + azPowerShellVersion: '8.0' + cleanupPreference: 'Always' + enableDefaultTelemetry: '' + kind: 'AzurePowerShell' + lock: 'CanNotDelete' + retentionInterval: 'P1D' + runOnce: false + scriptContent: 'Write-Host \'The cake is a lie!\'' + storageAccountResourceId: '' + tags: { + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + timeout: 'PT30M' + userAssignedIdentities: { + '': {} + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "rdsps001" + }, + // Non-required parameters + "azPowerShellVersion": { + "value": "8.0" + }, + "cleanupPreference": { + "value": "Always" + }, + "enableDefaultTelemetry": { + "value": "" + }, + "kind": { + "value": "AzurePowerShell" + }, + "lock": { + "value": "CanNotDelete" + }, + "retentionInterval": { + "value": "P1D" + }, + "runOnce": { + "value": false + }, + "scriptContent": { + "value": "Write-Host \"The cake is a lie!\"" + }, + "storageAccountResourceId": { + "value": "" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "Role": "DeploymentValidation" + } + }, + "timeout": { + "value": "PT30M" + }, + "userAssignedIdentities": { + "value": { + "": {} + } + } + } +} +``` + +
+

diff --git a/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/deploy.bicep b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/deploy.bicep new file mode 100644 index 00000000..fe29c5b5 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/deploy.bicep @@ -0,0 +1,156 @@ +metadata name = 'Deployment Scripts' +metadata description = 'This module deploys a Deployment Script.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Display name of the script to be run.') +param name string + +@description('Optional. The ID(s) to assign to the resource.') +param userAssignedIdentities object = {} + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. Type of the script. AzurePowerShell, AzureCLI.') +@allowed([ + 'AzurePowerShell' + 'AzureCLI' +]) +param kind string = 'AzurePowerShell' + +@description('Optional. Azure PowerShell module version to be used.') +param azPowerShellVersion string = '3.0' + +@description('Optional. Azure CLI module version to be used.') +param azCliVersion string = '' + +@description('Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead.') +param scriptContent string = '' + +@description('Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent instead.') +param primaryScriptUri string = '' + +@description('Optional. The environment variables to pass over to the script. The list is passed as an object with a key name "secureList" and the value is the list of environment variables (array). The list must have a \'name\' and a \'value\' or a \'secretValue\' property for each object.') +@secure() +param environmentVariables object = {} + +@description('Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent).') +param supportingScriptUris array = [] + +@description('Optional. Command-line arguments to pass to the script. Arguments are separated by spaces.') +param arguments string = '' + +@description('Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week).') +param retentionInterval string = 'P1D' + +@description('Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once.') +param runOnce bool = false + +@description('Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled).') +@allowed([ + 'Always' + 'OnSuccess' + 'OnExpiration' +]) +param cleanupPreference string = 'Always' + +@description('Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a \'containerGroupName\' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use \'containerGroupName\' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. \'containerGroupName\' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed.') +param containerGroupName string = '' + +@description('Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account.') +param storageAccountResourceId string = '' + +@description('Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; \'PT30M\' - 30 minutes; \'P5D\' - 5 days; \'P1Y\' 1 year.') +param timeout string = 'PT1H' + +@description('Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed.') +param baseTime string = utcNow('yyyy-MM-dd-HH-mm-ss') + +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Tags of the resource.') +param tags object = {} + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +var containerSettings = { + containerGroupName: containerGroupName +} + +var identityType = !empty(userAssignedIdentities) ? 'UserAssigned' : 'None' + +var identity = identityType != 'None' ? { + type: identityType + userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null +} : null + +var storageAccountSettings = !empty(storageAccountResourceId) ? { + storageAccountKey: listKeys(storageAccountResourceId, '2019-06-01').keys[0].value + storageAccountName: last(split(storageAccountResourceId, '/')) +} : {} + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource deploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: name + location: location + tags: tags + identity: identity + kind: any(kind) + properties: { + azPowerShellVersion: kind == 'AzurePowerShell' ? azPowerShellVersion : null + azCliVersion: kind == 'AzureCLI' ? azCliVersion : null + containerSettings: !empty(containerGroupName) ? containerSettings : null + storageAccountSettings: !empty(storageAccountResourceId) ? storageAccountSettings : null + arguments: arguments + environmentVariables: !empty(environmentVariables) ? environmentVariables.secureList : [] + scriptContent: !empty(scriptContent) ? scriptContent : null + primaryScriptUri: !empty(primaryScriptUri) ? primaryScriptUri : null + supportingScriptUris: !empty(supportingScriptUris) ? supportingScriptUris : null + cleanupPreference: cleanupPreference + forceUpdateTag: runOnce ? resourceGroup().name : baseTime + retentionInterval: retentionInterval + timeout: timeout + } +} + +resource deploymentScript_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { + name: '${deploymentScript.name}-${lock}-lock' + properties: { + level: any(lock) + notes: lock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.' + } + scope: deploymentScript +} + +@description('The resource ID of the deployment script.') +output resourceId string = deploymentScript.id + +@description('The resource group the deployment script was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the deployment script.') +output name string = deploymentScript.name + +@description('The location the resource was deployed into.') +output location string = deploymentScript.location + +@description('The output of the deployment script.') +output outputs object = contains(deploymentScript.properties, 'outputs') ? deploymentScript.properties.outputs : {} diff --git a/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/version.json b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/version.json new file mode 100644 index 00000000..98789666 --- /dev/null +++ b/src/carml/v0.6.0/Microsoft.Resources/deploymentScripts/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.4" +} diff --git a/src/self/subResourceWrapper/deploy.bicep b/src/self/subResourceWrapper/deploy.bicep index 8d2f64f6..740908b6 100644 --- a/src/self/subResourceWrapper/deploy.bicep +++ b/src/self/subResourceWrapper/deploy.bicep @@ -88,6 +88,25 @@ param roleAssignments array = [] @sys.description('Disable telemetry collection by this module. For more information on the telemetry collected by this module, that is controlled by this parameter, see this page in the wiki: [Telemetry Tracking Using Customer Usage Attribution (PID)](https://github.com/Azure/bicep-lz-vending/wiki/Telemetry)') param disableTelemetry bool = false +@maxLength(90) +@sys.description('The name of the resource group to create the deployment script for resource providers registration.') +param deploymentScriptResourceGroupName string + +@sys.description('The location of the deployment script. Use region shortnames e.g. uksouth, eastus, etc.') +param deploymentScriptLocation string = deployment().location + +@sys.description('The name of the deployment script to register resource providers') +param deploymentScriptName string + +@sys.description('Supply an array of resource providers to register.') +param resourceProviders array =[] + +@sys.description('Supply an array of resource providers features to register.') +param resourceProvidersFeatures array =[] + +@sys.description('The name of the user managed identity for the resource providers registration deployment script.') +param deploymentScriptManagedIdentityName string + // VARIABLES // Deployment name variables @@ -102,6 +121,10 @@ var deploymentNames = { createLzRoleAssignmentsSub: take('lz-vend-rbac-sub-create-${uniqueString(subscriptionId, deployment().name)}', 64) createLzRoleAssignmentsRsgsSelf: take('lz-vend-rbac-rsg-self-create-${uniqueString(subscriptionId, deployment().name)}', 64) createLzRoleAssignmentsRsgsNotSelf: take('lz-vend-rbac-rsg-nself-create-${uniqueString(subscriptionId, deployment().name)}', 64) + createResourceGroupForDeploymentScript: take('lz-vend-rsg-ds-create-${uniqueString(subscriptionId, deploymentScriptResourceGroupName, deploymentScriptLocation, deployment().name)}', 64) + registerResourceProviders: take('lz-vend-ds-create-${uniqueString(subscriptionId, deployment().name)}', 64) + createDeploymentScriptManagedIdentity: take('lz-vend-ds-msi-create-${uniqueString(subscriptionId, deploymentScriptResourceGroupName, deployment().name)}', 64) + createRoleAssignmentsDeploymentScript: take('lz-vend-ds-rbac-create-${uniqueString(subscriptionId, deploymentScriptResourceGroupName, deploymentScriptManagedIdentityName, deployment().name)}', 64) } // Role Assignments filtering and splitting @@ -129,6 +152,10 @@ var virtualWanHubConnectionPropogatedLabels = !empty(virtualNetworkVwanPropagate // Telemetry for CARML flip var enableTelemetryForCarml = !disableTelemetry +var resourceProvidersFormatted = replace(string(resourceProviders), '"', '\\"') + +var resourceProvidersFeaturesFormatted = replace(string(resourceProvidersFeatures), '"', '\\"') + // RESOURCES & MODULES module moveSubscriptionToManagementGroup '../Microsoft.Management/managementGroups/subscriptions/deploy.bicep' = if (subscriptionManagementGroupAssociationEnabled && !empty(subscriptionManagementGroupId)) { @@ -271,4 +298,67 @@ module createLzRoleAssignmentsRsgsNotSelf '../../carml/v0.6.0/Microsoft.Authoriz } }] +module createResourceGroupForDeploymentScript '../../carml/v0.6.0/Microsoft.Resources/resourceGroups/deploy.bicep' = { + scope: subscription(subscriptionId) + name: deploymentNames.createResourceGroupForDeploymentScript + params: { + name: deploymentScriptResourceGroupName + location: deploymentScriptLocation + lock: '' + enableDefaultTelemetry: enableTelemetryForCarml + } +} + +module createDeploymentScriptManagedIdentity '../../carml/v0.6.0/Microsoft.ManagedIdentity/userAssignedIdentity/deploy.bicep' = { + scope: resourceGroup(subscriptionId, deploymentScriptResourceGroupName) + dependsOn: [ + createResourceGroupForDeploymentScript + ] + name: deploymentNames.createDeploymentScriptManagedIdentity + params: { + location: deploymentScriptLocation + name: deploymentScriptManagedIdentityName + enableDefaultTelemetry: enableTelemetryForCarml + } +} + +module createRoleAssignmentsDeploymentScript '../../carml/v0.6.0/Microsoft.Authorization/roleAssignments/deploy.bicep' = { + dependsOn: [ + createDeploymentScriptManagedIdentity + ] + name: take('${deploymentNames.createRoleAssignmentsDeploymentScript}', 64) + params: { + location: deploymentScriptLocation + principalId: createDeploymentScriptManagedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Contributor' + subscriptionId: subscriptionId + enableDefaultTelemetry: enableTelemetryForCarml + } +} + +module registerResourceProviders '../../carml/v0.6.0/Microsoft.Resources/deploymentScripts/deploy.bicep' = { + scope: resourceGroup(subscriptionId, deploymentScriptResourceGroupName) + name: deploymentNames.registerResourceProviders + params: { + name: deploymentScriptName + kind: 'AzurePowerShell' + azPowerShellVersion: '3.0' + cleanupPreference: 'Always' + enableDefaultTelemetry: enableTelemetryForCarml + location: deploymentScriptLocation + retentionInterval: 'P1D' + timeout: 'PT1H' + runOnce: true + userAssignedIdentities: { + '${createDeploymentScriptManagedIdentity.outputs.resourceId}': {} + } + arguments: '-resourceProviders \'${resourceProvidersFormatted}\' -resourceProvidersFeatures \'${resourceProvidersFeaturesFormatted}\' -subscriptionId ${subscriptionId}' + scriptContent: loadTextContent('../../../.github/scripts/Register-SubResourceProviders.ps1') + } +} + // OUTPUTS + +output failedProviders string = registerResourceProviders.outputs.outputs['failedProviderRegistrations'] +output failedFeatures string = registerResourceProviders.outputs.outputs['failedFeaturesRegistrations'] + diff --git a/tests/lz-vending/full.test.bicep b/tests/lz-vending/full.test.bicep index d68437c8..7f74975a 100644 --- a/tests/lz-vending/full.test.bicep +++ b/tests/lz-vending/full.test.bicep @@ -28,6 +28,14 @@ module createSub '../../main.bicep' = { relativeScope: '' } ] + resourceProviders : [ + 'Microsoft.Compute' + 'Microsoft.AVS' + ] + resourceProvidersFeatures: [ + 'AzureServicesVm' + 'InGuestHotPatchVMPreview' + ] } } diff --git a/tests/pester/full.tests.ps1 b/tests/pester/full.tests.ps1 index 10296973..978fa3c0 100644 --- a/tests/pester/full.tests.ps1 +++ b/tests/pester/full.tests.ps1 @@ -45,6 +45,20 @@ Describe "Bicep Landing Zone (Sub) Vending Tests" { $mgAssociation = Get-AzManagementGroupSubscription -SubscriptionId $subId -GroupId "bicep-lz-vending-automation-child" -ErrorAction SilentlyContinue $mgAssociation.Id | Should -Be "/providers/Microsoft.Management/managementGroups/bicep-lz-vending-automation-child/subscriptions/$subId" } + + It "Should have the 'Microsoft.Compute', 'Microsoft.AVS' resource providers registered and the 'AzureServicesVm', 'InGuestHotPatchVMPreview' resource providers features" { + $resourceProviders = @( "Microsoft.Compute", "Microsoft.AVS" ) + $resourceProvidersFeatures = @( "AzureServicesVm", "InGuestHotPatchVMPreview" ) + ForEach ($provider in $resourceProviders) { + $providerStatus = (Get-AzResourceProvider -ListAvailable | Where-Object ProviderNamespace -eq $provider).registrationState + $providerStatus | Should -BeIn @('Registered', 'Registering') + } + + ForEach ($feature in $resourceProvidersFeatures) { + $providerFeatureStatus = (Get-AzProviderFeature -ListAvailable | Where-Object FeatureName -eq $feature).registrationState + $providerFeatureStatus | Should -BeIn @('Registered', 'Registering', 'Pending') + } + } } Context "Role-Based Access Control Assignment Tests" {