Skip to content

Commit 9509df8

Browse files
feat(new): Added Azure.MySQL.MaintenanceWindow (Azure#2923)
Co-authored-by: Bernie White <[email protected]>
1 parent 0578f32 commit 9509df8

File tree

5 files changed

+210
-12
lines changed

5 files changed

+210
-12
lines changed

docs/CHANGELOG-v1.md

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
3232
What's changed since pre-release v1.37.0:
3333

3434
- New rules:
35+
- Azure Database for MySQL:
36+
- Verify that Azure Database for MySQL servers have a customer-controlled maintenance window configured by @BenjaminEngeset.
37+
[#2916](https://github.com/Azure/PSRule.Rules.Azure/issues/2916)
3538
- Azure Firewall:
3639
- Verify that firewalls have availability zones configured by @BenjaminEngeset.
3740
[#2909](https://github.com/Azure/PSRule.Rules.Azure/issues/2909)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
severity: Important
3+
pillar: Reliability
4+
category: RE:04 Target metrics
5+
resource: Azure Database for MySQL
6+
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.MySQL.MaintenanceWindow/
7+
---
8+
9+
# Customer-controlled maintenance window configuration
10+
11+
## SYNOPSIS
12+
13+
Configure a customer-controlled maintenance window for Azure Database for MySQL servers.
14+
15+
## DESCRIPTION
16+
17+
Azure Database for MySQL flexible servers undergo periodic maintenance to ensure your managed database remains secure, stable, and up-to-date. This maintenance includes applying security updates, system upgrades, and software patches.
18+
19+
Maintenance windows can be scheduled in two ways for each flexible server:
20+
21+
- System-Managed Schedule: The system automatically selects a one-hour window between 11 PM and 7 AM in your server’s regional time.
22+
- Custom Schedule: You can specify a preferred maintenance window by choosing the day of the week and a one-hour time window.
23+
24+
By configuring a customer-controlled maintenance window, you can schedule updates to occur during a preferred time, ideally outside business hours, minimizing disruptions.
25+
26+
Only the flexible server deployment model supports customer-controlled maintenance windows.
27+
28+
## RECOMMENDATION
29+
30+
Consider using a customer-controlled maintenance window to efficiently schedule updates and minimize disruptions.
31+
32+
## EXAMPLES
33+
34+
### Configure with Azure template
35+
36+
To configure servers that pass this rule:
37+
38+
- Set the `properties.maintenanceWindow.customWindow` property to `Enabled`.
39+
40+
For example:
41+
42+
```json
43+
{
44+
"type": "Microsoft.DBforMySQL/flexibleServers",
45+
"apiVersion": "2023-10-01-preview",
46+
"name": "[parameters('serverName')]",
47+
"location": "[parameters('location')]",
48+
"sku": {
49+
"name": "Standard_D16as",
50+
"tier": "GeneralPurpose"
51+
},
52+
"properties": {
53+
"administratorLogin": "[parameters('administratorLogin')]",
54+
"administratorLoginPassword": "[parameters('administratorLoginPassword')]",
55+
"createMode": "Default",
56+
"version": "[parameters('mysqlVersion')]",
57+
"maintenanceWindow": {
58+
"customWindow": "Enabled",
59+
"dayOfWeek": "0",
60+
"startHour": "1",
61+
"startMinute": "0"
62+
}
63+
}
64+
}
65+
```
66+
67+
### Configure with Bicep
68+
69+
To configure servers that pass this rule:
70+
71+
- Set the `properties.maintenanceWindow.customWindow` property to `Enabled`.
72+
73+
For example:
74+
75+
```bicep
76+
resource mysqlDbServer 'Microsoft.DBforMySQL/flexibleServers@2023-10-01-preview' = {
77+
name: serverName
78+
location: location
79+
sku: {
80+
name: 'Standard_D16as'
81+
tier: 'GeneralPurpose'
82+
}
83+
properties: {
84+
administratorLogin: administratorLogin
85+
administratorLoginPassword: administratorLoginPassword
86+
createMode: 'Default'
87+
version: mysqlVersion
88+
maintenanceWindow: {
89+
customWindow: 'Enabled'
90+
dayOfWeek: 0
91+
startHour: 1
92+
startMinute: 0
93+
}
94+
}
95+
}
96+
```
97+
98+
## NOTES
99+
100+
The custom schedule maintenance window feature is only available for the flexible server deployment model.
101+
102+
## LINKS
103+
104+
- [RE:04 Target metrics](https://learn.microsoft.com/azure/well-architected/reliability/metrics)
105+
- [Scheduled maintenance in Azure Database for MySQL](https://learn.microsoft.com/azure/mysql/flexible-server/concepts-maintenance)
106+
- [Select a maintenance window](https://learn.microsoft.com/azure/mysql/flexible-server/concepts-maintenance#select-a-maintenance-window)
107+
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.dbformysql/flexibleservers)

src/PSRule.Rules.Azure/rules/Azure.MySQL.Rule.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@ spec:
4747
field: properties.minimalTlsVersion
4848
equals: TLS1_2
4949

50+
---
51+
# Synopsis: Configure a customer-controlled maintenance window for Azure Database for MySQL servers.
52+
apiVersion: github.com/microsoft/PSRule/v1
53+
kind: Rule
54+
metadata:
55+
name: Azure.MySQL.MaintenanceWindow
56+
ref: AZR-000431
57+
tags:
58+
release: GA
59+
ruleSet: 2024_06
60+
Azure.WAF/pillar: Reliability
61+
spec:
62+
type:
63+
- Microsoft.DBforMySQL/flexibleServers
64+
condition:
65+
field: properties.maintenanceWindow.customWindow
66+
equals: Enabled
67+
5068
#endregion Region

tests/PSRule.Rules.Azure.Tests/Azure.MySQL.Tests.ps1

+31-12
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,14 @@ Describe 'Azure.MySQL' -Tag 'MySql' {
128128
# Fail
129129
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
130130
$ruleResult | Should -Not -BeNullOrEmpty;
131-
$ruleResult.Length | Should -Be 4;
132-
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-E', 'server-F';
131+
$ruleResult.Length | Should -Be 5;
132+
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-E', 'server-F', 'server-G';
133133

134134
$ruleResult[0].Reason | Should -BeExactly "The Azure Database for MySQL 'server-B' should have geo-redundant backup configured.";
135135
$ruleResult[1].Reason | Should -BeExactly "The Azure Database for MySQL 'server-A' should have geo-redundant backup configured.";
136136
$ruleResult[2].Reason | Should -BeExactly "The Azure Database for MySQL 'server-E' should have geo-redundant backup configured.";
137137
$ruleResult[3].Reason | Should -BeExactly "The Azure Database for MySQL 'server-F' should have geo-redundant backup configured.";
138-
138+
139139
# Pass
140140
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
141141
$ruleResult | Should -Not -BeNullOrEmpty;
@@ -178,8 +178,8 @@ Describe 'Azure.MySQL' -Tag 'MySql' {
178178
# Pass
179179
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
180180
$ruleResult | Should -Not -BeNullOrEmpty;
181-
$ruleResult.Length | Should -Be 3;
182-
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-F';
181+
$ruleResult.Length | Should -Be 4;
182+
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-F', 'server-G';
183183
}
184184

185185
It 'Azure.MySQL.AAD' {
@@ -188,8 +188,8 @@ Describe 'Azure.MySQL' -Tag 'MySql' {
188188
# Fail
189189
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
190190
$ruleResult | Should -Not -BeNullOrEmpty;
191-
$ruleResult.Length | Should -Be 6;
192-
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-D', 'server-E', 'ActiveDirectoryAdmin-A', 'ActiveDirectoryAdmin-C';
191+
$ruleResult.Length | Should -Be 7;
192+
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-D', 'server-E', 'ActiveDirectoryAdmin-A', 'ActiveDirectoryAdmin-C', 'server-G';
193193

194194
$ruleResult[0].Reason | Should -BeIn 'Path properties.administratorType: Is null or empty.', 'Path properties.login: Is null or empty.', 'Path properties.sid: Is null or empty.';
195195
$ruleResult[1].Reason | Should -BeIn "A sub-resource of type 'Microsoft.DBforMySQL/servers/administrators' has not been specified.";
@@ -209,19 +209,38 @@ Describe 'Azure.MySQL' -Tag 'MySql' {
209209
# Fail
210210
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
211211
$ruleResult | Should -Not -BeNullOrEmpty;
212-
$ruleResult.Length | Should -Be 3;
213-
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'aad_auth_only-A';
212+
$ruleResult.Length | Should -Be 4;
213+
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-G', 'aad_auth_only-A';
214214

215-
$ruleResult[0].Reason | Should -BeIn "A sub-resource of type 'Microsoft.DBforMySQL/flexibleServers/configurations' has not been specified.";
216-
$ruleResult[1].Reason | Should -BeIn "Path properties.value: Is set to 'OFF'.";
217-
$ruleResult[2].Reason | Should -BeIn "Path properties.value: Is set to 'OFF'.";
215+
$ruleResult[0].Reason | Should -BeExactly "A sub-resource of type 'Microsoft.DBforMySQL/flexibleServers/configurations' has not been specified.";
216+
$ruleResult[1].Reason | Should -BeExactly "Path properties.value: Is set to 'OFF'.";
217+
$ruleResult[2].Reason | Should -BeExactly "A sub-resource of type 'Microsoft.DBforMySQL/flexibleServers/configurations' has not been specified.";
218+
$ruleResult[3].Reason | Should -BeExactly "Path properties.value: Is set to 'OFF'.";
218219

219220
# Pass
220221
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
221222
$ruleResult | Should -Not -BeNullOrEmpty;
222223
$ruleResult.Length | Should -Be 2;
223224
$ruleResult.TargetName | Should -BeIn 'server-F', 'aad_auth_only-B';
224225
}
226+
227+
It 'Azure.MySQL.MaintenanceWindow' {
228+
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.MySQL.MaintenanceWindow' };
229+
230+
# Fail
231+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
232+
$ruleResult.Length | Should -Be 3;
233+
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-F';
234+
235+
$ruleResult[0].Reason | Should -BeExactly "Path properties.maintenanceWindow.customWindow: The field 'properties.maintenanceWindow.customWindow' does not exist.";
236+
$ruleResult[1].Reason | Should -BeExactly "Path properties.maintenanceWindow.customWindow: Is set to 'notset'.";
237+
$ruleResult[2].Reason | Should -BeExactly "Path properties.maintenanceWindow.customWindow: Is set to 'Disabled'.";
238+
239+
# Pass
240+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
241+
$ruleResult.Length | Should -Be 1;
242+
$ruleResult.TargetName | Should -BeIn 'server-G';
243+
}
225244
}
226245

227246
Context 'Resource name - Azure.MySQL.ServerName' {

tests/PSRule.Rules.Azure.Tests/Resources.MySQL.json

+51
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@
371371
"backup": {
372372
"backupRetentionDays": 7
373373
},
374+
"maintenanceWindow": {
375+
"customWindow": "notset",
376+
"dayOfWeek": "notset",
377+
"startHour": "notset",
378+
"startMinute": "notset"
379+
},
374380
"highAvailability": {
375381
"mode": "Disabled"
376382
},
@@ -434,6 +440,12 @@
434440
"backupRetentionDays": 7,
435441
"geoRedundantBackup": "Disabled"
436442
},
443+
"maintenanceWindow": {
444+
"customWindow": "Disabled",
445+
"dayOfWeek": 0,
446+
"startHour": 1,
447+
"startMinute": 0
448+
},
437449
"highAvailability": {
438450
"mode": "Disabled"
439451
},
@@ -480,6 +492,45 @@
480492
}
481493
]
482494
},
495+
{
496+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforMySQL/flexibleServers/server-G",
497+
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforMySQL/flexibleServers/server-G",
498+
"Location": "region",
499+
"ResourceName": "server-G",
500+
"Name": "server-G",
501+
"Properties": {
502+
"administratorLogin": "db-admin",
503+
"storage": {
504+
"autoGrow": "Enabled",
505+
"iops": 360,
506+
"storageSizeGB": 20
507+
},
508+
"backup": {
509+
"backupRetentionDays": 7,
510+
"geoRedundantBackup": "Disabled"
511+
},
512+
"maintenanceWindow": {
513+
"customWindow": "Enabled",
514+
"dayOfWeek": 0,
515+
"startHour": 1,
516+
"startMinute": 0
517+
},
518+
"highAvailability": {
519+
"mode": "Disabled"
520+
},
521+
"version": "8.0.21",
522+
"createMode": "Default"
523+
},
524+
"ResourceGroupName": "test-rg",
525+
"Type": "Microsoft.DBforMySQL/flexibleServers",
526+
"ResourceType": "Microsoft.DBforMySQL/flexibleServers",
527+
"Sku": {
528+
"Name": "E64",
529+
"Tier": "MemoryOptimized"
530+
},
531+
"Tags": null,
532+
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
533+
},
483534
{
484535
"Name": "ActiveDirectoryAdmin-A",
485536
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforMySQL/servers/server-B/administrators/ActiveDirectoryAdmin-A",

0 commit comments

Comments
 (0)