Skip to content

Commit b512d50

Browse files
Support for Azure Redis Cache and Cluster resources (#198)
* git rebase origin/main * Unit tests for Redis commands * Remove incomplete redis.bicep * dotnet format * Add Redis commands to docs * Add live tests for Redis * Redis CODEOWNERS * Address code review feedback * Cleanup PR issues * Accommodate changes in command Name/Desc/Title * Support for Azure Redis Caches and Clusters * Unit tests for Redis commands * dotnet format * Cleanup empty class declarations * Enable EntraID auth; disable local auth on cache * Remove duplicated code from merge error * dotnet format --------- Co-authored-by: Xiang Yan <xiangsjtu@gmail.com>
1 parent 4f8b313 commit b512d50

37 files changed

+1996
-0
lines changed

.github/CODEOWNERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@
116116
# ServiceLabel: %area-ServiceBus
117117
# ServiceOwners: @shankarsama @EldertGrootenboer
118118

119+
# PRLabel: %area-Redis
120+
/src/Arguments/Redis/ @philon-msft @xiangyan99
121+
/src/Commands/Redis/ @philon-msft @xiangyan99
122+
/src/Services/Azure/Redis/ @philon-msft @xiangyan99
123+
124+
# ServiceLabel: %area-Redis
125+
# ServiceOwners: @philon-msft @carldc
119126

120127
# PRLabel: %area-Storage
121128
/src/Arguments/Storage/ @alzimmermsft @jongio

.vscode/cspell.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
],
132132
"words": [
133133
"1espt",
134+
"accesspolicy",
134135
"ADMINPROVIDER",
135136
"alcoop",
136137
"appconfig",
@@ -150,6 +151,7 @@
150151
"gethealth",
151152
"healthmodels",
152153
"kcsb",
154+
"keyspace",
153155
"Kusto",
154156
"esrp",
155157
"ESRPRELPACMANTEST",
@@ -171,6 +173,7 @@
171173
"npmjs",
172174
"onboarded",
173175
"pscore",
176+
"RediSearch",
174177
"resourcegroup",
175178
"resourcegroups",
176179
"servicebus",

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Support for Azure Redis Caches and Clusters https://github.com/Azure/azure-mcp/pull/198
8+
79
### Breaking Changes
810

911
### Bugs Fixed

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
<PackageVersion Include="Azure.ResourceManager.AppConfiguration" Version="1.4.0" />
1414
<PackageVersion Include="Azure.ResourceManager.Kusto" Version="1.1.0" />
1515
<PackageVersion Include="Azure.ResourceManager.PostgreSql" Version="1.2.0" />
16+
<PackageVersion Include="Azure.ResourceManager.Redis" Version="1.5.0" />
17+
<PackageVersion Include="Azure.ResourceManager.RedisEnterprise" Version="1.1.0" />
1618
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.7.0" />
1719
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
1820
<PackageVersion Include="Azure.ResourceManager.CosmosDB" Version="1.3.2" />

docs/azmcp-commands.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,21 @@ azmcp servicebus topic subscription peek --subscription <subscription> --namespa
229229
azmcp servicebus topic subscription details --subscription <subscription> --namespace <service-bus-namespace> --topic-name <topic-name> --subscription-name <subscription-name>
230230
```
231231

232+
### Azure Redis Operations
233+
```bash
234+
# Lists Redis Clusters in the Azure Managed Redis or Azure Redis Enterprise services
235+
azmcp redis cluster list --subscription <subscription>
236+
237+
# Lists Databases in an Azure Redis Cluster
238+
azmcp redis cluster database list --subscription <subscription> --resource-group <resource-group> --cluster <cluster-name>
239+
240+
# Lists Redis Caches in the Azure Cache for Redis service
241+
azmcp redis cache list --subscription <subscription>
242+
243+
# Lists Access Policy Assignments in an Azure Redis Cache
244+
azmcp redis cache list accesspolicy --subscription <subscription> --resource-group <resource-group> --cache <cache-name>
245+
```
246+
232247
### Azure Resource Group Operations
233248
```bash
234249
# List resource groups in a subscription

infra/services/redis.bicep

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
targetScope = 'resourceGroup'
2+
3+
@minLength(3)
4+
@maxLength(24)
5+
@description('The base resource name.')
6+
param baseName string = resourceGroup().name
7+
8+
@description('The location of the resource. By default, this is the same as the resource group.')
9+
param location string = resourceGroup().location
10+
11+
@description('The tenant ID to which the application and resources belong.')
12+
param tenantId string = '72f988bf-86f1-41af-91ab-2d7cd011db47'
13+
14+
@description('The client OID to grant access to test resources.')
15+
param testApplicationOid string
16+
17+
resource redisCache 'Microsoft.Cache/Redis@2024-11-01' = {
18+
name: baseName
19+
location: location
20+
properties: {
21+
enableNonSslPort: false
22+
minimumTlsVersion: '1.2'
23+
disableAccessKeyAuthentication: true
24+
sku: {
25+
capacity: 0
26+
family: 'C'
27+
name: 'Basic'
28+
}
29+
redisConfiguration: {
30+
'aad-enabled': 'true'
31+
}
32+
}
33+
}
34+
35+
resource redisCacheAccessPolicyAssignment 'Microsoft.Cache/Redis/accessPolicyAssignments@2024-11-01' = {
36+
parent: redisCache
37+
name: baseName
38+
properties: {
39+
accessPolicyName: 'Data Owner'
40+
objectId: testApplicationOid
41+
objectIdAlias: testApplicationOid
42+
}
43+
}
44+
45+
resource redisCluster 'Microsoft.Cache/redisEnterprise@2025-05-01-preview' = {
46+
location: location
47+
name: baseName
48+
sku: {
49+
name: 'Balanced_B0'
50+
}
51+
identity: {
52+
type: 'None'
53+
}
54+
properties: {
55+
minimumTlsVersion: '1.2'
56+
}
57+
}
58+
59+
resource redisClusterDatabase 'Microsoft.Cache/redisEnterprise/databases@2025-05-01-preview' = {
60+
parent: redisCluster
61+
name: 'default'
62+
properties:{
63+
clientProtocol: 'Encrypted'
64+
port: 10000
65+
clusteringPolicy: 'OSSCluster'
66+
evictionPolicy: 'NoEviction'
67+
persistence:{
68+
aofEnabled: false
69+
rdbEnabled: false
70+
}
71+
}
72+
}

infra/test-resources.bicep

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ module servicebus 'services/servicebus.bicep' = {
7676
}
7777
}
7878

