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

feat: New module avm/res/maintenance/configuration-assignment #4765

Merged
merged 64 commits into from
Mar 21, 2025
Merged
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
6a21e98
temp update vm
eriqua Mar 14, 2025
9d35d66
enforce deployment only
eriqua Mar 14, 2025
c3e60d7
enforce deployment only
eriqua Mar 14, 2025
5256ab8
enforce deployment only existing vm
eriqua Mar 14, 2025
a38f623
no location out
eriqua Mar 14, 2025
8a9db9c
removal
eriqua Mar 14, 2025
361494c
vm no lock
eriqua Mar 14, 2025
3cf2c9f
vm lock back
eriqua Mar 14, 2025
3b0fb22
dep comment
eriqua Mar 15, 2025
5bbe2f5
dep include
eriqua Mar 15, 2025
074c430
automatic by platform
eriqua Mar 15, 2025
bc9559d
api update linux
eriqua Mar 15, 2025
e8574b9
skip removal
eriqua Mar 15, 2025
4677ddb
removal
eriqua Mar 15, 2025
4de5a7d
remove scope
eriqua Mar 17, 2025
af97650
test filter
eriqua Mar 17, 2025
5885200
test filter rt
eriqua Mar 17, 2025
385cbcb
test multiscope
eriqua Mar 17, 2025
3da83b7
readme, waf, static, removal
eriqua Mar 17, 2025
368504a
codeowners
eriqua Mar 17, 2025
8c3cd49
test opt
eriqua Mar 17, 2025
8a2a965
orphaned
eriqua Mar 17, 2025
cd58ede
skip static
eriqua Mar 17, 2025
9f6ab49
multiscope
eriqua Mar 17, 2025
7f3a408
vm scope
eriqua Mar 17, 2025
ba7b18e
vm multiscope on existing
eriqua Mar 17, 2025
47e96b8
scope null
eriqua Mar 17, 2025
f339bb3
scope null condition
eriqua Mar 17, 2025
c15c646
rg id scope
eriqua Mar 17, 2025
1d20ca4
vm rg scope
eriqua Mar 17, 2025
fdf90fd
multiscope multires
eriqua Mar 17, 2025
21e2d3b
null
eriqua Mar 17, 2025
d934ff5
multirg
eriqua Mar 17, 2025
798289d
multirg dep name
eriqua Mar 17, 2025
c2f2cc9
multirg dep name removal
eriqua Mar 17, 2025
81ed6af
multirg dep name removal
eriqua Mar 17, 2025
ed4ce40
res id condition
eriqua Mar 17, 2025
29e5b95
res id existing condition
eriqua Mar 17, 2025
fb2a280
skip removal
eriqua Mar 17, 2025
1731204
remove
eriqua Mar 18, 2025
dd5ed03
winvm
eriqua Mar 18, 2025
0708513
win rename
eriqua Mar 18, 2025
f4928fe
static and win rename
eriqua Mar 18, 2025
f174ad7
static and location rotation
eriqua Mar 18, 2025
1082ae3
unorphan
eriqua Mar 18, 2025
e82e631
adminuser
eriqua Mar 18, 2025
cbb93ac
Merge branch 'main' into users/erikag/new-maint-config-assign
eriqua Mar 18, 2025
bf1e879
cleanup
eriqua Mar 18, 2025
ebe212e
unremove
eriqua Mar 18, 2025
3eca120
remove
eriqua Mar 18, 2025
367db39
default
eriqua Mar 18, 2025
3cd8a3f
uae max ne waf
eriqua Mar 18, 2025
c1e8d6a
Merge branch 'main' into users/erikag/new-maint-config-assign
eriqua Mar 18, 2025
cd76476
Update .github/workflows/avm.res.compute.virtual-machine.yml
eriqua Mar 18, 2025
b2e9e94
Update .github/workflows/avm.res.compute.virtual-machine.yml
eriqua Mar 18, 2025
c528e81
Update .github/workflows/avm.res.maintenance.configuration-assignment…
eriqua Mar 18, 2025
b51e67d
address comments
eriqua Mar 20, 2025
52081d6
test location rotation
eriqua Mar 20, 2025
7a5ae94
regen module
eriqua Mar 20, 2025
7aa9230
cleanup
eriqua Mar 20, 2025
a3b1270
Update avm/res/maintenance/configuration-assignment/main.bicep
eriqua Mar 21, 2025
abe294d
tags and trigger
eriqua Mar 21, 2025
d86a7b3
tag description
eriqua Mar 21, 2025
e79c71c
cleanup
eriqua Mar 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -117,6 +117,7 @@
/avm/res/load-test-service/load-test/ @Azure/avm-res-loadtestservice-loadtest-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/logic/workflow/ @Azure/avm-res-logic-workflow-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/machine-learning-services/workspace/ @Azure/avm-res-machinelearningservices-workspace-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/maintenance/configuration-assignment/ @Azure/avm-res-maintenance-configurationassignment-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/maintenance/maintenance-configuration/ @Azure/avm-res-maintenance-maintenanceconfiguration-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/managed-identity/user-assigned-identity/ @Azure/avm-res-managedidentity-userassignedidentity-module-owners-bicep @Azure/avm-module-reviewers-bicep
/avm/res/managed-services/registration-definition/ @Azure/avm-res-managedservices-registrationdefinition-module-owners-bicep @Azure/avm-module-reviewers-bicep
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/avm_module_issue.yml
Original file line number Diff line number Diff line change
@@ -152,6 +152,7 @@ body:
- "avm/res/load-test-service/load-test"
- "avm/res/logic/workflow"
- "avm/res/machine-learning-services/workspace"
- "avm/res/maintenance/configuration-assignment"
- "avm/res/maintenance/maintenance-configuration"
- "avm/res/managed-identity/user-assigned-identity"
- "avm/res/managed-services/registration-definition"
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: "avm.res.maintenance.configuration-assignment"

