Skip to content

Commit 727cb6e

Browse files
Added Azure.RedisEnterprise.MigrateAMR (#3612)
1 parent 76c88e4 commit 727cb6e

File tree

6 files changed

+217
-22
lines changed

6 files changed

+217
-22
lines changed

docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
4545
- `AZURE_REDIS_CACHE_NAME_FORMAT`
4646
- `AZURE_REDIS_ENTERPRISE_NAME_FORMAT`
4747
- Added configured name format by @BernieWhite.
48+
- Azure Cache for Redis Enterprise and Enterprise Flash:
49+
- Check for deprecated Redis Enterprise and Enterprise Flash SKUs by @BenjaminEngeset.
50+
[#3606](https://github.com/Azure/PSRule.Rules.Azure/issues/3606)
4851
- Azure Database for MySQL:
4952
- Check resources naming matches configured name format by @BernieWhite.
5053
[#3548](https://github.com/Azure/PSRule.Rules.Azure/issues/3548)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
reviewed: 2025-11-28
3+
severity: Important
4+
pillar: Operational Excellence
5+
category: OE:05 Infrastructure as code
6+
resource: Azure Cache for Redis Enterprise
7+
resourceType: Microsoft.Cache/redisEnterprise
8+
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.RedisEnterprise.MigrateAMR/
9+
---
10+
11+
# Migrate to Azure Managed Redis
12+
13+
## SYNOPSIS
14+
15+
Azure Cache for Redis Enterprise and Enterprise Flash are being retired. Migrate to Azure Managed Redis.
16+
17+
## DESCRIPTION
18+
19+
Microsoft has announced the retirement timeline for Azure Cache for Redis Enterprise and Enterprise Flash SKUs.
20+
The recommended replacement going forward is Azure Managed Redis.
21+
22+
Azure Cache for Redis Enterprise (`Enterprise_*`) and Enterprise Flash (`EnterpriseFlash_*`) SKUs will be retired according to the following timeline:
23+
24+
- Creation blocked for all customers: April 1, 2026.
25+
- Retirement Date: March 31, 2027.
26+
- Instances will be migrated to Azure Managed Redis starting April 1, 2027.
27+
28+
To avoid service disruption, migrate your workloads to Azure Managed Redis.
29+
30+
## RECOMMENDATION
31+
32+
Plan and execute migration from Azure Cache for Redis Enterprise / Enterprise Flash to Azure Managed Redis before the retirement dates to avoid service disruption.
33+
34+
## EXAMPLES
35+
36+
### Configure with Bicep
37+
38+
To deploy resource that pass this rule:
39+
40+
- Create resources of type `Microsoft.Cache/redisEnterprise` and an Azure Managed Redis SKU, such as:
41+
- `Balanced_*`
42+
- `MemoryOptimized_*`
43+
- `ComputeOptimized_*`
44+
45+
For example:
46+
47+
```bicep
48+
resource primary 'Microsoft.Cache/redisEnterprise@2025-07-01' = {
49+
name: name
50+
location: location
51+
properties: {
52+
highAvailability: 'Enabled'
53+
publicNetworkAccess: 'Disabled'
54+
}
55+
sku: {
56+
name: 'Balanced_B10'
57+
}
58+
}
59+
```
60+
61+
### Configure with Azure template
62+
63+
To deploy resource that pass this rule:
64+
65+
- Create resources of type `Microsoft.Cache/redisEnterprise` and an Azure Managed Redis SKU, such as:
66+
- `Balanced_*`
67+
- `MemoryOptimized_*`
68+
- `ComputeOptimized_*`
69+
70+
For example:
71+
72+
```json
73+
{
74+
"type": "Microsoft.Cache/redisEnterprise",
75+
"apiVersion": "2025-07-01",
76+
"name": "[parameters('name')]",
77+
"location": "[parameters('location')]",
78+
"properties": {
79+
"highAvailability": "Enabled",
80+
"publicNetworkAccess": "Disabled"
81+
},
82+
"sku": {
83+
"name": "Balanced_B10"
84+
}
85+
}
86+
```
87+
88+
## NOTES
89+
90+
Azure Cache for Redis (Basic, Standard, Premium) using the `Microsoft.Cache/redis` resource type is also deprecated and covered by a separate rule.
91+
92+
## LINKS
93+
94+
- [OE:05 Infrastructure as code](https://learn.microsoft.com/azure/architecture/framework/devops/automation-infrastructure)
95+
- [Operational Excellence: Level 4](https://learn.microsoft.com/azure/well-architected/operational-excellence/maturity-model?tabs=level4)
96+
- [Azure Cache for Redis retirement: What to know and how to prepare](https://techcommunity.microsoft.com/blog/azure-managed-redis/azure-cache-for-redis-retirement-what-to-know-and-how-to-prepare/4458721)
97+
- [Azure Cache for Redis retirement FAQ](https://learn.microsoft.com/azure/azure-cache-for-redis/retirement-faq)
98+
- [Azure Managed Redis documentation](https://learn.microsoft.com/azure/azure-cache-for-redis/managed-redis/managed-redis-overview)
99+
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.cache/redisenterprise)

src/PSRule.Rules.Azure/en/PSRule-rules.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@
132132
ActiveSecurityAlerts = "There are {0} active security alerts of high or medium severity."
133133
KeyValueShouldNotContainSecrets = "The key value '{0}' property should not contain secrets."
134134
CacheRedisMigrateAMR = "Azure Cache for Redis is being retired. Migrate to Azure Managed Redis."
135+
RedisEnterpriseMigrateAMR = "Azure Cache for Redis Enterprise and Enterprise Flash are being retired. Migrate to Azure Managed Redis."
135136
}

src/PSRule.Rules.Azure/rules/Azure.RedisEnterprise.Rule.ps1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ Rule 'Azure.RedisEnterprise.Naming' -Ref 'AZR-000524' -Type 'Microsoft.Cache/Red
1313
}
1414

1515
#endregion Naming rules
16+
17+
# Synopsis: Azure Cache for Redis Enterprise and Enterprise Flash are being retired. Migrate to Azure Managed Redis.
18+
Rule 'Azure.RedisEnterprise.MigrateAMR' -Ref 'AZR-000534' -Type 'Microsoft.Cache/redisEnterprise' -Tag @{ release = 'GA'; ruleSet = '2025_12'; 'Azure.WAF/pillar' = 'Operational Excellence'; } {
19+
$Assert.NotLike($TargetObject, 'sku.name', @('Enterprise_*', 'EnterpriseFlash_*')).Reason($LocalizedData.RedisEnterpriseMigrateAMR)
20+
}

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

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
118118
# None
119119
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
120120
$ruleResult | Should -Not -BeNullOrEmpty;
121-
$ruleResult.Length | Should -Be 8;
122-
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-R', 'redis-S';
121+
$ruleResult.Length | Should -Be 11;
122+
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-R', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
123123
}
124124

125125
It 'Azure.Redis.AvailabilityZone' {
@@ -146,8 +146,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
146146
# None
147147
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
148148
$ruleResult | Should -Not -BeNullOrEmpty;
149-
$ruleResult.Length | Should -Be 13;
150-
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S';
149+
$ruleResult.Length | Should -Be 16;
150+
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
151151
}
152152

153153
It 'Azure.RedisEnterprise.Zones' {
@@ -173,8 +173,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
173173
# None
174174
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
175175
$ruleResult | Should -Not -BeNullOrEmpty;
176-
$ruleResult.Length | Should -Be 12;
177-
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R';
176+
$ruleResult.Length | Should -Be 15;
177+
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R', 'redis-T', 'redis-U', 'redis-V';
178178
}
179179

180180
It 'Azure.Redis.PublicNetworkAccess' {
@@ -214,8 +214,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
214214
# None
215215
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
216216
$ruleResult | Should -Not -BeNullOrEmpty;
217-
$ruleResult.Length | Should -Be 7;
218-
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S';
217+
$ruleResult.Length | Should -Be 10;
218+
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
219219
}
220220

221221
It 'Azure.Redis.FirewallRuleCount' {
@@ -253,8 +253,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
253253
# None
254254
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
255255
$ruleResult | Should -Not -BeNullOrEmpty
256-
$ruleResult.Length | Should -Be 9;
257-
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-F', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S';
256+
$ruleResult.Length | Should -Be 12;
257+
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-F', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
258258
}
259259

260260
It 'Azure.Redis.FirewallIPRange' {
@@ -290,8 +290,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
290290
# None
291291
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
292292
$ruleResult | Should -Not -BeNullOrEmpty
293-
$ruleResult.Length | Should -Be 9;
294-
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-F', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S';
293+
$ruleResult.Length | Should -Be 12;
294+
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-F', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
295295
}
296296

297297
It 'Azure.Redis.Version' {
@@ -334,8 +334,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
334334

335335
# None
336336
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
337-
$ruleResult.Length | Should -Be 7;
338-
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S';
337+
$ruleResult.Length | Should -Be 10;
338+
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
339339
}
340340

341341
It 'Azure.Redis.LocalAuth' {
@@ -365,6 +365,24 @@ Describe 'Azure.Redis' -Tag 'Redis' {
365365

366366
$ruleResult[0].Reason | Should -BeExactly "Azure Cache for Redis is being retired. Migrate to Azure Managed Redis.";
367367
}
368+
369+
It 'Azure.RedisEnterprise.MigrateAMR' {
370+
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.RedisEnterprise.MigrateAMR' };
371+
372+
# Fail - Enterprise and EnterpriseFlash SKUs should fail
373+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
374+
$ruleResult | Should -Not -BeNullOrEmpty;
375+
$ruleResult.Length | Should -Be 7;
376+
$ruleResult.TargetName | Should -BeIn 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-S';
377+
378+
$ruleResult[0].Reason | Should -BeExactly "Azure Cache for Redis Enterprise and Enterprise Flash are being retired. Migrate to Azure Managed Redis.";
379+
380+
# Pass - Azure Managed Redis SKUs should pass
381+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
382+
$ruleResult | Should -Not -BeNullOrEmpty;
383+
$ruleResult.Length | Should -Be 3;
384+
$ruleResult.TargetName | Should -BeIn 'redis-T', 'redis-U', 'redis-V';
385+
}
368386
}
369387

370388
Context 'With Configuration Option' -Tag 'Configuration' {
@@ -421,8 +439,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
421439
# None
422440
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
423441
$ruleResult | Should -Not -BeNullOrEmpty;
424-
$ruleResult.Length | Should -Be 13;
425-
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S';
442+
$ruleResult.Length | Should -Be 16;
443+
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
426444
}
427445

428446
It 'Azure.Redis.AvailabilityZone - YAML file option' {
@@ -455,8 +473,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
455473
# None
456474
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
457475
$ruleResult | Should -Not -BeNullOrEmpty;
458-
$ruleResult.Length | Should -Be 13;
459-
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S';
476+
$ruleResult.Length | Should -Be 16;
477+
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-K', 'redis-L', 'redis-M', 'redis-N', 'redis-O', 'redis-P', 'redis-Q', 'redis-R', 'redis-S', 'redis-T', 'redis-U', 'redis-V';
460478
}
461479

462480
It 'Azure.RedisEnterprise.Zones - HashTable option' {
@@ -498,8 +516,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
498516
# None
499517
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
500518
$ruleResult | Should -Not -BeNullOrEmpty;
501-
$ruleResult.Length | Should -Be 12;
502-
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R';
519+
$ruleResult.Length | Should -Be 15;
520+
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R', 'redis-T', 'redis-U', 'redis-V';
503521
}
504522

505523
It 'Azure.RedisEnterprise.Zones - YAML file option' {
@@ -530,8 +548,8 @@ Describe 'Azure.Redis' -Tag 'Redis' {
530548
# None
531549
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' });
532550
$ruleResult | Should -Not -BeNullOrEmpty;
533-
$ruleResult.Length | Should -Be 12;
534-
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R';
551+
$ruleResult.Length | Should -Be 15;
552+
$ruleResult.TargetName | Should -Be 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R', 'redis-T', 'redis-U', 'redis-V';
535553
}
536554
}
537555

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,5 +1121,74 @@
11211121
"capacity": 15
11221122
},
11231123
"zones": []
1124+
},
1125+
{
1126+
"Name": "redis-T",
1127+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Cache/redisEnterprise/redis-T",
1128+
"ResourceName": "redis-T",
1129+
"ResourceType": "Microsoft.Cache/redisEnterprise",
1130+
"ResourceGroupName": "test-rg",
1131+
"Location": "australiaeast",
1132+
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
1133+
"Tags": {},
1134+
"Properties": {
1135+
"minimumTlsVersion": "1.2",
1136+
"hostName": "redis-T.redis.cache.windows.net"
1137+
},
1138+
"sku": {
1139+
"name": "Balanced_B10",
1140+
"capacity": 2
1141+
},
1142+
"zones": [
1143+
"1",
1144+
"2",
1145+
"3"
1146+
]
1147+
},
1148+
{
1149+
"Name": "redis-U",
1150+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Cache/redisEnterprise/redis-U",
1151+
"ResourceName": "redis-U",
1152+
"ResourceType": "Microsoft.Cache/redisEnterprise",
1153+
"ResourceGroupName": "test-rg",
1154+
"Location": "australiaeast",
1155+
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
1156+
"Tags": {},
1157+
"Properties": {
1158+
"minimumTlsVersion": "1.2",
1159+
"hostName": "redis-U.redis.cache.windows.net"
1160+
},
1161+
"sku": {
1162+
"name": "MemoryOptimized_M10",
1163+
"capacity": 2
1164+
},
1165+
"zones": [
1166+
"1",
1167+
"2",
1168+
"3"
1169+
]
1170+
},
1171+
{
1172+
"Name": "redis-V",
1173+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Cache/redisEnterprise/redis-V",
1174+
"ResourceName": "redis-V",
1175+
"ResourceType": "Microsoft.Cache/redisEnterprise",
1176+
"ResourceGroupName": "test-rg",
1177+
"Location": "australiaeast",
1178+
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
1179+
"Tags": {},
1180+
"Properties": {
1181+
"minimumTlsVersion": "1.2",
1182+
"hostName": "redis-V.redis.cache.windows.net"
1183+
},
1184+
"sku": {
1185+
"name": "ComputeOptimized_X10",
1186+
"capacity": 2
1187+
},
1188+
"zones": [
1189+
"1",
1190+
"2",
1191+
"3"
1192+
]
11241193
}
11251194
]

0 commit comments

Comments
 (0)