Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: "Breaking Changes"
description: "Removed unused parameters from App Service tools."
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@
<PackageReference Include="Azure.ResourceManager.AppService" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Commands.Subscription;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.AppService.Models;
using Azure.Mcp.Tools.AppService.Options;
using Azure.Mcp.Tools.AppService.Options.Database;
using Azure.Mcp.Tools.AppService.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Command;

namespace Azure.Mcp.Tools.AppService.Commands.Database;
Expand All @@ -27,51 +27,24 @@ namespace Azure.Mcp.Tools.AppService.Commands.Database;
ReadOnly = false,
Secret = false,
LocalRequired = false)]
public sealed class DatabaseAddCommand(ILogger<DatabaseAddCommand> logger, IAppServiceService appServiceService)
: BaseAppServiceCommand<DatabaseAddOptions>(resourceGroupRequired: true, appRequired: true)
public sealed class DatabaseAddCommand(ILogger<DatabaseAddCommand> logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver)
: SubscriptionCommand<DatabaseAddOptions, DatabaseAddCommand.DatabaseAddResult>(subscriptionResolver)
{
private readonly ILogger<DatabaseAddCommand> _logger = logger;
private readonly IAppServiceService _appServiceService = appServiceService;

protected override void RegisterOptions(Command command)
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, DatabaseAddOptions options, CancellationToken cancellationToken)
{
base.RegisterOptions(command);
command.Options.Add(AppServiceOptionDefinitions.DatabaseTypeOption);
command.Options.Add(AppServiceOptionDefinitions.DatabaseServerOption);
command.Options.Add(AppServiceOptionDefinitions.DatabaseNameOption);
command.Options.Add(AppServiceOptionDefinitions.ConnectionStringOption);
}

protected override DatabaseAddOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.DatabaseType = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseTypeOption);
options.DatabaseServer = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseServerOption);
options.DatabaseName = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseNameOption);
options.ConnectionString = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.ConnectionStringOption);
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
{
// Validate first, then bind
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
context.Activity?.AddTag("subscription", options.Subscription);

var connectionInfo = await _appServiceService.AddDatabaseAsync(
options.AppName!,
options.ResourceGroup!,
options.DatabaseType!,
options.DatabaseServer!,
options.DatabaseName!,
options.App,
options.ResourceGroup,
options.DatabaseType,
options.DatabaseServer,
options.Database,
Comment thread
vcolin7 marked this conversation as resolved.
options.ConnectionString ?? string.Empty, // connectionString - will be generated if not provided
options.Subscription!,
options.Tenant,
Expand All @@ -82,12 +55,12 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to add database connection to App Service '{AppName}'", options.AppName);
_logger.LogError(ex, "Failed to add database connection to App Service '{App}'", options.App);
HandleException(context, ex);
}

return context.Response;
}

public record DatabaseAddResult(DatabaseConnectionInfo ConnectionInfo);
public sealed record DatabaseAddResult(DatabaseConnectionInfo ConnectionInfo);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Commands.Subscription;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.AppService.Models;
using Azure.Mcp.Tools.AppService.Options;
using Azure.Mcp.Tools.AppService.Options.Webapp.Deployment;
using Azure.Mcp.Tools.AppService.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Command;

namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Deployment;
Expand All @@ -29,43 +29,22 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Deployment;
ReadOnly = true,
Secret = false,
LocalRequired = false)]
public sealed class DeploymentGetCommand(ILogger<DeploymentGetCommand> logger, IAppServiceService appServiceService)
: BaseAppServiceCommand<DeploymentGetOptions>(resourceGroupRequired: true, appRequired: true)
public sealed class DeploymentGetCommand(ILogger<DeploymentGetCommand> logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver)
: SubscriptionCommand<DeploymentGetOptions, DeploymentGetCommand.DeploymentGetResult>(subscriptionResolver)
{
private readonly ILogger<DeploymentGetCommand> _logger = logger;
private readonly IAppServiceService _appServiceService = appServiceService;

protected override void RegisterOptions(Command command)
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, DeploymentGetOptions options, CancellationToken cancellationToken)
{
base.RegisterOptions(command);
command.Options.Add(AppServiceOptionDefinitions.DeploymentIdOption);
}