on:
workflow_dispatch:
inputs:
staticValidation:
type: boolean
description: "Execute static validation"
required: false
default: true
deploymentValidation:
type: boolean
description: "Execute deployment validation"
required: false
default: true
removeDeployment:
type: boolean
description: "Remove deployed module"
required: false
default: true
customLocation:
type: string
description: "Default location overwrite (e.g., eastus)"
required: false
push:
branches:
- main
paths:
- ".github/actions/templates/avm-**"
- ".github/workflows/avm.template.module.yml"
- ".github/workflows/avm.res.maintenance.configuration-assignment.yml"
- "avm/res/maintenance/configuration-assignment/**"
- "utilities/pipelines/**"
- "!utilities/pipelines/platform/**"
- "!*/**/README.md"

env:
modulePath: "avm/res/maintenance/configuration-assignment"
workflowPath: ".github/workflows/avm.res.maintenance.configuration-assignment.yml"

concurrency:
group: ${{ github.workflow }}

jobs:
###########################
# Initialize pipeline #
###########################
job_initialize_pipeline:
runs-on: ubuntu-latest
name: "Initialize pipeline"
steps:
- name: "Checkout"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set input parameters to output variables"
id: get-workflow-param
uses: ./.github/actions/templates/avm-getWorkflowInput
with:
workflowPath: "${{ env.workflowPath}}"
- name: "Get module test file paths"
id: get-module-test-file-paths
uses: ./.github/actions/templates/avm-getModuleTestFiles
with:
modulePath: "${{ env.modulePath }}"
outputs:
workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }}
moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }}
psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }}
modulePath: "${{ env.modulePath }}"

##############################
# Call reusable workflow #
##############################
call-workflow-passing-data:
name: "Run"
permissions:
id-token: write # For OIDC
contents: write # For release tags
needs:
- job_initialize_pipeline
uses: ./.github/workflows/avm.template.module.yml
with:
workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}"
moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}"
psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}"
modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}"
secrets: inherit
432 changes: 432 additions & 0 deletions avm/res/maintenance/configuration-assignment/README.md

Large diffs are not rendered by default.

115 changes: 115 additions & 0 deletions avm/res/maintenance/configuration-assignment/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
metadata name = 'Maintenance Configuration Assignments'
metadata description = 'This module deploys a Maintenance Configuration Assignment.'

