Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
167 changes: 90 additions & 77 deletions .vscode/cspell.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
<PackageVersion Include="Azure.ResourceManager.Authorization" Version="1.1.5" />
<PackageVersion Include="Azure.ResourceManager.ContainerRegistry" Version="1.3.1" />
<PackageVersion Include="Azure.ResourceManager.DesktopVirtualization" Version="1.3.2" />
<PackageVersion Include="Azure.ResourceManager.ContainerService" Version="1.2.5" />
<PackageVersion Include="Azure.ResourceManager.EventGrid" Version="1.2.0-beta.2" />
<PackageVersion Include="Azure.ResourceManager.Kusto" Version="1.6.1" />
<PackageVersion Include="Azure.ResourceManager.CognitiveServices" Version="1.5.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Azure.Mcp.Core.Options;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Core.Services.Azure.Tenant;
using Azure.ResourceManager;
using Azure.ResourceManager.ResourceGraph;
using Azure.ResourceManager.ResourceGraph.Models;
using Azure.ResourceManager.Resources;
Expand Down Expand Up @@ -48,12 +49,26 @@ private async Task<TenantResource> GetTenantResourceAsync(Guid? tenantId, Cancel
return tenantResource;
}

/// <summary>
/// Validates that the specified resource group exists within the given subscription.
/// </summary>
/// <param name="subscriptionResource">The subscription resource to check against.</param>
/// <param name="resourceGroupName">The name of the resource group to validate.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>True if the resource group exists; otherwise, false.</returns>
private async Task<bool> ValidateResourceGroupExistsAsync(SubscriptionResource subscriptionResource, string resourceGroupName, CancellationToken cancellationToken = default)
{
var resourceGroupCollection = subscriptionResource.GetResourceGroups();
var result = await resourceGroupCollection.ExistsAsync(resourceGroupName, cancellationToken).ConfigureAwait(false);
return result.Value;
}

/// <summary>
/// Executes a Resource Graph query and returns a list of resources of the specified type.
/// </summary>
/// <typeparam name="T">The type to convert each resource to</typeparam>
/// <param name="resourceType">The Azure resource type to query for (e.g., "Microsoft.Sql/servers/databases")</param>
/// <param name="resourceGroup">The resource group name to filter by</param>
/// <param name="resourceGroup">The resource group name to filter by (null to query all resource groups)</param>
/// <param name="subscription">The subscription ID or name</param>
/// <param name="retryPolicy">Optional retry policy configuration</param>
/// <param name="converter">Function to convert JsonElement to the target type</param>
Expand All @@ -63,23 +78,31 @@ private async Task<TenantResource> GetTenantResourceAsync(Guid? tenantId, Cancel
/// <returns>List of resources converted to the specified type</returns>
protected async Task<List<T>> ExecuteResourceQueryAsync<T>(
string resourceType,
string resourceGroup,
string? resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
Func<JsonElement, T> converter,
string? additionalFilter = null,
int limit = 50,
CancellationToken cancellationToken = default)
{
ValidateRequiredParameters(resourceType, resourceGroup, subscription);
ValidateRequiredParameters(resourceType, subscription);
ArgumentNullException.ThrowIfNull(converter);

var results = new List<T>();

var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy);
var tenantResource = await GetTenantResourceAsync(subscriptionResource.Data.TenantId, cancellationToken);

var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}' and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}'";
if (!string.IsNullOrEmpty(resourceGroup))
{
if (!await ValidateResourceGroupExistsAsync(subscriptionResource, resourceGroup, cancellationToken))
{
throw new KeyNotFoundException($"Resource group '{resourceGroup}' does not exist in subscription '{subscriptionResource.Data.SubscriptionId}'");
}
queryFilter += $" and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
}
if (!string.IsNullOrEmpty(additionalFilter))
{
queryFilter += $" and {additionalFilter}";
Expand Down Expand Up @@ -113,7 +136,7 @@ protected async Task<List<T>> ExecuteResourceQueryAsync<T>(
/// </summary>
/// <typeparam name="T">The type to convert the resource to</typeparam>
/// <param name="resourceType">The Azure resource type to query for (e.g., "Microsoft.Sql/servers/databases")</param>
/// <param name="resourceGroup">The resource group name to filter by</param>
/// <param name="resourceGroup">The resource group name to filter by (null to query all resource groups)</param>
/// <param name="subscription">The subscription ID or name</param>
/// <param name="retryPolicy">Optional retry policy configuration</param>
/// <param name="converter">Function to convert JsonElement to the target type</param>
Expand All @@ -122,20 +145,28 @@ protected async Task<List<T>> ExecuteResourceQueryAsync<T>(
/// <returns>Single resource converted to the specified type, or null if not found</returns>
protected async Task<T?> ExecuteSingleResourceQueryAsync<T>(
string resourceType,
string resourceGroup,
string? resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
Func<JsonElement, T> converter,
string? additionalFilter = null,
CancellationToken cancellationToken = default) where T : class
{
ValidateRequiredParameters(resourceType, resourceGroup, subscription);
ValidateRequiredParameters(resourceType, subscription);
ArgumentNullException.ThrowIfNull(converter);

var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy);
var tenantResource = await GetTenantResourceAsync(subscriptionResource.Data.TenantId, cancellationToken);

var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}' and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}'";
if (!string.IsNullOrEmpty(resourceGroup))
{
if (!await ValidateResourceGroupExistsAsync(subscriptionResource, resourceGroup, cancellationToken))
{
throw new KeyNotFoundException($"Resource group '{resourceGroup}' does not exist in subscription '{subscriptionResource.Data.SubscriptionId}'");
}
queryFilter += $" and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
}
if (!string.IsNullOrEmpty(additionalFilter))
{
queryFilter += $" and {additionalFilter}";
Expand Down
1 change: 0 additions & 1 deletion docs/new-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -1961,7 +1961,6 @@ Before submitting:
- [ ] **AOT compilation verified** with `./eng/scripts/Build-Local.ps1 -BuildNative`
- [ ] **Clean up unused using statements**: Run `dotnet format --include="tools/Azure.Mcp.Tools.{Toolset}/**/*.cs"` to remove unnecessary imports and ensure consistent formatting
- [ ] Fix formatting issues with `dotnet format ./AzureMcp.sln` and ensure no warnings
- [ ] Identify unused properties for Azure Resource with `.\eng\scripts\Check-Unused-ResourceProperties.ps1`

### Azure SDK Integration
- [ ] All Azure SDK property names verified and correct
Expand Down
Loading