protected override DeploymentGetOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.DeploymentId = parseResult.GetValueOrDefault<string>(AppServiceOptionDefinitions.DeploymentIdOption.Name);
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
{
// Validate first, then bind
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
context.Activity?.AddTag("subscription", options.Subscription);

var deployments = await _appServiceService.GetDeploymentsAsync(
options.Subscription!,
options.ResourceGroup!,
options.AppName!,
options.ResourceGroup,
options.App,
Comment thread
vcolin7 marked this conversation as resolved.
options.DeploymentId,
options.Tenant,
options.RetryPolicy,
Expand All @@ -77,19 +56,19 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
{
if (options.DeploymentId == null)
{
_logger.LogError(ex, "Failed to list deployments for Web App '{AppName}' in resource group {ResourceGroup} and subscription {Subscription}",
options.AppName, options.ResourceGroup, options.Subscription);
_logger.LogError(ex, "Failed to list deployments for Web App '{App}' in resource group {ResourceGroup} and subscription {Subscription}",
options.App, options.ResourceGroup, options.Subscription);
}
else
{
_logger.LogError(ex, "Failed to get deployment '{DeploymentId}' for Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}",
options.DeploymentId, options.AppName, options.Subscription, options.ResourceGroup);
_logger.LogError(ex, "Failed to get deployment '{DeploymentId}' for Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}",
options.DeploymentId, options.App, options.Subscription, options.ResourceGroup);
}
HandleException(context, ex);
}

return context.Response;
}

public record DeploymentGetResult(List<DeploymentDetails> Deployments);
public sealed record DeploymentGetResult(List<DeploymentDetails> Deployments);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Commands.Subscription;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.AppService.Models;
using Azure.Mcp.Tools.AppService.Options;
using Azure.Mcp.Tools.AppService.Options.Webapp.Diagnostic;
using Azure.Mcp.Tools.AppService.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Extensions;
using Microsoft.Mcp.Core.Models.Command;
using Microsoft.Mcp.Core.Models.Option;

namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic;

Expand All @@ -29,82 +28,36 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic;
ReadOnly = true,
Secret = false,
LocalRequired = false)]
public sealed class DetectorDiagnoseCommand(ILogger<DetectorDiagnoseCommand> logger, IAppServiceService appServiceService)
: BaseAppServiceCommand<DetectorDiagnoseOptions>(resourceGroupRequired: true, appRequired: true)
public sealed class DetectorDiagnoseCommand(ILogger<DetectorDiagnoseCommand> logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver)
: SubscriptionCommand<DetectorDiagnoseOptions, DetectorDiagnoseCommand.DetectorDiagnoseResult>(subscriptionResolver)
{
private readonly ILogger<DetectorDiagnoseCommand> _logger = logger;
private readonly IAppServiceService _appServiceService = appServiceService;

protected override void RegisterOptions(Command command)
public override void ValidateOptions(DetectorDiagnoseOptions options, ValidationResult validationResult)
{
base.RegisterOptions(command);
command.Options.Add(AppServiceOptionDefinitions.DetectorId.AsRequired());
command.Options.Add(AppServiceOptionDefinitions.StartTime);
command.Options.Add(AppServiceOptionDefinitions.EndTime);
command.Options.Add(AppServiceOptionDefinitions.Interval);
command.Validators.Add(result =>
{
var startTime = result.GetValueOrDefault<string?>(AppServiceOptionDefinitions.StartTime.Name);
var endTime = result.GetValueOrDefault<string?>(AppServiceOptionDefinitions.EndTime.Name);

bool hasStartTime = !string.IsNullOrEmpty(startTime);
bool hasEndTime = !string.IsNullOrEmpty(endTime);

if (hasStartTime && !DateTimeOffset.TryParse(startTime, out _))
{
result.AddError($"Invalid start time format: {startTime}. Please provide a valid ISO format date time string.");
}
base.ValidateOptions(options, validationResult);

if (hasEndTime && !DateTimeOffset.TryParse(endTime, out _))
{
result.AddError($"Invalid end time format: {endTime}. Please provide a valid ISO format date time string.");
}
options.StartTime = options.StartTime?.ToUniversalTime();
options.EndTime = options.EndTime?.ToUniversalTime();

if (hasStartTime && hasEndTime
&& DateTimeOffset.TryParse(startTime, out var start)
&& DateTimeOffset.TryParse(endTime, out var end)
&& start > end)
{
result.AddError($"Start time '{startTime}' must be earlier than end time '{endTime}'.");
}
});
}

protected override DetectorDiagnoseOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.DetectorId = parseResult.GetValueOrDefault<string>(AppServiceOptionDefinitions.DetectorId.Name);
if (DateTimeOffset.TryParse(parseResult.GetValueOrDefault<string?>(AppServiceOptionDefinitions.StartTime.Name), out var startTime))
if (options.StartTime != null && options.EndTime != null && options.StartTime > options.EndTime)
{
options.StartTime = startTime.ToUniversalTime();
validationResult.Errors.Add($"Start time '{options.StartTime}' must be earlier than end time '{options.EndTime}'.");
Comment thread
alzimmermsft marked this conversation as resolved.
}
Comment thread
alzimmermsft marked this conversation as resolved.
if (DateTimeOffset.TryParse(parseResult.GetValueOrDefault<string?>(AppServiceOptionDefinitions.EndTime.Name), out var endTime))
{
options.EndTime = endTime.ToUniversalTime();
}
options.Interval = parseResult.GetValueOrDefault<string?>(AppServiceOptionDefinitions.Interval.Name);
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, DetectorDiagnoseOptions options, CancellationToken cancellationToken)
{
// Validate first, then bind
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
context.Activity?.AddTag("subscription", options.Subscription);

var diagnoses = await _appServiceService.DiagnoseDetectorAsync(
options.Subscription!,
options.ResourceGroup!,
options.AppName!,
options.DetectorId!,
options.ResourceGroup,
options.App,
options.DetectorId,
Comment thread
vcolin7 marked this conversation as resolved.
options.StartTime,
options.EndTime,
options.Interval,
Expand All @@ -116,13 +69,13 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}",
options.AppName, options.Subscription, options.ResourceGroup);
_logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}",
options.App, options.Subscription, options.ResourceGroup);
HandleException(context, ex);
}

return context.Response;
}

public record DetectorDiagnoseResult(DiagnosisResults Diagnoses);
public sealed record DetectorDiagnoseResult(DiagnosisResults Diagnoses);
}
Loading