// ============== //
// Parameters //
// ============== //

@description('Required. Maintenance configuration assignment Name.')
param name string

@description('Optional. Enable/Disable usage telemetry for module.')
param enableTelemetry bool = true

@description('Optional. Location for all Resources.')
param location string = resourceGroup().location

@description('Required. The maintenance configuration resource ID.')
param maintenanceConfigurationResourceId string

@description('Conditional. The unique virtual machine resource ID to assign the configuration to. Required if filter is not provided. If resourceId is provided, filter is ignored. If provided, the module scope must be set to the resource group of the virtual machine.')
param resourceId string?

@description('Conditional. Properties of the dynamic configuration assignment. Required if resourceId is not provided.')
param filter filterType?

// =============== //
// Deployments //
// =============== //

#disable-next-line no-deployments-resources
resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
name: '46d3xbcp.res.maintenance-configurationassignment.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}'
properties: {
mode: 'Incremental'
template: {
'$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
contentVersion: '1.0.0.0'
resources: []
outputs: {
telemetry: {
type: 'String'
value: 'For more information, see https://aka.ms/avm/TelemetryInfo'
}
}
}
}
}

resource vm 'Microsoft.Compute/virtualMachines@2024-07-01' existing = if (resourceId != null) {
name: last(split(resourceId ?? '', '/'))!
}

resource configurationAssignment 'Microsoft.Maintenance/configurationAssignments@2023-04-01' = if (resourceId != null) {
scope: vm
location: location
name: name
properties: {
maintenanceConfigurationId: maintenanceConfigurationResourceId
resourceId: vm.id
}
}

resource configurationAssignment_dynamic 'Microsoft.Maintenance/configurationAssignments@2023-04-01' = if (resourceId == null) {
location: location
name: name
properties: {
filter: filter
maintenanceConfigurationId: maintenanceConfigurationResourceId
}
}

// =========== //
// Outputs //
// =========== //

@description('The name of the Maintenance configuration assignment.')
output name string = configurationAssignment.name ?? configurationAssignment_dynamic.name

@description('The resource ID of the Maintenance configuration assignment.')
output resourceId string = configurationAssignment.id ?? configurationAssignment_dynamic.id

@description('The name of the resource group the Maintenance configuration assignment was created in.')
output resourceGroupName string = resourceGroup().name

// =============== //
// Definitions //
// =============== //

