-
Notifications
You must be signed in to change notification settings - Fork 423
Add azmcp containerapps list command
#1981
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
base: main
Are you sure you want to change the base?
Changes from all commits
0280abd
af27706
60affb5
40e6c16
66a2ab8
fe35e7f
13c02fd
192566f
b11ffce
ca36c86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| pr: 1981 | ||
| changes: | ||
| - section: "Features Added" | ||
| description: "Added `azmcp containerapps list` command to list Azure Container Apps in a subscription" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Runtime.CompilerServices; | ||
|
|
||
| [assembly: InternalsVisibleTo("Azure.Mcp.Tools.ContainerApps.UnitTests")] | ||
| [assembly: InternalsVisibleTo("Azure.Mcp.Tools.ContainerApps.LiveTests")] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <IsAotCompatible>true</IsAotCompatible> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <EmbeddedResource Include="**\Resources\*.txt" /> | ||
| <EmbeddedResource Include="**\Resources\*.json" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="Azure.ResourceManager" /> | ||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> | ||
| <PackageReference Include="ModelContextProtocol" /> | ||
| <PackageReference Include="System.CommandLine" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using Azure.Mcp.Core.Commands.Subscription; | ||
| using Azure.Mcp.Core.Extensions; | ||
| using Azure.Mcp.Core.Models.Option; | ||
| using Microsoft.Mcp.Core.Models.Option; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Commands; | ||
|
|
||
| public abstract class BaseContainerAppsCommand< | ||
| [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions> | ||
| : SubscriptionCommand<TOptions> where TOptions : SubscriptionOptions, new() | ||
| { | ||
| protected override void RegisterOptions(Command command) | ||
| { | ||
| base.RegisterOptions(command); | ||
| command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); | ||
| } | ||
|
|
||
| protected override TOptions BindOptions(ParseResult parseResult) | ||
| { | ||
| var options = base.BindOptions(parseResult); | ||
| options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name); | ||
| return options; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Azure.Mcp.Tools.ContainerApps.Options.ContainerApp; | ||
| using Azure.Mcp.Tools.ContainerApps.Services; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Mcp.Core.Commands; | ||
| using Microsoft.Mcp.Core.Models.Command; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; | ||
|
|
||
| public sealed class ContainerAppListCommand(ILogger<ContainerAppListCommand> logger, IContainerAppsService containerAppsService) : BaseContainerAppsCommand<ContainerAppListOptions> | ||
| { | ||
| private const string CommandTitle = "List Container Apps"; | ||
| private readonly ILogger<ContainerAppListCommand> _logger = logger; | ||
| private readonly IContainerAppsService _containerAppsService = containerAppsService; | ||
|
|
||
| public override string Id => "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90"; | ||
|
|
||
| public override string Name => "list"; | ||
|
|
||
| public override string Description => | ||
| $""" | ||
| List Azure Container Apps in a subscription. Optionally filter by resource group. Each container app result | ||
| includes: name, location, resourceGroup, managedEnvironmentId, provisioningState. If no container apps are | ||
| found the tool returns an empty list of results (consistent with other list commands). | ||
| """; | ||
|
|
||
| public override string Title => CommandTitle; | ||
|
|
||
| public override ToolMetadata Metadata => new() | ||
| { | ||
| Destructive = false, | ||
| Idempotent = true, | ||
| OpenWorld = false, | ||
| ReadOnly = true, | ||
| LocalRequired = false, | ||
| Secret = false | ||
| }; | ||
|
|
||
| public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) | ||
| { | ||
| if (!Validate(parseResult.CommandResult, context.Response).IsValid) | ||
| { | ||
| return context.Response; | ||
| } | ||
|
|
||
| var options = BindOptions(parseResult); | ||
|
|
||
| try | ||
| { | ||
| var containerApps = await _containerAppsService.ListContainerApps( | ||
| options.Subscription!, | ||
| options.ResourceGroup, | ||
| options.RetryPolicy, | ||
| cancellationToken); | ||
|
|
||
| context.Response.Results = ResponseResult.Create(new(containerApps?.Results ?? [], containerApps?.AreResultsTruncated ?? false), ContainerAppsJsonContext.Default.ContainerAppListCommandResult); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, | ||
| "Error listing container apps. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, Options: {@Options}", | ||
| options.Subscription, options.ResourceGroup, options); | ||
| HandleException(context, ex); | ||
| } | ||
|
|
||
| return context.Response; | ||
| } | ||
|
|
||
| internal record ContainerAppListCommandResult(List<Models.ContainerAppInfo> ContainerApps, bool AreResultsTruncated); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Text.Json.Serialization; | ||
| using Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Commands; | ||
|
|
||
| [JsonSerializable(typeof(ContainerAppListCommand.ContainerAppListCommandResult))] | ||
| [JsonSerializable(typeof(Models.ContainerAppInfo))] | ||
| [JsonSourceGenerationOptions( | ||
| PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, | ||
| DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] | ||
| internal sealed partial class ContainerAppsJsonContext : JsonSerializerContext | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; | ||
| using Azure.Mcp.Tools.ContainerApps.Services; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Mcp.Core.Areas; | ||
| using Microsoft.Mcp.Core.Commands; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps; | ||
|
|
||
| public class ContainerAppsSetup : IAreaSetup | ||
| { | ||
| public string Name => "containerapps"; | ||
|
|
||
| public string Title => "Azure Container Apps Management"; | ||
|
|
||
| public void ConfigureServices(IServiceCollection services) | ||
| { | ||
| services.AddSingleton<IContainerAppsService, ContainerAppsService>(); | ||
|
|
||
| services.AddSingleton<ContainerAppListCommand>(); | ||
| } | ||
|
|
||
| public CommandGroup RegisterCommands(IServiceProvider serviceProvider) | ||
| { | ||
| var containerapps = new CommandGroup(Name, "Azure Container Apps operations - Commands for managing Azure Container Apps resources. Includes operations for listing container apps and managing container app configurations.", Title); | ||
|
|
||
| var containerAppList = serviceProvider.GetRequiredService<ContainerAppListCommand>(); | ||
| containerapps.AddCommand(containerAppList.Name, containerAppList); | ||
|
|
||
| return containerapps; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| global using System.CommandLine; | ||
| global using Azure.Mcp.Core.Commands; | ||
| global using Azure.Mcp.Core.Options; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Models; | ||
|
|
||
| public sealed record ContainerAppInfo( | ||
| string Name, | ||
| string? Location, | ||
| string? ResourceGroup, | ||
| string? ManagedEnvironmentId, | ||
| string? ProvisioningState); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Options.ContainerApp; | ||
|
|
||
| public class ContainerAppListOptions : SubscriptionOptions | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Text.Json; | ||
| using Azure.Mcp.Core.Services.Azure; | ||
| using Azure.Mcp.Core.Services.Azure.Subscription; | ||
| using Azure.Mcp.Core.Services.Azure.Tenant; | ||
| using Azure.Mcp.Tools.ContainerApps.Models; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Services; | ||
|
|
||
| public sealed class ContainerAppsService(ISubscriptionService subscriptionService, ITenantService tenantService) | ||
| : BaseAzureResourceService(subscriptionService, tenantService), IContainerAppsService | ||
| { | ||
| public async Task<ResourceQueryResults<ContainerAppInfo>> ListContainerApps( | ||
| string subscription, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The |
||
| string? resourceGroup = null, | ||
| RetryPolicyOptions? retryPolicy = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| ValidateRequiredParameters((nameof(subscription), subscription)); | ||
|
|
||
| var containerApps = await ExecuteResourceQueryAsync( | ||
| "Microsoft.App/containerApps", | ||
| resourceGroup, | ||
| subscription, | ||
| retryPolicy, | ||
| ConvertToContainerAppInfoModel, | ||
| cancellationToken: cancellationToken); | ||
|
|
||
| return containerApps; | ||
| } | ||
|
|
||
| private static ContainerAppInfo ConvertToContainerAppInfoModel(JsonElement item) | ||
| { | ||
| var name = item.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? string.Empty : string.Empty; | ||
| var location = item.TryGetProperty("location", out var locationElement) ? locationElement.GetString() : null; | ||
| var resourceGroup = item.TryGetProperty("resourceGroup", out var rgElement) ? rgElement.GetString() : null; | ||
|
|
||
| string? managedEnvironmentId = null; | ||
| string? provisioningState = null; | ||
|
|
||
| if (item.TryGetProperty("properties", out var properties)) | ||
| { | ||
| managedEnvironmentId = properties.TryGetProperty("managedEnvironmentId", out var envElement) ? envElement.GetString() : null; | ||
| provisioningState = properties.TryGetProperty("provisioningState", out var stateElement) ? stateElement.GetString() : null; | ||
| } | ||
|
|
||
| return new ContainerAppInfo(name, location, resourceGroup, managedEnvironmentId, provisioningState); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Azure.Mcp.Core.Services.Azure; | ||
| using Azure.Mcp.Tools.ContainerApps.Models; | ||
|
|
||
| namespace Azure.Mcp.Tools.ContainerApps.Services; | ||
|
|
||
| public interface IContainerAppsService | ||
| { | ||
| Task<ResourceQueryResults<ContainerAppInfo>> ListContainerApps( | ||
| string subscription, | ||
| string? resourceGroup = null, | ||
| RetryPolicyOptions? retryPolicy = null, | ||
| CancellationToken cancellationToken = default); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| [assembly: Microsoft.Mcp.Tests.Helpers.ClearEnvironmentVariablesBeforeTest] | ||
| [assembly: Xunit.CollectionBehavior(Xunit.CollectionBehavior.CollectionPerAssembly)] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <IsPackable>false</IsPackable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.NET.Test.Sdk" /> | ||
| <PackageReference Include="NSubstitute" /> | ||
| <PackageReference Include="NSubstitute.Analyzers.CSharp" /> | ||
| <PackageReference Include="xunit.v3" /> | ||
| <PackageReference Include="xunit.v3.assert" /> | ||
| <PackageReference Include="xunit.runner.visualstudio" /> | ||
| <PackageReference Include="coverlet.collector" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Azure.Mcp.Tools.ContainerApps.csproj" /> | ||
| <ProjectReference Include="$(RepoRoot)core\Azure.Mcp.Core\tests\Azure.Mcp.Tests\Azure.Mcp.Tests.csproj" /> | ||
| </ItemGroup> | ||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vcolin7 if the PR number is not added does it automagically get resolved later?