79+
module redis 'services/redis.bicep' = {
80+
name: '${deploymentName}-redis'
81+
params: {
82+
baseName: baseName
83+
location: location
84+
tenantId: tenantId
85+
testApplicationOid: testApplicationOid
86+
}
87+
}
88+
7989
module kusto 'services/kusto.bicep' = {
8090
name: '${deploymentName}-kusto'
8191
params: {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace AzureMcp.Arguments.Redis.CacheForRedis;
5+
6+
public class AccessPolicyListArguments : BaseCacheArguments;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json.Serialization;
5+
using AzureMcp.Models.Argument;
6+
7+
namespace AzureMcp.Arguments.Redis.CacheForRedis;
8+
9+
public class BaseCacheArguments : SubscriptionArguments
10+
{
11+
[JsonPropertyName(ArgumentDefinitions.Redis.CacheName)]
12+
public string? Cache { get; set; }
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace AzureMcp.Arguments.Redis.CacheForRedis;
5+
6+
public class CacheListArguments : SubscriptionArguments;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json.Serialization;
5+
using AzureMcp.Models.Argument;
6+
7+
namespace AzureMcp.Arguments.Redis.ManagedRedis;
8+
9+
public class BaseClusterArguments : SubscriptionArguments
10+
{
11+
[JsonPropertyName(ArgumentDefinitions.Redis.ClusterName)]
12+
public string? Cluster { get; set; }
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace AzureMcp.Arguments.Redis.ManagedRedis;
5+
6+
public class ClusterListArguments : SubscriptionArguments;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace AzureMcp.Arguments.Redis.ManagedRedis;
5+
6+
public class DatabaseListArguments : BaseClusterArguments;

src/AzureMcp.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
<PackageReference Include="Azure.ResourceManager.AppConfiguration" />
5252
<PackageReference Include="Azure.ResourceManager.Kusto" />
5353
<PackageReference Include="Azure.ResourceManager.PostgreSql" />
54+
<PackageReference Include="Azure.ResourceManager.Redis" />
55+
<PackageReference Include="Azure.ResourceManager.RedisEnterprise" />
5456
<PackageReference Include="Azure.Security.KeyVault.Keys" />
5557
<PackageReference Include="Azure.Storage.Blobs" />
5658
<PackageReference Include="Azure.ResourceManager.CosmosDB" />

src/Commands/CommandFactory.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private void RegisterCommandGroup()
8787
RegisterGroupCommands();
8888
RegisterMcpServerCommands();
8989
RegisterServiceBusCommands();
90+
RegisterRedisCommands();
9091
}
9192

9293
private void RegisterBestPracticesCommand()
@@ -372,6 +373,34 @@ private void RegisterServiceBusCommands()
372373
topic.AddSubGroup(subscription);
373374
}
374375

376+
private void RegisterRedisCommands()
377+
{
378+
var redis = new CommandGroup("redis", "Redis Cache operations - Commands for managing and accessing Azure Redis Cache resources.");
379+
_rootGroup.AddSubGroup(redis);
380+
381+
// Azure Cache for Redis
382+
var cache = new CommandGroup("cache", "Redis Cache resource operations - Commands for listing and managing Redis Cache resources in your Azure subscription.");
383+
redis.AddSubGroup(cache);
384+
385+
cache.AddCommand("list", new Redis.CacheForRedis.CacheListCommand(GetLogger<Redis.CacheForRedis.CacheListCommand>()));
386+
387+
var accessPolicy = new CommandGroup("accesspolicy", "Redis Cluster database operations - Commands for listing and managing Redis Cluster databases in your Azure subscription.");
388+
cache.AddSubGroup(accessPolicy);
389+
390+
accessPolicy.AddCommand("list", new Redis.CacheForRedis.AccessPolicyListCommand(GetLogger<Redis.CacheForRedis.AccessPolicyListCommand>()));
391+
392+
// Azure Managed Redis
393+
var cluster = new CommandGroup("cluster", "Redis Cluster resource operations - Commands for listing and managing Redis Cluster resources in your Azure subscription.");
394+
redis.AddSubGroup(cluster);
395+
396+
cluster.AddCommand("list", new Redis.ManagedRedis.ClusterListCommand(GetLogger<Redis.ManagedRedis.ClusterListCommand>()));
397+
398+
var database = new CommandGroup("database", "Redis Cluster database operations - Commands for listing and managing Redis Cluster Databases in your Azure subscription.");
399+
cluster.AddSubGroup(database);
400+
401+
database.AddCommand("list", new Redis.ManagedRedis.DatabaseListCommand(GetLogger<Redis.ManagedRedis.DatabaseListCommand>()));
402+
}
403+
375404
private void ConfigureCommands(CommandGroup group)
376405
{
377406
// Configure direct commands in this group
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.CommandLine.Parsing;
5+
using AzureMcp.Arguments.Redis.CacheForRedis;
6+
using AzureMcp.Models.Command;
7+
using AzureMcp.Models.Redis.CacheForRedis;
8+
using AzureMcp.Services.Interfaces;
9+
using Microsoft.Extensions.Logging;
10+
using ModelContextProtocol.Server;
11+
12+
namespace AzureMcp.Commands.Redis.CacheForRedis;
13+
14+
/// <summary>
15+
/// Lists the access policy assignments in the specified Azure cache.
16+
/// </summary>
17+
public sealed class AccessPolicyListCommand(ILogger<AccessPolicyListCommand> logger) : BaseCacheCommand<AccessPolicyListArguments>()
18+
{
19+
private const string _commandTitle = "List Redis Cache Access Policy Assignments";
20+
private readonly ILogger<AccessPolicyListCommand> _logger = logger;
21+
22+
public override string Name => "list";
23+
public override string Description =>
24+
$"""
25+
List the Access Policies and Assignments for the specified Redis cache. Returns an array of Redis Access Policy Assignment details.
26+
Use this command to explore which Access Policies have been assigned to which identities for your Redis cache.
27+
""";
28+
29+
public override string Title => _commandTitle;
30+
31+
[McpServerTool(Destructive = false, ReadOnly = true, Title = _commandTitle)]
32+
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
33+
{
34+
try
35+
{
36+
var args = BindArguments(parseResult);
37+
38+
if (!await ProcessArguments(context, args))
39+
{
40+
return context.Response;
41+
}
42+
43+
var redisService = context.GetService<IRedisService>() ?? throw new InvalidOperationException("Redis service is not available.");
44+
var accessPolicyAssignments = await redisService.ListAccessPolicyAssignmentsAsync(
45+
args.Cache!,
46+
args.ResourceGroup!,
47+
args.Subscription!,
48+
args.Tenant,
49+
args.AuthMethod,
50+
args.RetryPolicy);
51+
52+
context.Response.Results = accessPolicyAssignments.Any() ?
53+
ResponseResult.Create(
54+
new AccessPolicyListCommandResult(accessPolicyAssignments),
55+
RedisJsonContext.Default.AccessPolicyListCommandResult) :
56+
null;
57+
}
58+
catch (Exception ex)
59+
{
60+
_logger.LogError(ex, "Failed to list Redis Access Policy Assignments");
61+
HandleException(context.Response, ex);
62+
}
63+
64+
return context.Response;
65+
}
66+
67+
internal record AccessPolicyListCommandResult(IEnumerable<AccessPolicyAssignment> AccessPolicyAssignments);
68+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.CommandLine;
5+
using System.CommandLine.Parsing;
6+
using System.Diagnostics.CodeAnalysis;
7+
using AzureMcp.Arguments.Redis.CacheForRedis;
8+
using AzureMcp.Models.Argument;
9+
10+
namespace AzureMcp.Commands.Redis.CacheForRedis;
11+
12+
public abstract class BaseCacheCommand<
13+
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] T>
14+
: SubscriptionCommand<T> where T : BaseCacheArguments, new()
15+
{
16+
protected readonly Option<string> _cacheOption = ArgumentDefinitions.Redis.Cache.ToOption();
17+
18+
protected override void RegisterOptions(Command command)
19+
{
20+
base.RegisterOptions(command);
21+
command.AddOption(_cacheOption);
22+
command.AddOption(_resourceGroupOption);
23+
}
24+
25+
protected override void RegisterArguments()
26+
{
27+
base.RegisterArguments();
28+
AddArgument(CreateCacheArgument());
29+
AddArgument(CreateResourceGroupArgument());
30+
}
31+
32+
protected override T BindArguments(ParseResult parseResult)
33+
{
34+
var args = base.BindArguments(parseResult);
35+
args.Cache = parseResult.GetValueForOption(_cacheOption);
36+
args.ResourceGroup = parseResult.GetValueForOption(_resourceGroupOption) ?? ArgumentDefinitions.Common.ResourceGroup.DefaultValue;
37+
return args;
38+
}
39+
40+
protected ArgumentBuilder<T> CreateCacheArgument() =>
41+
ArgumentBuilder<T>
42+
.Create(ArgumentDefinitions.Redis.Cache.Name, ArgumentDefinitions.Redis.Cache.Description)
43+
.WithValueAccessor(args => args.Cache ?? string.Empty)
44+
.WithIsRequired(ArgumentDefinitions.Redis.Cache.Required);
45+
}

0 commit comments

Comments
 (0)