@export()
@description('The type for a managed configuration dynamic assignment filter.')
type filterType = {
@description('Optional. List of allowed resource group names.')
resourceGroups: string[]?

@description('Optional. List of allowed resource types.')
resourceTypes: string[]?

@description('Optional. List of locations to scope the query to.')
locations: string[]?

@description('Optional. List of allowed operating systems.')
osTypes: ('Linux' | 'Windows')[]?

@description('Optional. Tag settings for the VM.')
tagSettings: {
@description('Required. Filter VMs by Any or All specified tags.')
filterOperator: ('All' | 'Any')

@description('Required. Dictionary of tags with its list of values.')
tags: {
@description('Required. A key-value pair.')
*: string
}
}?
}
215 changes: 215 additions & 0 deletions avm/res/maintenance/configuration-assignment/main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"languageVersion": "2.0",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.33.93.31351",
"templateHash": "13366072417349467027"
},
"name": "Maintenance Configuration Assignments",
"description": "This module deploys a Maintenance Configuration Assignment."
},
"definitions": {
"filterType": {
"type": "object",
"properties": {
"resourceGroups": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true,
"metadata": {
"description": "Optional. List of allowed resource group names."
}
},
"resourceTypes": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true,
"metadata": {
"description": "Optional. List of allowed resource types."
}
},
"locations": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true,
"metadata": {
"description": "Optional. List of locations to scope the query to."
}
},
"osTypes": {
"type": "array",
"allowedValues": [
"Linux",
"Windows"
],
"nullable": true,
"metadata": {
"description": "Optional. List of allowed operating systems."
}
},
"tagSettings": {
"type": "object",
"properties": {
"filterOperator": {
"type": "string",
"allowedValues": [
"All",
"Any"
],
"metadata": {
"description": "Required. Filter VMs by Any or All specified tags."
}
},
"tags": {
"type": "object",
"properties": {},
"additionalProperties": {
"type": "string",
"metadata": {
"description": "Required. A key-value pair."
}
},
"metadata": {
"description": "Required. Dictionary of tags with its list of values."
}
}
},
"nullable": true,
"metadata": {
"description": "Optional. Tag settings for the VM."
}
}
},
"metadata": {
"__bicep_export!": true,
"description": "The type for a managed configuration dynamic assignment filter."
}
}
},
"parameters": {
"name": {
"type": "string",
"metadata": {
"description": "Required. Maintenance configuration assignment Name."
}
},
"enableTelemetry": {
"type": "bool",
"defaultValue": true,
"metadata": {
"description": "Optional. Enable/Disable usage telemetry for module."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Optional. Location for all Resources."
}
},
"maintenanceConfigurationResourceId": {
"type": "string",
"metadata": {
"description": "Required. The maintenance configuration resource ID."
}
},
"resourceId": {
"type": "string",
"nullable": true,
"metadata": {
"description": "Conditional. The unique virtual machine resource ID to assign the configuration to. Required if filter is not provided. If resourceId is provided, filter is ignored. If provided, the module scope must be set to the resource group of the virtual machine."
}
},
"filter": {
"$ref": "#/definitions/filterType",
"nullable": true,
"metadata": {
"description": "Conditional. Properties of the dynamic configuration assignment. Required if resourceId is not provided."
}
}
},
"resources": {
"avmTelemetry": {
"condition": "[parameters('enableTelemetry')]",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2024-03-01",
"name": "[format('46d3xbcp.res.maintenance-configurationassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [],
"outputs": {
"telemetry": {
"type": "String",
"value": "For more information, see https://aka.ms/avm/TelemetryInfo"
}
}
}
}
},
"vm": {
"condition": "[not(equals(parameters('resourceId'), null()))]",
"existing": true,
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2024-07-01",
"name": "[last(split(coalesce(parameters('resourceId'), ''), '/'))]"
},
"configurationAssignment": {
"condition": "[not(equals(parameters('resourceId'), null()))]",
"type": "Microsoft.Maintenance/configurationAssignments",
"apiVersion": "2023-04-01",
"scope": "[format('Microsoft.Compute/virtualMachines/{0}', last(split(coalesce(parameters('resourceId'), ''), '/')))]",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"properties": {
"maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]",
"resourceId": "[resourceId('Microsoft.Compute/virtualMachines', last(split(coalesce(parameters('resourceId'), ''), '/')))]"
}
},
"configurationAssignment_dynamic": {
"condition": "[equals(parameters('resourceId'), null())]",
"type": "Microsoft.Maintenance/configurationAssignments",
"apiVersion": "2023-04-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"properties": {
"filter": "[parameters('filter')]",
"maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]"
}
}
},
"outputs": {
"name": {
"type": "string",
"metadata": {
"description": "The name of the Maintenance configuration assignment."
},
"value": "[coalesce(parameters('name'), parameters('name'))]"
},
"resourceId": {
"type": "string",
"metadata": {
"description": "The resource ID of the Maintenance configuration assignment."
},
"value": "[coalesce(extensionResourceId(resourceId('Microsoft.Compute/virtualMachines', last(split(coalesce(parameters('resourceId'), ''), '/'))), 'Microsoft.Maintenance/configurationAssignments', parameters('name')), resourceId('Microsoft.Maintenance/configurationAssignments', parameters('name')))]"
},
"resourceGroupName": {
"type": "string",
"metadata": {
"description": "The name of the resource group the Maintenance configuration assignment was created in."
},
"value": "[resourceGroup().name]"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@description('Optional. The location to deploy to.')
param location string = resourceGroup().location

@description('Required. The name of the Maintenance Configuration to create.')
param maintenanceConfigurationName string

resource maintenanceConfiguration 'Microsoft.Maintenance/maintenanceConfigurations@2023-10-01-preview' = {
name: maintenanceConfigurationName
location: location
properties: {
extensionProperties: {
InGuestPatchMode: 'User'
}
maintenanceScope: 'InGuestPatch'
maintenanceWindow: {
startDateTime: '2025-01-09 00:00'
expirationDateTime: '2026-01-08 00:00'
duration: '03:00'
timeZone: 'UTC'
recurEvery: 'Week'
}
visibility: 'Custom'
installPatches: {
rebootSetting: 'AlwaysReboot'
windowsParameters: {
kbNumbersToExclude: [
'KB123456'
'KB3654321'
]
kbNumbersToInclude: [
'KB111111'
'KB222222'
]
classificationsToInclude: [
'Critical'
'Security'
]
}
linuxParameters: {
classificationsToInclude: [
'Critical'
'Security'
]
}
}
}
}

@description('The resource ID of the maintenance configuration.')
output maintenanceConfigurationResourceId string = maintenanceConfiguration.id
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
targetScope = 'subscription'

metadata name = 'Using only defaults'
metadata description = 'This instance deploys the module with the minimum set of required parameters. This instance uses filters to define a dynamic scope and assign it to the input maintenance configuration. The dynamic scope will be resolved at run time. '

// ========== //
// Parameters //
// ========== //

@description('Optional. The location for all resources.')
param resourceLocation string = deployment().location

@description('Optional. The name of the resource group to deploy for testing purposes.')
@maxLength(90)
param resourceGroupName string = 'dep-${namePrefix}-maintenance.maintenanceconfigurations-${serviceShort}-rg'

@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.')
param serviceShort string = 'mcamin'

@description('Optional. A token to inject into the name of each resource.')
param namePrefix string = '#_namePrefix_#'

// ============ //
// Dependencies //
// ============ //

// General resources
// =================
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: resourceLocation
}

module nestedDependencies 'dependencies.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies'
params: {
maintenanceConfigurationName: 'dep-${namePrefix}-mc-${serviceShort}'
location: resourceLocation
}
}

// ============== //
// Test Execution //
// ============== //
@batchSize(1)
module testDeployment '../../../main.bicep' = [
for iteration in ['init', 'idem']: {
scope: resourceGroup
name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
params: {
name: '${namePrefix}${serviceShort}001'
maintenanceConfigurationResourceId: nestedDependencies.outputs.maintenanceConfigurationResourceId
filter: {
osTypes: [
'Linux'
'Windows'
]
resourceTypes: [
'Virtual Machines'
]
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
@description('Optional. The location to deploy to.')
param location string = resourceGroup().location

@description('Required. The name of the Maintenance Configuration to create.')
param maintenanceConfigurationName string

@description('Required. The name of the Virtual Network to create.')
param virtualNetworkName string

@description('Required. The name of the Virtual Machine to create.')
param virtualMachineName string

@description('Required. The username to leverage for the VM login.')
@secure()
param adminUsername string

@description('Optional. The password to leverage for the VM login.')
@secure()
param password string = newGuid()

var addressPrefix = '10.0.0.0/16'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = {
name: virtualNetworkName
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: 'defaultSubnet'
properties: {
addressPrefix: cidrSubnet(addressPrefix, 16, 0)
}
}
]
}
}

resource networkInterface 'Microsoft.Network/networkInterfaces@2023-04-01' = {
name: '${virtualMachineName}-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig01'
properties: {
subnet: {
id: virtualNetwork.properties.subnets[0].id
}
}
}
]
}
}

resource maintenanceConfiguration 'Microsoft.Maintenance/maintenanceConfigurations@2023-10-01-preview' = {
name: maintenanceConfigurationName
location: location
properties: {
extensionProperties: {
InGuestPatchMode: 'User'
}
maintenanceScope: 'InGuestPatch'
maintenanceWindow: {
startDateTime: '2025-01-09 00:00'
expirationDateTime: '2026-01-08 00:00'
duration: '03:00'
timeZone: 'UTC'
recurEvery: 'Week'
}
visibility: 'Custom'
installPatches: {
rebootSetting: 'AlwaysReboot'
windowsParameters: {
kbNumbersToExclude: [
'KB123456'
'KB3654321'
]
kbNumbersToInclude: [
'KB111111'
'KB222222'
]
classificationsToInclude: [
'Critical'
'Security'
]
}
linuxParameters: {
classificationsToInclude: [
'Critical'
'Security'
]
}
}
}
}

resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = {
name: virtualMachineName
location: location
properties: {
networkProfile: {
networkInterfaces: [
{
id: networkInterface.id
properties: {
deleteOption: 'Delete'
primary: true
}
}
]
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
deleteOption: 'Delete'
createOption: 'FromImage'
}
}
hardwareProfile: {
vmSize: 'Standard_B1ms'
}
osProfile: {
adminUsername: adminUsername
adminPassword: password
computerName: virtualMachineName
linuxConfiguration: {
disablePasswordAuthentication: false
patchSettings: {
assessmentMode: 'AutomaticByPlatform'
automaticByPlatformSettings: {
rebootSetting: 'IfRequired'
bypassPlatformSafetyChecksOnUserSchedule: true
}
patchMode: 'AutomaticByPlatform'
}
}
}
}
}

@description('The resource ID of the maintenance configuration.')
output maintenanceConfigurationResourceId string = maintenanceConfiguration.id

@description('The resource ID of the created Virtual Machine.')
output virtualMachineResourceId string = virtualMachine.id
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
targetScope = 'subscription'

metadata name = 'WAF-aligned'
metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. This instance assigns an existing Linux virtual machine to the input maintenance configuration.'

// ========== //
// Parameters //
// ========== //

@description('Optional. The name of the resource group to deploy for testing purposes.')
@maxLength(90)
param resourceGroupName string = 'dep-${namePrefix}-maintenance.maintenanceconfigurations-${serviceShort}-rg'

@description('Optional. The location for all resources.')
param resourceLocation string = deployment().location

@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.')
param serviceShort string = 'mcawaf'

@description('Optional. A token to inject into the name of each resource.')
param namePrefix string = '#_namePrefix_#'

// ============ //
// Dependencies //
// ============ //

// General resources
// =================
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: resourceLocation
}

module nestedDependencies 'dependencies.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies'
params: {
virtualMachineName: 'dep-${namePrefix}-vm-${serviceShort}'
adminUsername: 'localAdminUser'
virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}'
maintenanceConfigurationName: 'dep-${namePrefix}-mc-${serviceShort}'
location: resourceLocation
}
}

// ============== //
// Test Execution //
// ============== //
@batchSize(1)
module testDeployment '../../../main.bicep' = [
for iteration in ['init', 'idem']: {
scope: resourceGroup
name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
params: {
name: '${namePrefix}${serviceShort}001'
maintenanceConfigurationResourceId: nestedDependencies.outputs.maintenanceConfigurationResourceId
resourceId: nestedDependencies.outputs.virtualMachineResourceId
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@description('Optional. The location to deploy to.')
param location string = resourceGroup().location

@description('Required. The name of the Maintenance Configuration to create.')
param maintenanceConfigurationName string

resource maintenanceConfiguration 'Microsoft.Maintenance/maintenanceConfigurations@2023-10-01-preview' = {
name: maintenanceConfigurationName
location: location
properties: {
extensionProperties: {
InGuestPatchMode: 'User'
}
maintenanceScope: 'InGuestPatch'
maintenanceWindow: {
startDateTime: '2025-01-09 00:00'
expirationDateTime: '2026-01-08 00:00'
duration: '03:00'
timeZone: 'UTC'
recurEvery: 'Week'
}
visibility: 'Custom'
installPatches: {
rebootSetting: 'AlwaysReboot'
windowsParameters: {
kbNumbersToExclude: [
'KB123456'
'KB3654321'
]
kbNumbersToInclude: [
'KB111111'
'KB222222'
]
classificationsToInclude: [
'Critical'
'Security'
]
}
linuxParameters: {
classificationsToInclude: [
'Critical'
'Security'
]
}
}
}
}

@description('The resource ID of the maintenance configuration.')
output maintenanceConfigurationResourceId string = maintenanceConfiguration.id
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
@description('Optional. The location to deploy to.')
param location string = resourceGroup().location

@description('Required. The name of the Virtual Network to create.')
param virtualNetworkName string

@description('Required. The name of the Virtual Machine to create.')
param virtualMachineName string

@description('Required. The name of the Virtual Machine to create.')
@maxLength(15)
param computerName string

@description('Optional. The password to leverage for the VM login.')
@secure()
param password string = newGuid()

@description('Required. The username to leverage for the VM login.')
@secure()
param adminUsername string

var addressPrefix = '10.0.0.0/16'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = {
name: virtualNetworkName
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: 'defaultSubnet'
properties: {
addressPrefix: cidrSubnet(addressPrefix, 16, 0)
}
}
]
}
}

resource networkInterface 'Microsoft.Network/networkInterfaces@2023-04-01' = {
name: '${virtualMachineName}-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig01'
properties: {
subnet: {
id: virtualNetwork.properties.subnets[0].id
}
}
}
]
}
}

resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = {
name: virtualMachineName
location: location
properties: {
networkProfile: {
networkInterfaces: [
{
id: networkInterface.id
properties: {
deleteOption: 'Delete'
primary: true
}
}
]
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2022-datacenter-azure-edition'
version: 'latest'
}
osDisk: {
deleteOption: 'Delete'
createOption: 'FromImage'
}
}
hardwareProfile: {
vmSize: 'Standard_B1ms'
}
osProfile: {
adminUsername: adminUsername
adminPassword: password
computerName: computerName
windowsConfiguration: {
enableAutomaticUpdates: true
patchSettings: {
assessmentMode: 'AutomaticByPlatform'
automaticByPlatformSettings: {
rebootSetting: 'IfRequired'
bypassPlatformSafetyChecksOnUserSchedule: true
}
patchMode: 'AutomaticByPlatform'
}
}
}
}
}

@description('The resource ID of the created Virtual Machine.')
output virtualMachineResourceId string = virtualMachine.id
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
targetScope = 'subscription'

metadata name = 'Multi resource group'
metadata description = 'This instance deploys the module leveraging virtual machine and maintenance configuration dependencies from two different resource groups. This instance assigns an existing Windows virtual machine to the input maintenance configuration.'

// ========== //
// Parameters //
// ========== //

@description('Optional. The name of the resource group to deploy for testing purposes.')
@maxLength(90)
param resourceGroupName string = 'dep-${namePrefix}-maintenance.maintenanceconfigurations-${serviceShort}-rg'

@description('Optional. The location for all resources.')
param resourceLocation string = deployment().location

@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.')
param serviceShort string = 'mcamrg'

@description('Optional. A token to inject into the name of each resource.')
param namePrefix string = '#_namePrefix_#'

// ============ //
// Dependencies //
// ============ //

// General resources
// =================
resource resourceGroup_vm 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: resourceLocation
}

resource resourceGroup_mc 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: '${take(resourceGroupName, 87)}-mc' // Ensure the resource group name is within the 90 character limit
location: resourceLocation
}

module nestedDependencies_vm 'dependencies_vm.bicep' = {
scope: resourceGroup_vm
name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies_vm'
params: {
virtualMachineName: 'dep-${namePrefix}-vm-${serviceShort}'
computerName: 'dep${namePrefix}${serviceShort}'
adminUsername: 'localAdminUser'
virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}'
location: resourceLocation
}
}

module nestedDependencies_mc 'dependencies_mc.bicep' = {
scope: resourceGroup_mc
name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies_mc'
params: {
maintenanceConfigurationName: 'dep-${namePrefix}-mc-${serviceShort}'
location: resourceLocation
}
}

// ============== //
// Test Execution //
// ============== //
@batchSize(1)
module testDeployment '../../../main.bicep' = [
for iteration in ['init', 'idem']: {
scope: resourceGroup_vm
name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
params: {
name: '${namePrefix}${serviceShort}001'
maintenanceConfigurationResourceId: nestedDependencies_mc.outputs.maintenanceConfigurationResourceId
resourceId: nestedDependencies_vm.outputs.virtualMachineResourceId
}
}
]
7 changes: 7 additions & 0 deletions avm/res/maintenance/configuration-assignment/version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://aka.ms/bicep-registry-module-version-file-schema#",
"version": "0.1",
"pathFilters": [
"./main.json"
]
}