diff --git a/servers/Azure.Mcp.Server/changelog-entries/1781549268619.yaml b/servers/Azure.Mcp.Server/changelog-entries/1781549268619.yaml new file mode 100644 index 0000000000..594977e40d --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1781549268619.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Breaking Changes" + description: "Removed unused parameters from Azure Backup tools." diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Backup/BackupStatusCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Backup/BackupStatusCommand.cs index 914fceac2b..bf3769d9bf 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Backup/BackupStatusCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Backup/BackupStatusCommand.cs @@ -3,15 +3,13 @@ using System.Net; using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Backup; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Backup; @@ -31,44 +29,23 @@ and the Azure region (location) where the resource exists. ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class BackupStatusCommand(ILogger logger, IAzureBackupService azureBackupService) : SubscriptionCommand() +public sealed class BackupStatusCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, BackupStatusOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.DatasourceId.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Location.AsRequired()); - } - - protected override BackupStatusOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DatasourceId = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DatasourceId.Name); - options.Location = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Location.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, "status-check"); try { var result = await _azureBackupService.GetBackupStatusAsync( - options.DatasourceId!, + options.DatasourceId, options.Subscription!, - options.Location!, + options.Location, options.Tenant, options.RetryPolicy, cancellationToken); @@ -97,5 +74,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record BackupStatusCommandResult(BackupStatusResult Status); + public sealed record BackupStatusCommandResult(BackupStatusResult Status); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseAzureBackupCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseAzureBackupCommand.cs index 929d38884f..b14dc32715 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseAzureBackupCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseAzureBackupCommand.cs @@ -3,45 +3,24 @@ using System.Diagnostics.CodeAnalysis; using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Options; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.AzureBackup.Commands; -public abstract class BaseAzureBackupCommand< - [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] T> - : SubscriptionCommand - where T : BaseAzureBackupOptions, new() +public abstract class BaseAzureBackupCommand<[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions, TResult>(ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) where TOptions : BaseAzureBackupOptions { - protected override void RegisterOptions(Command command) + public override void ValidateOptions(TOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Vault.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.VaultType); - command.Validators.Add(commandResult => - { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.VaultType.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.VaultType.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("rsv", StringComparison.OrdinalIgnoreCase) && - !value.Equals("dpp", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); - } - } - }); - } + base.ValidateOptions(options, validationResult); - protected override T BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.Vault = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Vault.Name); - options.VaultType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.VaultType.Name); - return options; + if (!string.IsNullOrEmpty(options.VaultType) && + !options.VaultType.Equals("rsv", StringComparison.OrdinalIgnoreCase) && + !options.VaultType.Equals("dpp", StringComparison.OrdinalIgnoreCase)) + { + validationResult.Errors.Add("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); + } } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseProtectedItemCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseProtectedItemCommand.cs deleted file mode 100644 index 5d5f9bc444..0000000000 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/BaseProtectedItemCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics.CodeAnalysis; -using Azure.Mcp.Tools.AzureBackup.Options; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.AzureBackup.Commands; - -public abstract class BaseProtectedItemCommand< - [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] T> - : BaseAzureBackupCommand - where T : BaseProtectedItemOptions, new() -{ - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.ProtectedItem.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Container); - } - - protected override T BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ProtectedItem = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ProtectedItem.Name); - options.Container = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Container.Name); - return options; - } -} diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/DisasterRecovery/DisasterRecoveryEnableCrrCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/DisasterRecovery/DisasterRecoveryEnableCrrCommand.cs index adce3693e6..a1e96f95bb 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/DisasterRecovery/DisasterRecoveryEnableCrrCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/DisasterRecovery/DisasterRecoveryEnableCrrCommand.cs @@ -2,14 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.DisasterRecovery; @@ -24,28 +23,22 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.DisasterRecovery; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class DisasterRecoveryEnableCrrCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class DisasterRecoveryEnableCrrCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, BaseAzureBackupOptions options, CancellationToken cancellationToken) { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); try { var result = await _azureBackupService.ConfigureCrossRegionRestoreAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Tenant, @@ -81,5 +74,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record DisasterRecoveryEnableCrrCommandResult(OperationResult Result); + public sealed record DisasterRecoveryEnableCrrCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceFindUnprotectedCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceFindUnprotectedCommand.cs index e0531913b4..c4d0f07aa2 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceFindUnprotectedCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceFindUnprotectedCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Governance; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Governance; @@ -28,37 +26,14 @@ Scans the subscription to find Azure resources that are not currently protected ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class GovernanceFindUnprotectedCommand(ILogger logger, IAzureBackupService azureBackupService) : SubscriptionCommand() +public sealed class GovernanceFindUnprotectedCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, GovernanceFindUnprotectedOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.ResourceTypeFilter); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); - command.Options.Add(AzureBackupOptionDefinitions.TagFilter); - } - - protected override GovernanceFindUnprotectedOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceTypeFilter = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ResourceTypeFilter.Name); - options.ResourceGroup = parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.TagFilter = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.TagFilter.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, "scan"); @@ -86,5 +61,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - internal record GovernanceFindUnprotectedCommandResult(List Resources); + public sealed record GovernanceFindUnprotectedCommandResult(List Resources); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceImmutabilityCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceImmutabilityCommand.cs index 756f504c6c..aada2ea21d 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceImmutabilityCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceImmutabilityCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Governance; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Governance; @@ -28,47 +26,27 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Governance; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class GovernanceImmutabilityCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class GovernanceImmutabilityCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(GovernanceImmutabilityOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.ImmutabilityState.AsRequired()); - command.Validators.Add(commandResult => - { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.ImmutabilityState.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.ImmutabilityState.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("Disabled", StringComparison.OrdinalIgnoreCase) && - !value.Equals("Enabled", StringComparison.OrdinalIgnoreCase) && - !value.Equals("Locked", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--immutability-state must be 'Disabled', 'Enabled', or 'Locked'. Warning: 'Locked' is irreversible."); - } - } - }); - } - - protected override GovernanceImmutabilityOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ImmutabilityState = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ImmutabilityState.Name); - return options; - } + base.ValidateOptions(options, validationResult); - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.ImmutabilityState) && + !options.ImmutabilityState.Equals("Disabled", StringComparison.OrdinalIgnoreCase) && + !options.ImmutabilityState.Equals("Enabled", StringComparison.OrdinalIgnoreCase) && + !options.ImmutabilityState.Equals("Locked", StringComparison.OrdinalIgnoreCase)) { - return context.Response; + validationResult.Errors.Add("--immutability-state must be 'Disabled', 'Enabled', or 'Locked'. Warning: 'Locked' is irreversible."); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, GovernanceImmutabilityOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); @@ -109,5 +87,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record GovernanceImmutabilityCommandResult(OperationResult Result); + public sealed record GovernanceImmutabilityCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceSoftDeleteCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceSoftDeleteCommand.cs index 0bca4e2cc4..25e6ed795b 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceSoftDeleteCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Governance/GovernanceSoftDeleteCommand.cs @@ -1,15 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Governance; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Governance; @@ -27,71 +25,45 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Governance; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class GovernanceSoftDeleteCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class GovernanceSoftDeleteCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(GovernanceSoftDeleteOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.SoftDelete.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.SoftDeleteRetentionDays); - command.Validators.Add(commandResult => - { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.SoftDelete.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.SoftDelete.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("AlwaysOn", StringComparison.OrdinalIgnoreCase) && - !value.Equals("On", StringComparison.OrdinalIgnoreCase) && - !value.Equals("Off", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--soft-delete must be 'AlwaysOn', 'On', or 'Off'."); - } - } - - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name)) - { - var retentionValue = commandResult.GetValue(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name); - if (!string.IsNullOrEmpty(retentionValue)) - { - if (!int.TryParse(retentionValue, out var retentionDays) || retentionDays < 14 || retentionDays > 180) - { - commandResult.AddError("--soft-delete-retention-days must be an integer between 14 and 180."); - } - } - } - }); - } - - protected override GovernanceSoftDeleteOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.SoftDeleteState = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SoftDelete.Name); - options.SoftDeleteRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name); - return options; - } + base.ValidateOptions(options, validationResult); - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.SoftDelete) && + !options.SoftDelete.Equals("AlwaysOn", StringComparison.OrdinalIgnoreCase) && + !options.SoftDelete.Equals("On", StringComparison.OrdinalIgnoreCase) && + !options.SoftDelete.Equals("Off", StringComparison.OrdinalIgnoreCase)) { - return context.Response; + validationResult.Errors.Add("--soft-delete must be 'AlwaysOn', 'On', or 'Off'."); } - var options = BindOptions(parseResult); + if (!string.IsNullOrEmpty(options.SoftDeleteRetentionDays) && + (!int.TryParse(options.SoftDeleteRetentionDays, out var retentionDays) + || retentionDays < 14 + || retentionDays > 180)) + { + validationResult.Errors.Add("--soft-delete-retention-days must be an integer between 14 and 180."); + } + } + public override async Task ExecuteAsync(CommandContext context, GovernanceSoftDeleteOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); try { var result = await _azureBackupService.ConfigureSoftDeleteAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, - options.SoftDeleteState!, + options.SoftDelete, options.VaultType, options.SoftDeleteRetentionDays, options.Tenant, @@ -105,12 +77,12 @@ public override async Task ExecuteAsync(CommandContext context, catch (Exception ex) { _logger.LogError(ex, "Error configuring soft delete. Vault: {Vault}, State: {SoftDeleteState}", - options.Vault, options.SoftDeleteState); + options.Vault, options.SoftDelete); HandleException(context, ex); } return context.Response; } - internal record GovernanceSoftDeleteCommandResult(OperationResult Result); + public sealed record GovernanceSoftDeleteCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Job/JobGetCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Job/JobGetCommand.cs index 7adb384775..b38a5a0d63 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Job/JobGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Job/JobGetCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Job; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Job; @@ -33,33 +31,14 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Job; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class JobGetCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class JobGetCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, JobGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Job.AsOptional()); - } - - protected override JobGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Job = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Job.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, string.IsNullOrEmpty(options.Job) ? "list" : "single"); @@ -69,8 +48,8 @@ public override async Task ExecuteAsync(CommandContext context, if (!string.IsNullOrEmpty(options.Job)) { var job = await _azureBackupService.GetJobAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.Job, options.VaultType, @@ -85,8 +64,8 @@ public override async Task ExecuteAsync(CommandContext context, else { var jobs = await _azureBackupService.ListJobsAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Tenant, @@ -115,5 +94,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record JobGetCommandResult(List Jobs); + public sealed record JobGetCommandResult(List Jobs); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyCreateCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyCreateCommand.cs index 28c30d2599..ca6651f8a6 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyCreateCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyCreateCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Policy; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Policy; @@ -25,128 +23,14 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Policy; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class PolicyCreateCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class PolicyCreateCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, PolicyCreateOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Policy.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.WorkloadType.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.DailyRetentionDays); - // Common schedule flags (new in policy create overhaul; bound here, consumed by builders in a later commit). - command.Options.Add(AzureBackupOptionDefinitions.TimeZone); - command.Options.Add(AzureBackupOptionDefinitions.ScheduleFrequency); - command.Options.Add(AzureBackupOptionDefinitions.ScheduleTimes); - command.Options.Add(AzureBackupOptionDefinitions.ScheduleDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.HourlyIntervalHours); - command.Options.Add(AzureBackupOptionDefinitions.HourlyWindowStartTime); - command.Options.Add(AzureBackupOptionDefinitions.HourlyWindowDurationHours); - // Retention flags (new in policy create overhaul; bound here, consumed by builders in a later commit). - command.Options.Add(AzureBackupOptionDefinitions.WeeklyRetentionWeeks); - command.Options.Add(AzureBackupOptionDefinitions.WeeklyRetentionDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.MonthlyRetentionMonths); - command.Options.Add(AzureBackupOptionDefinitions.MonthlyRetentionWeekOfMonth); - command.Options.Add(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfMonth); - command.Options.Add(AzureBackupOptionDefinitions.YearlyRetentionYears); - command.Options.Add(AzureBackupOptionDefinitions.YearlyRetentionMonths); - command.Options.Add(AzureBackupOptionDefinitions.YearlyRetentionWeekOfMonth); - command.Options.Add(AzureBackupOptionDefinitions.YearlyRetentionDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.YearlyRetentionDaysOfMonth); - command.Options.Add(AzureBackupOptionDefinitions.ArchiveTierAfterDays); - command.Options.Add(AzureBackupOptionDefinitions.ArchiveTierMode); - // RSV-VM only. - command.Options.Add(AzureBackupOptionDefinitions.PolicySubType); - command.Options.Add(AzureBackupOptionDefinitions.InstantRpRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.InstantRpResourceGroup); - command.Options.Add(AzureBackupOptionDefinitions.SnapshotConsistency); - // RSV-VmWorkload (SQL / SAPHANA / SAPASE). - command.Options.Add(AzureBackupOptionDefinitions.FullScheduleFrequency); - command.Options.Add(AzureBackupOptionDefinitions.FullScheduleDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.DifferentialScheduleDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.DifferentialRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.IncrementalScheduleDaysOfWeek); - command.Options.Add(AzureBackupOptionDefinitions.IncrementalRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.LogFrequencyMinutes); - command.Options.Add(AzureBackupOptionDefinitions.LogRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.IsCompression); - command.Options.Add(AzureBackupOptionDefinitions.IsSqlCompression); - // Stage 2 expansion. - command.Options.Add(AzureBackupOptionDefinitions.SmartTier); - command.Options.Add(AzureBackupOptionDefinitions.EnableSnapshotBackup); - command.Options.Add(AzureBackupOptionDefinitions.SnapshotInstantRpRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.SnapshotInstantRpResourceGroup); - command.Options.Add(AzureBackupOptionDefinitions.EnableVaultTierCopy); - command.Options.Add(AzureBackupOptionDefinitions.VaultTierCopyAfterDays); - command.Options.Add(AzureBackupOptionDefinitions.BackupMode); - command.Options.Add(AzureBackupOptionDefinitions.PitrRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.PolicyTags); - } - - protected override PolicyCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Policy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Policy.Name); - options.WorkloadType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.WorkloadType.Name); - options.DailyRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DailyRetentionDays.Name); - options.TimeZone = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.TimeZone.Name); - options.ScheduleFrequency = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ScheduleFrequency.Name); - options.ScheduleTimes = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ScheduleTimes.Name); - options.ScheduleDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ScheduleDaysOfWeek.Name); - options.HourlyIntervalHours = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.HourlyIntervalHours.Name); - options.HourlyWindowStartTime = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.HourlyWindowStartTime.Name); - options.HourlyWindowDurationHours = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.HourlyWindowDurationHours.Name); - options.WeeklyRetentionWeeks = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.WeeklyRetentionWeeks.Name); - options.WeeklyRetentionDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.WeeklyRetentionDaysOfWeek.Name); - options.MonthlyRetentionMonths = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.MonthlyRetentionMonths.Name); - options.MonthlyRetentionWeekOfMonth = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.MonthlyRetentionWeekOfMonth.Name); - options.MonthlyRetentionDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfWeek.Name); - options.MonthlyRetentionDaysOfMonth = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfMonth.Name); - options.YearlyRetentionYears = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.YearlyRetentionYears.Name); - options.YearlyRetentionMonths = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.YearlyRetentionMonths.Name); - options.YearlyRetentionWeekOfMonth = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.YearlyRetentionWeekOfMonth.Name); - options.YearlyRetentionDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.YearlyRetentionDaysOfWeek.Name); - options.YearlyRetentionDaysOfMonth = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.YearlyRetentionDaysOfMonth.Name); - options.ArchiveTierAfterDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ArchiveTierAfterDays.Name); - options.ArchiveTierMode = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ArchiveTierMode.Name); - options.PolicySubType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.PolicySubType.Name); - options.InstantRpRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.InstantRpRetentionDays.Name); - options.InstantRpResourceGroup = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.InstantRpResourceGroup.Name); - options.SnapshotConsistency = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SnapshotConsistency.Name); - options.FullScheduleFrequency = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.FullScheduleFrequency.Name); - options.FullScheduleDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.FullScheduleDaysOfWeek.Name); - options.DifferentialScheduleDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DifferentialScheduleDaysOfWeek.Name); - options.DifferentialRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DifferentialRetentionDays.Name); - options.IncrementalScheduleDaysOfWeek = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IncrementalScheduleDaysOfWeek.Name); - options.IncrementalRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IncrementalRetentionDays.Name); - options.LogFrequencyMinutes = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.LogFrequencyMinutes.Name); - options.LogRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.LogRetentionDays.Name); - options.IsCompression = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IsCompression.Name); - options.IsSqlCompression = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IsSqlCompression.Name); - options.SmartTier = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SmartTier.Name); - options.EnableSnapshotBackup = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.EnableSnapshotBackup.Name); - options.SnapshotInstantRpRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SnapshotInstantRpRetentionDays.Name); - options.SnapshotInstantRpResourceGroup = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SnapshotInstantRpResourceGroup.Name); - options.EnableVaultTierCopy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.EnableVaultTierCopy.Name); - options.VaultTierCopyAfterDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.VaultTierCopyAfterDays.Name); - options.BackupMode = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.BackupMode.Name); - options.PitrRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.PitrRetentionDays.Name); - options.PolicyTags = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.PolicyTags.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultAndWorkloadTags(context.Activity, options.VaultType, options.WorkloadType); @@ -162,8 +46,8 @@ public override async Task ExecuteAsync(CommandContext context, { var request = new Services.Policy.PolicyCreateRequest { - Policy = options.Policy!, - WorkloadType = options.WorkloadType!, + Policy = options.Policy, + WorkloadType = options.WorkloadType, DailyRetentionDays = options.DailyRetentionDays, TimeZone = options.TimeZone, ScheduleFrequency = options.ScheduleFrequency, @@ -212,8 +96,8 @@ public override async Task ExecuteAsync(CommandContext context, var result = await _azureBackupService.CreatePolicyAsync( request, - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Tenant, @@ -247,5 +131,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record PolicyCreateCommandResult(OperationResult Result); + public sealed record PolicyCreateCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyGetCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyGetCommand.cs index 61af9f190f..2d5e60a325 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyGetCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Policy; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Policy; @@ -33,33 +31,14 @@ information about a single policy including datasource types and protected items ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class PolicyGetCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class PolicyGetCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, PolicyGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Policy.AsOptional()); - } - - protected override PolicyGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Policy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Policy.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, string.IsNullOrEmpty(options.Policy) ? "list" : "single"); @@ -69,8 +48,8 @@ public override async Task ExecuteAsync(CommandContext context, if (!string.IsNullOrEmpty(options.Policy)) { var policy = await _azureBackupService.GetPolicyAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.Policy, options.VaultType, @@ -115,5 +94,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record PolicyGetCommandResult(List Policies); + public sealed record PolicyGetCommandResult(List Policies); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyUpdateCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyUpdateCommand.cs index ed7501734a..df5b5b1616 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyUpdateCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Policy/PolicyUpdateCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Policy; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Policy; @@ -25,47 +23,24 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Policy; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class PolicyUpdateCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class PolicyUpdateCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, PolicyUpdateOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Policy.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.ScheduleTime); - command.Options.Add(AzureBackupOptionDefinitions.DailyRetentionDays); - } - - protected override PolicyUpdateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Policy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Policy.Name); - options.ScheduleTime = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ScheduleTime.Name); - options.DailyRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DailyRetentionDays.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); try { var result = await _azureBackupService.UpdatePolicyAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, - options.Policy!, + options.Policy, options.VaultType, options.ScheduleTime, options.DailyRetentionDays, @@ -105,5 +80,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetStatusCode(ex) }; - internal record PolicyUpdateCommandResult(OperationResult Result); + public sealed record PolicyUpdateCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs index 033524a89d..7aeef9e61b 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.ProtectableItem; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.ProtectableItem; @@ -29,58 +28,39 @@ such as SQL databases and SAP HANA databases discovered on registered VMs. ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class ProtectableItemListCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class ProtectableItemListCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(ProtectableItemListOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.WorkloadType); - command.Options.Add(AzureBackupOptionDefinitions.Container); - command.Validators.Add(commandResult => - { - // NEW-4: reject unknown --workload-type at the command boundary so it surfaces - // as a 400 ValidationError instead of leaking the inner ArgumentException - // from the service layer as a 500. - // - // Read the value directly (no HasOptionResult gate) so whitespace-only inputs -- - // which System.CommandLine may report as "no result" -- still fail validation - // here instead of slipping past and being rejected by the service layer. - var value = commandResult.GetValueOrDefault(AzureBackupOptionDefinitions.WorkloadType.Name); - if (value is not null && !WorkloadTypeNormalizer.IsSupported(value)) - { - commandResult.AddError(WorkloadTypeNormalizer.FormatUnknownMessage(value)); - } - }); - } - - protected override ProtectableItemListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.WorkloadType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.WorkloadType.Name); - options.Container = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Container.Name); - return options; - } + base.ValidateOptions(options, validationResult); - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + // NEW-4: reject unknown --workload-type at the command boundary so it surfaces + // as a 400 ValidationError instead of leaking the inner ArgumentException + // from the service layer as a 500. + // + // Read the value directly (no HasOptionResult gate) so whitespace-only inputs -- + // which System.CommandLine may report as "no result" -- still fail validation + // here instead of slipping past and being rejected by the service layer. + if (options.WorkloadType != null && !WorkloadTypeNormalizer.IsSupported(options.WorkloadType)) { - return context.Response; + validationResult.Errors.Add(WorkloadTypeNormalizer.FormatUnknownMessage(options.WorkloadType)); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, ProtectableItemListOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultAndWorkloadTags(context.Activity, options.VaultType ?? "rsv", options.WorkloadType); try { var result = await _azureBackupService.ListProtectableItemsAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.WorkloadType, options.Container, @@ -109,5 +89,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record ProtectableItemListCommandResult(List Items); + public sealed record ProtectableItemListCommandResult(List Items); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemGetCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemGetCommand.cs index 5a8d46d0cd..b678a891cd 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemGetCommand.cs @@ -2,14 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.ProtectedItem; @@ -34,35 +33,14 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.ProtectedItem; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class ProtectedItemGetCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class ProtectedItemGetCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, BaseProtectedItemOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.ProtectedItem.AsOptional()); - command.Options.Add(AzureBackupOptionDefinitions.Container); - } - - protected override BaseProtectedItemOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ProtectedItem = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ProtectedItem.Name); - options.Container = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Container.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, string.IsNullOrEmpty(options.ProtectedItem) ? "list" : "single"); @@ -72,8 +50,8 @@ public override async Task ExecuteAsync(CommandContext context, if (!string.IsNullOrEmpty(options.ProtectedItem)) { var item = await _azureBackupService.GetProtectedItemAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.ProtectedItem, options.VaultType, @@ -89,8 +67,8 @@ public override async Task ExecuteAsync(CommandContext context, else { var items = await _azureBackupService.ListProtectedItemsAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Tenant, @@ -127,5 +105,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record ProtectedItemGetCommandResult(List ProtectedItems); + public sealed record ProtectedItemGetCommandResult(List ProtectedItems); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemProtectCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemProtectCommand.cs index b36d5a2ec9..b226399128 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemProtectCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemProtectCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.ProtectedItem; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.ProtectedItem; @@ -34,49 +32,14 @@ For workloads (SQL/HANA): pass the protectable item name from 'protectableitem l ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class ProtectedItemProtectCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class ProtectedItemProtectCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, ProtectedItemProtectOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.DatasourceId.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Policy.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Container); - command.Options.Add(AzureBackupOptionDefinitions.DatasourceType); - command.Options.Add(AzureBackupOptionDefinitions.AksSnapshotResourceGroup); - command.Options.Add(AzureBackupOptionDefinitions.AksIncludedNamespaces); - command.Options.Add(AzureBackupOptionDefinitions.AksExcludedNamespaces); - command.Options.Add(AzureBackupOptionDefinitions.AksLabelSelectors); - command.Options.Add(AzureBackupOptionDefinitions.AksIncludeClusterScopeResources); - } - - protected override ProtectedItemProtectOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DatasourceId = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DatasourceId.Name); - options.Policy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Policy.Name); - options.Container = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Container.Name); - options.DatasourceType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DatasourceType.Name); - options.AksSnapshotResourceGroup = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.AksSnapshotResourceGroup.Name); - options.AksIncludedNamespaces = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.AksIncludedNamespaces.Name); - options.AksExcludedNamespaces = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.AksExcludedNamespaces.Name); - options.AksLabelSelectors = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.AksLabelSelectors.Name); - options.AksIncludeClusterScopeResources = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.AksIncludeClusterScopeResources.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.DatasourceType, AzureBackupTelemetryTags.NormalizeWorkloadType(options.DatasourceType)); @@ -84,8 +47,8 @@ public override async Task ExecuteAsync(CommandContext context, try { var result = await _azureBackupService.ProtectItemAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.DatasourceId!, options.Policy!, @@ -126,5 +89,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record ProtectedItemProtectCommandResult(ProtectResult Result); + public sealed record ProtectedItemProtectCommandResult(ProtectResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemUndeleteCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemUndeleteCommand.cs index 9636368a3e..cd9c973fd0 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemUndeleteCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectedItem/ProtectedItemUndeleteCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.ProtectedItem; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.ProtectedItem; @@ -32,35 +30,14 @@ Use this when a backup or protected item was accidentally deleted and needs to b ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class ProtectedItemUndeleteCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class ProtectedItemUndeleteCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, ProtectedItemUndeleteOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.DatasourceId.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Container); - } - - protected override ProtectedItemUndeleteOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DatasourceId = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.DatasourceId.Name); - options.Container = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Container.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); @@ -105,5 +82,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record ProtectedItemUndeleteCommandResult(OperationResult Result); + public sealed record ProtectedItemUndeleteCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/RecoveryPoint/RecoveryPointGetCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/RecoveryPoint/RecoveryPointGetCommand.cs index eb3ba29441..bc52306bdb 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/RecoveryPoint/RecoveryPointGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/RecoveryPoint/RecoveryPointGetCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.RecoveryPoint; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.RecoveryPoint; @@ -33,33 +31,14 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.RecoveryPoint; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class RecoveryPointGetCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseProtectedItemCommand() +public sealed class RecoveryPointGetCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, RecoveryPointGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.RecoveryPoint.AsOptional()); - } - - protected override RecoveryPointGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.RecoveryPoint = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.RecoveryPoint.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, string.IsNullOrEmpty(options.RecoveryPoint) ? "list" : "single"); @@ -69,10 +48,10 @@ public override async Task ExecuteAsync(CommandContext context, if (!string.IsNullOrEmpty(options.RecoveryPoint)) { var rp = await _azureBackupService.GetRecoveryPointAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, - options.ProtectedItem!, + options.ProtectedItem, options.RecoveryPoint, options.VaultType, options.Container, @@ -87,10 +66,10 @@ public override async Task ExecuteAsync(CommandContext context, else { var points = await _azureBackupService.ListRecoveryPointsAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, - options.ProtectedItem!, + options.ProtectedItem, options.VaultType, options.Container, options.Tenant, @@ -124,5 +103,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record RecoveryPointGetCommandResult(List RecoveryPoints); + public sealed record RecoveryPointGetCommandResult(List RecoveryPoints); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureEncryptionCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureEncryptionCommand.cs index e62ec41046..03d1a53a21 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureEncryptionCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureEncryptionCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Security; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Security; @@ -31,87 +29,59 @@ identity must have the Key Vault Crypto Service Encryption User role on the Key ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class SecurityConfigureEncryptionCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class SecurityConfigureEncryptionCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; private string? _lastVaultType; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(SecurityConfigureEncryptionOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.KeyVaultUri); - command.Options.Add(AzureBackupOptionDefinitions.KeyNameOption); - command.Options.Add(AzureBackupOptionDefinitions.KeyVersion); - command.Options.Add(AzureBackupOptionDefinitions.IdentityType.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.UserAssignedIdentityId); + base.ValidateOptions(options, validationResult); - command.Validators.Add(commandResult => + if (!string.IsNullOrEmpty(options.IdentityType) && + !options.IdentityType.Equals("SystemAssigned", StringComparison.OrdinalIgnoreCase) && + !options.IdentityType.Equals("UserAssigned", StringComparison.OrdinalIgnoreCase)) { - var identityType = commandResult.GetValue(AzureBackupOptionDefinitions.IdentityType.Name); - if (!string.IsNullOrEmpty(identityType) && - !identityType.Equals("SystemAssigned", StringComparison.OrdinalIgnoreCase) && - !identityType.Equals("UserAssigned", StringComparison.OrdinalIgnoreCase)) + validationResult.Errors.Add("--identity-type must be 'SystemAssigned' or 'UserAssigned' for CMK encryption."); + } + + if (string.Equals(options.IdentityType, "UserAssigned", StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrEmpty(options.UserAssignedIdentityId)) { - commandResult.AddError("--identity-type must be 'SystemAssigned' or 'UserAssigned' for CMK encryption."); + validationResult.Errors.Add("--user-assigned-identity-id is required when --identity-type is 'UserAssigned'."); } - - if (string.Equals(identityType, "UserAssigned", StringComparison.OrdinalIgnoreCase)) + else if (!options.UserAssignedIdentityId.StartsWith("/subscriptions/", StringComparison.OrdinalIgnoreCase)) { - var uaId = commandResult.GetValue(AzureBackupOptionDefinitions.UserAssignedIdentityId.Name); - if (string.IsNullOrEmpty(uaId)) - { - commandResult.AddError("--user-assigned-identity-id is required when --identity-type is 'UserAssigned'."); - } - else if (!uaId.StartsWith("/subscriptions/", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--user-assigned-identity-id must be a valid ARM resource ID starting with '/subscriptions/'."); - } + validationResult.Errors.Add("--user-assigned-identity-id must be a valid ARM resource ID starting with '/subscriptions/'."); } + } - var keyVaultUri = commandResult.GetValue(AzureBackupOptionDefinitions.KeyVaultUri.Name); - if (!string.IsNullOrEmpty(keyVaultUri)) + if (!string.IsNullOrEmpty(options.KeyVaultUri)) + { + if (!Uri.TryCreate(options.KeyVaultUri, UriKind.Absolute, out var uri)) + { + validationResult.Errors.Add("--key-vault-uri must be a valid URI (e.g., 'https://kv-name.vault.azure.net/')."); + } + else { - if (!Uri.TryCreate(keyVaultUri, UriKind.Absolute, out var uri)) + if (!string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { - commandResult.AddError("--key-vault-uri must be a valid URI (e.g., 'https://kv-name.vault.azure.net/')."); + validationResult.Errors.Add("--key-vault-uri must use HTTPS (e.g., 'https://kv-name.vault.azure.net/')."); } - else - { - if (!string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--key-vault-uri must use HTTPS (e.g., 'https://kv-name.vault.azure.net/')."); - } - if (uri.AbsolutePath != "/" && !string.IsNullOrEmpty(uri.AbsolutePath.TrimEnd('/'))) - { - commandResult.AddError("--key-vault-uri must be the Key Vault base URI without path segments (e.g., 'https://kv-name.vault.azure.net/'). Do not include '/keys/...' in the URI."); - } + if (uri.AbsolutePath != "/" && !string.IsNullOrEmpty(uri.AbsolutePath.TrimEnd('/'))) + { + validationResult.Errors.Add("--key-vault-uri must be the Key Vault base URI without path segments (e.g., 'https://kv-name.vault.azure.net/'). Do not include '/keys/...' in the URI."); } } - }); - } - - protected override SecurityConfigureEncryptionOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.KeyVaultUri = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.KeyVaultUri.Name); - options.KeyName = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.KeyNameOption.Name); - options.KeyVersion = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.KeyVersion.Name); - options.IdentityType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IdentityType.Name); - options.UserAssignedIdentityId = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.UserAssignedIdentityId.Name); - return options; + } } - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, SecurityConfigureEncryptionOptions options, CancellationToken cancellationToken) { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); _lastVaultType = options.VaultType; @@ -119,12 +89,12 @@ public override async Task ExecuteAsync(CommandContext context, try { var result = await _azureBackupService.ConfigureEncryptionAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, - options.KeyVaultUri!, - options.KeyName!, - options.IdentityType!, + options.KeyVaultUri, + options.KeyName, + options.IdentityType, options.KeyVersion, options.UserAssignedIdentityId, options.VaultType, @@ -171,5 +141,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetStatusCode(ex) }; - internal record SecurityConfigureEncryptionCommandResult(OperationResult Result); + public sealed record SecurityConfigureEncryptionCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureMuaCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureMuaCommand.cs index db1c6d3fc9..1e3e32e97d 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureMuaCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Security/SecurityConfigureMuaCommand.cs @@ -2,13 +2,12 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Security; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Security; @@ -30,45 +29,25 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Security; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class SecurityConfigureMuaCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class SecurityConfigureMuaCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(SecurityConfigureMuaOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.ResourceGuardId); - command.Validators.Add(commandResult => - { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.ResourceGuardId.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.ResourceGuardId.Name); - if (!string.IsNullOrEmpty(value) && - !value.StartsWith("/subscriptions/", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--resource-guard-id must be a valid ARM resource ID starting with '/subscriptions/'."); - } - } - }); - } + base.ValidateOptions(options, validationResult); - protected override SecurityConfigureMuaOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGuardId = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ResourceGuardId.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.ResourceGuardId) && + !options.ResourceGuardId.StartsWith("/subscriptions/", StringComparison.OrdinalIgnoreCase)) { - return context.Response; + validationResult.Errors.Add("--resource-guard-id must be a valid ARM resource ID starting with '/subscriptions/'."); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, SecurityConfigureMuaOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); @@ -79,8 +58,8 @@ public override async Task ExecuteAsync(CommandContext context, if (!string.IsNullOrEmpty(options.ResourceGuardId)) { result = await _azureBackupService.ConfigureMultiUserAuthorizationAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.ResourceGuardId, options.VaultType, @@ -91,8 +70,8 @@ public override async Task ExecuteAsync(CommandContext context, else { result = await _azureBackupService.DisableMultiUserAuthorizationAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Tenant, @@ -138,5 +117,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetStatusCode(ex) }; - internal record SecurityConfigureMuaCommandResult(OperationResult Result); + public sealed record SecurityConfigureMuaCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultCreateCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultCreateCommand.cs index c9a3ed0155..26800d8742 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultCreateCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultCreateCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Vault; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Vault; @@ -32,69 +30,34 @@ vault details. ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class VaultCreateCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class VaultCreateCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(VaultCreateOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Location.AsRequired()); - command.Options.Add(AzureBackupOptionDefinitions.Sku); - command.Options.Add(AzureBackupOptionDefinitions.StorageType); - command.Validators.Add(commandResult => - { - if (!commandResult.HasOptionResult(AzureBackupOptionDefinitions.VaultType.Name)) - { - commandResult.AddError("--vault-type is required for vault creation. Specify 'rsv' or 'dpp'."); - } - else - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.VaultType.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("rsv", StringComparison.OrdinalIgnoreCase) && - !value.Equals("dpp", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); - } - } - }); + base.ValidateOptions(options, validationResult); - command.Validators.Add(commandResult => + if (string.IsNullOrEmpty(options.VaultType) || + (!options.VaultType.Equals("rsv", StringComparison.OrdinalIgnoreCase) && + !options.VaultType.Equals("dpp", StringComparison.OrdinalIgnoreCase))) { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.StorageType.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.StorageType.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("GeoRedundant", StringComparison.OrdinalIgnoreCase) && - !value.Equals("LocallyRedundant", StringComparison.OrdinalIgnoreCase) && - !value.Equals("ZoneRedundant", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--storage-type must be 'GeoRedundant', 'LocallyRedundant', or 'ZoneRedundant'."); - } - } - }); - } - - protected override VaultCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Location = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Location.Name); - options.Sku = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Sku.Name); - options.StorageType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.StorageType.Name); - return options; - } + validationResult.Errors.Add("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); + } - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.StorageType) && + !options.StorageType.Equals("GeoRedundant", StringComparison.OrdinalIgnoreCase) && + !options.StorageType.Equals("LocallyRedundant", StringComparison.OrdinalIgnoreCase) && + !options.StorageType.Equals("ZoneRedundant", StringComparison.OrdinalIgnoreCase)) { - return context.Response; + validationResult.Errors.Add("--storage-type must be 'GeoRedundant', 'LocallyRedundant', or 'ZoneRedundant'."); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, VaultCreateOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); @@ -137,5 +100,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record VaultCreateCommandResult(VaultCreateResult Vault); + public sealed record VaultCreateCommandResult(VaultCreateResult Vault); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultGetCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultGetCommand.cs index af504f99db..7993f2e79c 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultGetCommand.cs @@ -3,14 +3,13 @@ using System.Net; using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; +using Azure.Mcp.Tools.AzureBackup.Options.Vault; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Vault; @@ -35,56 +34,31 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Vault; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class VaultGetCommand(ILogger logger, IAzureBackupService azureBackupService) : SubscriptionCommand() +public sealed class VaultGetCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(VaultGetOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); - command.Options.Add(AzureBackupOptionDefinitions.Vault.AsOptional()); - command.Options.Add(AzureBackupOptionDefinitions.VaultType); - command.Validators.Add(commandResult => - { - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.Vault.Name) && - !commandResult.HasOptionResult(OptionDefinitions.Common.ResourceGroup.Name)) - { - commandResult.AddError("--resource-group is required when --vault is specified."); - } - - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.VaultType.Name)) - { - var value = commandResult.GetValue(AzureBackupOptionDefinitions.VaultType.Name); - if (!string.IsNullOrEmpty(value) && - !value.Equals("rsv", StringComparison.OrdinalIgnoreCase) && - !value.Equals("dpp", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); - } - } - }); - } + base.ValidateOptions(options, validationResult); - protected override BaseAzureBackupOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.Vault = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Vault.Name); - options.VaultType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.VaultType.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.Vault) && string.IsNullOrEmpty(options.ResourceGroup)) { - return context.Response; + validationResult.Errors.Add("--resource-group is required when --vault is specified."); } - var options = BindOptions(parseResult); + if (!string.IsNullOrEmpty(options.VaultType) && + !options.VaultType.Equals("rsv", StringComparison.OrdinalIgnoreCase) && + !options.VaultType.Equals("dpp", StringComparison.OrdinalIgnoreCase)) + { + validationResult.Errors.Add("--vault-type must be 'rsv' (Recovery Services vault) or 'dpp' (Backup vault)."); + } + } + public override async Task ExecuteAsync(CommandContext context, VaultGetOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); context.Activity?.AddTag(AzureBackupTelemetryTags.OperationScope, string.IsNullOrEmpty(options.Vault) ? "list" : "single"); @@ -140,5 +114,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record VaultGetCommandResult(List Vaults); + public sealed record VaultGetCommandResult(List Vaults); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultUpdateCommand.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultUpdateCommand.cs index 0bc85043d8..cd28049bce 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultUpdateCommand.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Commands/Vault/VaultUpdateCommand.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.Mcp.Tools.AzureBackup.Options; using Azure.Mcp.Tools.AzureBackup.Options.Vault; using Azure.Mcp.Tools.AzureBackup.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.AzureBackup.Commands.Vault; @@ -25,96 +23,62 @@ namespace Azure.Mcp.Tools.AzureBackup.Commands.Vault; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class VaultUpdateCommand(ILogger logger, IAzureBackupService azureBackupService) : BaseAzureBackupCommand() +public sealed class VaultUpdateCommand(ILogger logger, IAzureBackupService azureBackupService, ISubscriptionResolver subscriptionResolver) + : BaseAzureBackupCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAzureBackupService _azureBackupService = azureBackupService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(VaultUpdateOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AzureBackupOptionDefinitions.Redundancy); - command.Options.Add(AzureBackupOptionDefinitions.SoftDelete); - command.Options.Add(AzureBackupOptionDefinitions.SoftDeleteRetentionDays); - command.Options.Add(AzureBackupOptionDefinitions.ImmutabilityState); - command.Options.Add(AzureBackupOptionDefinitions.IdentityType); - command.Options.Add(AzureBackupOptionDefinitions.Tags); - command.Validators.Add(commandResult => - { - bool hasUpdate = - commandResult.HasOptionResult(AzureBackupOptionDefinitions.Redundancy.Name) || - commandResult.HasOptionResult(AzureBackupOptionDefinitions.SoftDelete.Name) || - commandResult.HasOptionResult(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name) || - commandResult.HasOptionResult(AzureBackupOptionDefinitions.ImmutabilityState.Name) || - commandResult.HasOptionResult(AzureBackupOptionDefinitions.IdentityType.Name) || - commandResult.HasOptionResult(AzureBackupOptionDefinitions.Tags.Name); + base.ValidateOptions(options, validationResult); - if (!hasUpdate) - { - commandResult.AddError( - "At least one update option must be provided: --redundancy, --soft-delete, --soft-delete-retention-days, --immutability-state, --identity-type, or --tags."); - } + bool hasUpdate = + !string.IsNullOrEmpty(options.Redundancy) || + !string.IsNullOrEmpty(options.SoftDelete) || + !string.IsNullOrEmpty(options.SoftDeleteRetentionDays) || + !string.IsNullOrEmpty(options.ImmutabilityState) || + !string.IsNullOrEmpty(options.IdentityType) || + !string.IsNullOrEmpty(options.Tags); - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name)) - { - var retentionValue = commandResult.GetValue(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name); - if (!string.IsNullOrEmpty(retentionValue)) - { - if (!int.TryParse(retentionValue, out var retentionDays) || retentionDays < 14 || retentionDays > 180) - { - commandResult.AddError("--soft-delete-retention-days must be an integer between 14 and 180."); - } - } - } + if (!hasUpdate) + { + validationResult.Errors.Add( + "At least one update option must be provided: --redundancy, --soft-delete, --soft-delete-retention-days, --immutability-state, --identity-type, or --tags."); + } - if (commandResult.HasOptionResult(AzureBackupOptionDefinitions.IdentityType.Name)) + if (!string.IsNullOrEmpty(options.SoftDeleteRetentionDays)) + { + if (!int.TryParse(options.SoftDeleteRetentionDays, out var retentionDays) || retentionDays < 14 || retentionDays > 180) { - var identityValue = commandResult.GetValue(AzureBackupOptionDefinitions.IdentityType.Name); - if (!string.IsNullOrEmpty(identityValue) && - !identityValue.Equals("SystemAssigned", StringComparison.OrdinalIgnoreCase) && - !identityValue.Equals("UserAssigned", StringComparison.OrdinalIgnoreCase) && - !identityValue.Equals("None", StringComparison.OrdinalIgnoreCase) && - !identityValue.Equals("SystemAssigned,UserAssigned", StringComparison.OrdinalIgnoreCase)) - { - commandResult.AddError("--identity-type must be 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned', or 'None'."); - } + validationResult.Errors.Add("--soft-delete-retention-days must be an integer between 14 and 180."); } - }); - } - - protected override VaultUpdateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Redundancy = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Redundancy.Name); - options.SoftDeleteState = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SoftDelete.Name); - options.SoftDeleteRetentionDays = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.SoftDeleteRetentionDays.Name); - options.ImmutabilityState = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.ImmutabilityState.Name); - options.IdentityType = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.IdentityType.Name); - options.Tags = parseResult.GetValueOrDefault(AzureBackupOptionDefinitions.Tags.Name); - return options; - } + } - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrEmpty(options.IdentityType) && + !options.IdentityType.Equals("SystemAssigned", StringComparison.OrdinalIgnoreCase) && + !options.IdentityType.Equals("UserAssigned", StringComparison.OrdinalIgnoreCase) && + !options.IdentityType.Equals("None", StringComparison.OrdinalIgnoreCase) && + !options.IdentityType.Equals("SystemAssigned,UserAssigned", StringComparison.OrdinalIgnoreCase)) { - return context.Response; + validationResult.Errors.Add("--identity-type must be 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned', or 'None'."); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, VaultUpdateOptions options, CancellationToken cancellationToken) + { AzureBackupTelemetryTags.AddSubscriptionTag(context.Activity, options.Subscription); AzureBackupTelemetryTags.AddVaultTags(context.Activity, options.VaultType); try { var result = await _azureBackupService.UpdateVaultAsync( - options.Vault!, - options.ResourceGroup!, + options.Vault, + options.ResourceGroup, options.Subscription!, options.VaultType, options.Redundancy, - options.SoftDeleteState, + options.SoftDelete, options.SoftDeleteRetentionDays, options.ImmutabilityState, options.IdentityType, @@ -150,5 +114,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record VaultUpdateCommandResult(OperationResult Result); + public sealed record VaultUpdateCommandResult(OperationResult Result); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/GlobalUsings.cs deleted file mode 100644 index b41cc886b4..0000000000 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/GlobalUsings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -global using System.CommandLine; diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/AzureBackupOptionDefinitions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/AzureBackupOptionDefinitions.cs index 754448b578..0d8955fa93 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/AzureBackupOptionDefinitions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/AzureBackupOptionDefinitions.cs @@ -5,50 +5,27 @@ namespace Azure.Mcp.Tools.AzureBackup.Options; public static class AzureBackupOptionDefinitions { - public const string VaultName = "vault"; - public const string VaultTypeName = "vault-type"; - public const string ProtectedItemName = "protected-item"; - public const string ContainerName = "container"; - public const string PolicyName = "policy"; - public const string JobName = "job"; - public const string RecoveryPointName = "recovery-point"; - public const string LocationName = "location"; - public const string DatasourceIdName = "datasource-id"; - public const string DatasourceTypeName = "datasource-type"; - public const string SkuName = "sku"; - public const string StorageTypeName = "storage-type"; - public const string RedundancyName = "redundancy"; - public const string IdentityTypeName = "identity-type"; - public const string ImmutabilityStateName = "immutability-state"; - public const string SoftDeleteName = "soft-delete"; - public const string SoftDeleteRetentionDaysName = "soft-delete-retention-days"; - public const string TagsName = "tags"; - public const string WorkloadTypeName = "workload-type"; - public const string DailyRetentionDaysName = "daily-retention-days"; - public const string VmResourceIdName = "vm-resource-id"; - public const string ResourceTypeFilterName = "resource-type-filter"; - public const string TagFilterName = "tag-filter"; - public const string ResourceGuardIdName = "resource-guard-id"; + internal const string Vault = "The name of the backup vault (Recovery Services vault or Backup vault)."; + internal const string VaultType = "The type of backup vault: 'rsv' (Recovery Services vault) or 'dpp' (Backup vault / Data Protection). Auto-detected if omitted for existing vaults."; + internal const string ProtectedItem = "The name of the protected item or backup instance."; + internal const string Container = "The RSV protection container name. Only applicable for Recovery Services vaults."; + internal const string Policy = "The name of the backup policy."; + internal const string Location = "The Azure region (e.g., 'eastus', 'westus2')."; + internal const string DatasourceId = "The datasource identifier. For VM/FileShare/DPP workloads, use the ARM resource ID (e.g., '/subscriptions/.../virtualMachines/myvm'). For RSV in-guest workloads (SQL/SAPHANA), use the protectable item name from 'protectableitem list' (e.g., 'SAPHanaDatabase;instance;dbname')."; + internal const string ImmutabilityState = "Immutability state: 'Disabled', 'Enabled', or 'Locked' (irreversible)."; + internal const string SoftDelete = "Soft delete state: 'AlwaysOn', 'On', or 'Off'."; + internal const string SoftDeleteRetentionDays = "Soft delete retention period (14-180 days)."; + internal const string WorkloadType = "Workload type: VM, SQL, SAPHANA, SAPASE, AzureFileShare (RSV types); AzureDisk, AzureBlob, AKS, ElasticSAN, PostgreSQLFlexible, ADLS, CosmosDB (DPP types). Also accepts aliases like AzureVM, SQLDatabase, etc."; + internal const string DailyRetentionDays = "Daily recovery point retention in days. Defaults to datasource-specific value if omitted."; - // Security - CMK encryption - public const string KeyVaultUriName = "key-vault-uri"; - public const string KeyNameOptionName = "key-name"; - public const string KeyVersionName = "key-version"; - public const string UserAssignedIdentityIdName = "user-assigned-identity-id"; + public const string LocationName = "location"; // Policy create - common schedule flags (new in policy create overhaul) - public const string ScheduleTimeName = "schedule-time"; - public const string TimeZoneName = "time-zone"; public const string ScheduleFrequencyName = "schedule-frequency"; - public const string ScheduleTimesName = "schedule-times"; public const string ScheduleDaysOfWeekName = "schedule-days-of-week"; - public const string HourlyIntervalHoursName = "hourly-interval-hours"; - public const string HourlyWindowStartTimeName = "hourly-window-start-time"; - public const string HourlyWindowDurationHoursName = "hourly-window-duration-hours"; // Policy create - retention flags (new in policy create overhaul) public const string WeeklyRetentionWeeksName = "weekly-retention-weeks"; - public const string WeeklyRetentionDaysOfWeekName = "weekly-retention-days-of-week"; public const string MonthlyRetentionMonthsName = "monthly-retention-months"; public const string MonthlyRetentionWeekOfMonthName = "monthly-retention-week-of-month"; public const string MonthlyRetentionDaysOfWeekName = "monthly-retention-days-of-week"; @@ -95,476 +72,4 @@ public static class AzureBackupOptionDefinitions public const string PitrRetentionDaysName = "pitr-retention-days"; // RSV policy-level tags public const string PolicyTagsName = "policy-tags"; - // DPP AKS-specific - public const string AksSnapshotResourceGroupName = "aks-snapshot-resource-group"; - public const string AksIncludedNamespacesName = "aks-included-namespaces"; - public const string AksExcludedNamespacesName = "aks-excluded-namespaces"; - public const string AksLabelSelectorsName = "aks-label-selectors"; - public const string AksIncludeClusterScopeResourcesName = "aks-include-cluster-scope-resources"; - - - public static readonly Option Vault = new($"--{VaultName}") - { - Description = "The name of the backup vault (Recovery Services vault or Backup vault).", - Required = true - }; - - public static readonly Option VaultType = new($"--{VaultTypeName}") - { - Description = "The type of backup vault: 'rsv' (Recovery Services vault) or 'dpp' (Backup vault / Data Protection). Auto-detected if omitted for existing vaults.", - Required = false - }; - - public static readonly Option ProtectedItem = new($"--{ProtectedItemName}") - { - Description = "The name of the protected item or backup instance.", - Required = true - }; - - public static readonly Option Container = new($"--{ContainerName}") - { - Description = "The RSV protection container name. Only applicable for Recovery Services vaults.", - Required = false - }; - - public static readonly Option Policy = new($"--{PolicyName}") - { - Description = "The name of the backup policy.", - Required = true - }; - - public static readonly Option Job = new($"--{JobName}") - { - Description = "The backup job ID.", - Required = true - }; - - public static readonly Option RecoveryPoint = new($"--{RecoveryPointName}") - { - Description = "The recovery point ID.", - Required = true - }; - - public static readonly Option Location = new($"--{LocationName}") - { - Description = "The Azure region (e.g., 'eastus', 'westus2').", - Required = true - }; - - public static readonly Option DatasourceId = new($"--{DatasourceIdName}") - { - Description = "The datasource identifier. For VM/FileShare/DPP workloads, use the ARM resource ID (e.g., '/subscriptions/.../virtualMachines/myvm'). For RSV in-guest workloads (SQL/SAPHANA), use the protectable item name from 'protectableitem list' (e.g., 'SAPHanaDatabase;instance;dbname').", - Required = true - }; - - public static readonly Option DatasourceType = new($"--{DatasourceTypeName}") - { - Description = "The workload type hint: VM, SQL, SAPHANA, SAPASE, AzureFileShare (RSV types); AzureDisk, AzureBlob, AKS, ElasticSAN, PostgreSQLFlexible, ADLS, CosmosDB (DPP types). Also accepts aliases like AzureVM, SQLDatabase, etc.", - Required = false - }; - - public static readonly Option Sku = new($"--{SkuName}") - { - Description = "The vault SKU.", - Required = false - }; - - public static readonly Option StorageType = new($"--{StorageTypeName}") - { - Description = "Storage redundancy: 'GeoRedundant', 'LocallyRedundant', or 'ZoneRedundant'.", - Required = false - }; - - public static readonly Option Redundancy = new($"--{RedundancyName}") - { - Description = "Storage redundancy: 'GeoRedundant', 'LocallyRedundant', 'ZoneRedundant', or 'ReadAccessGeoZoneRedundant'.", - Required = false - }; - - public static readonly Option IdentityType = new($"--{IdentityTypeName}") - { - Description = "Managed identity type: 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned', or 'None'.", - Required = false - }; - - public static readonly Option ImmutabilityState = new($"--{ImmutabilityStateName}") - { - Description = "Immutability state: 'Disabled', 'Enabled', or 'Locked' (irreversible).", - Required = false - }; - - public static readonly Option SoftDelete = new($"--{SoftDeleteName}") - { - Description = "Soft delete state: 'AlwaysOn', 'On', or 'Off'.", - Required = false - }; - - public static readonly Option SoftDeleteRetentionDays = new($"--{SoftDeleteRetentionDaysName}") - { - Description = "Soft delete retention period (14-180 days).", - Required = false - }; - - public static readonly Option Tags = new($"--{TagsName}") - { - Description = "Resource tags as JSON key-value object.", - Required = false - }; - - public static readonly Option WorkloadType = new($"--{WorkloadTypeName}") - { - Description = "Workload type: VM, SQL, SAPHANA, SAPASE, AzureFileShare (RSV types); AzureDisk, AzureBlob, AKS, ElasticSAN, PostgreSQLFlexible, ADLS, CosmosDB (DPP types). Also accepts aliases like AzureVM, SQLDatabase, etc.", - Required = false - }; - - public static readonly Option DailyRetentionDays = new($"--{DailyRetentionDaysName}") - { - Description = "Daily recovery point retention in days. Defaults to datasource-specific value if omitted.", - Required = false - }; - - public static readonly Option VmResourceId = new($"--{VmResourceIdName}") - { - Description = "ARM ID of the VM hosting SQL or SAP HANA.", - Required = false - }; - - public static readonly Option ResourceTypeFilter = new($"--{ResourceTypeFilterName}") - { - Description = "Resource types to filter (comma-separated).", - Required = false - }; - - public static readonly Option TagFilter = new($"--{TagFilterName}") - { - Description = "Tag-based filter in key=value format (e.g., 'environment=production').", - Required = false - }; - - public static readonly Option TimeZone = new($"--{TimeZoneName}") - { - Description = "Windows time-zone identifier for the backup schedule (e.g., 'UTC', 'Pacific Standard Time', 'India Standard Time'). If omitted, the schedule runs in UTC.", - Required = false - }; - - public static readonly Option ScheduleTime = new($"--{ScheduleTimeName}") - { - Description = "Backup schedule time in 24h HH:mm format (e.g., '02:00'). Used for policy update.", - Required = false - }; - - public static readonly Option ScheduleFrequency = new($"--{ScheduleFrequencyName}") - { - Description = "Backup schedule frequency. RSV vaults accept 'Daily', 'Weekly', or 'Hourly'. DPP (Backup) vaults accept ISO 8601 intervals: 'PT4H', 'PT6H', 'PT8H', 'PT12H', 'P1D', 'P1W', 'P2W', or 'P1M'.", - Required = false - }; - - public static readonly Option ScheduleTimes = new($"--{ScheduleTimesName}") - { - Description = "Comma-separated list of backup times in 24h HH:mm format (e.g., '02:00' or '02:00,14:00'). Interpreted in --time-zone. Defaults to 02:00 UTC if not specified. Only the first time is used as the schedule start time.", - Required = false - }; - - public static readonly Option ScheduleDaysOfWeek = new($"--{ScheduleDaysOfWeekName}") - { - Description = "Comma-separated days of the week the backup should run (e.g., 'Monday,Wednesday,Friday'). Required for Weekly schedules.", - Required = false - }; - - public static readonly Option HourlyIntervalHours = new($"--{HourlyIntervalHoursName}") - { - Description = "Interval in hours between hourly backups. Valid values: 4, 6, 8, 12. Used only when --schedule-frequency is 'Hourly' (RSV).", - Required = false - }; - - public static readonly Option HourlyWindowStartTime = new($"--{HourlyWindowStartTimeName}") - { - Description = "Start time of the hourly backup window in 24h HH:mm format (e.g., '08:00'). Used only when --schedule-frequency is 'Hourly' (RSV).", - Required = false - }; - - public static readonly Option HourlyWindowDurationHours = new($"--{HourlyWindowDurationHoursName}") - { - Description = "Duration of the hourly backup window in hours (e.g., 12). Used only when --schedule-frequency is 'Hourly' (RSV).", - Required = false - }; - - public static readonly Option WeeklyRetentionWeeks = new($"--{WeeklyRetentionWeeksName}") - { - Description = "Number of weeks to keep weekly recovery points. Required alongside --weekly-retention-days-of-week.", - Required = false - }; - - public static readonly Option WeeklyRetentionDaysOfWeek = new($"--{WeeklyRetentionDaysOfWeekName}") - { - Description = "Comma-separated days of the week tagged for weekly retention (e.g., 'Sunday' or 'Saturday,Sunday'). Required alongside --weekly-retention-weeks.", - Required = false - }; - - public static readonly Option MonthlyRetentionMonths = new($"--{MonthlyRetentionMonthsName}") - { - Description = "Number of months to keep monthly recovery points. Combine with either --monthly-retention-days-of-month (absolute) OR --monthly-retention-week-of-month + --monthly-retention-days-of-week (relative).", - Required = false - }; - - public static readonly Option MonthlyRetentionWeekOfMonth = new($"--{MonthlyRetentionWeekOfMonthName}") - { - Description = "Which week of the month to tag for monthly retention: 'First', 'Second', 'Third', 'Fourth', or 'Last'. Use with --monthly-retention-days-of-week (relative scheme).", - Required = false - }; - - public static readonly Option MonthlyRetentionDaysOfWeek = new($"--{MonthlyRetentionDaysOfWeekName}") - { - Description = "Comma-separated days of the week for the monthly retention tag (e.g., 'Sunday'). Use with --monthly-retention-week-of-month (relative scheme).", - Required = false - }; - - public static readonly Option MonthlyRetentionDaysOfMonth = new($"--{MonthlyRetentionDaysOfMonthName}") - { - Description = "Comma-separated days of the month for monthly retention (1-28 or 'Last'; e.g., '1,15,Last'). Absolute scheme; mutually exclusive with --monthly-retention-week-of-month.", - Required = false - }; - - public static readonly Option YearlyRetentionYears = new($"--{YearlyRetentionYearsName}") - { - Description = "Number of years to keep yearly recovery points. Combine with --yearly-retention-months and either --yearly-retention-days-of-month (absolute) OR --yearly-retention-week-of-month + --yearly-retention-days-of-week (relative).", - Required = false - }; - - public static readonly Option YearlyRetentionMonths = new($"--{YearlyRetentionMonthsName}") - { - Description = "Comma-separated months tagged for yearly retention (e.g., 'January' or 'January,July').", - Required = false - }; - - public static readonly Option YearlyRetentionWeekOfMonth = new($"--{YearlyRetentionWeekOfMonthName}") - { - Description = "Which week of the selected month(s) to tag for yearly retention: 'First', 'Second', 'Third', 'Fourth', or 'Last'. Use with --yearly-retention-days-of-week (relative scheme).", - Required = false - }; - - public static readonly Option YearlyRetentionDaysOfWeek = new($"--{YearlyRetentionDaysOfWeekName}") - { - Description = "Comma-separated days of the week for the yearly retention tag (e.g., 'Sunday'). Use with --yearly-retention-week-of-month (relative scheme).", - Required = false - }; - - public static readonly Option YearlyRetentionDaysOfMonth = new($"--{YearlyRetentionDaysOfMonthName}") - { - Description = "Comma-separated days of the selected month(s) for yearly retention (1-28 or 'Last'; e.g., '1,Last'). Absolute scheme; mutually exclusive with --yearly-retention-week-of-month.", - Required = false - }; - - public static readonly Option ArchiveTierAfterDays = new($"--{ArchiveTierAfterDaysName}") - { - Description = "Move recovery points to the archive tier after this many days. Pair with --archive-tier-mode.", - Required = false - }; - - public static readonly Option ArchiveTierMode = new($"--{ArchiveTierModeName}") - { - Description = "Archive tiering mode: 'TierAfter' (always tier after --archive-tier-after-days) or 'CopyOnExpiry' (copy to archive when the recovery point expires). Use --smart-tier for service-recommended tiering.", - Required = false - }; - - public static readonly Option PolicySubType = new($"--{PolicySubTypeName}") - { - Description = "RSV VM policy sub-type: 'Standard' or 'Enhanced'. Enhanced is required for hourly schedules and Trusted Launch VMs. RSV VM only.", - Required = false - }; - - public static readonly Option InstantRpRetentionDays = new($"--{InstantRpRetentionDaysName}") - { - Description = "Instant recovery point retention in days (1-30 for Standard, 1-7 for Enhanced). RSV VM only.", - Required = false - }; - - public static readonly Option InstantRpResourceGroup = new($"--{InstantRpResourceGroupName}") - { - Description = "Resource group that hosts the instant recovery point snapshots. RSV VM only.", - Required = false - }; - - public static readonly Option SnapshotConsistency = new($"--{SnapshotConsistencyName}") - { - Description = "Snapshot consistency mode for VM backups: 'ApplicationConsistent' or 'CrashConsistent'. RSV VM only.", - Required = false - }; - - public static readonly Option FullScheduleFrequency = new($"--{FullScheduleFrequencyName}") - { - Description = "Full backup schedule frequency for SQL/SAPHANA/SAPASE: 'Daily' or 'Weekly'. RSV VmWorkload only.", - Required = false - }; - - public static readonly Option FullScheduleDaysOfWeek = new($"--{FullScheduleDaysOfWeekName}") - { - Description = "Comma-separated days of the week for the Full backup (e.g., 'Sunday'). Required when --full-schedule-frequency is 'Weekly'. RSV VmWorkload only.", - Required = false - }; - - public static readonly Option DifferentialScheduleDaysOfWeek = new($"--{DifferentialScheduleDaysOfWeekName}") - { - Description = "Comma-separated days of the week for the Differential backup (e.g., 'Monday,Thursday'). RSV VmWorkload only.", - Required = false - }; - - public static readonly Option DifferentialRetentionDays = new($"--{DifferentialRetentionDaysName}") - { - Description = "Retention period in days for Differential backups. RSV VmWorkload only.", - Required = false - }; - - public static readonly Option IncrementalScheduleDaysOfWeek = new($"--{IncrementalScheduleDaysOfWeekName}") - { - Description = "Comma-separated days of the week for the Incremental backup. RSV SAPHANA / SAPASE only.", - Required = false - }; - - public static readonly Option IncrementalRetentionDays = new($"--{IncrementalRetentionDaysName}") - { - Description = "Retention period in days for Incremental backups. RSV SAPHANA / SAPASE only.", - Required = false - }; - - public static readonly Option LogFrequencyMinutes = new($"--{LogFrequencyMinutesName}") - { - Description = "Transaction log backup frequency in minutes (e.g., 15, 30, 60). RSV VmWorkload only.", - Required = false - }; - - public static readonly Option LogRetentionDays = new($"--{LogRetentionDaysName}") - { - Description = "Retention period in days for transaction log backups. RSV VmWorkload only.", - Required = false - }; - - public static readonly Option IsCompression = new($"--{IsCompressionName}") - { - Description = "Enable backup compression at the policy level. RSV VmWorkload only.", - Required = false - }; - - public static readonly Option IsSqlCompression = new($"--{IsSqlCompressionName}") - { - Description = "Enable SQL Server on VM native backup compression. RSV SQL only.", - Required = false - }; - - // ===== Stage 2 expansion ===== - - - public static readonly Option SmartTier = new($"--{SmartTierName}") - { - Description = "Enable smart-tiering (ML-based archive recommendation). RSV VM only - equivalent to TieringMode=TierRecommended. Kept separate from --archive-tier-mode because it emits a structurally different tiering shape (Duration=0, DurationType=Invalid).", - Required = false - }; - - public static readonly Option EnableSnapshotBackup = new($"--{EnableSnapshotBackupName}") - { - Description = "Enable snapshot/instance backups (HANA System Replication snapshot RPs). RSV SAPHANA only.", - Required = false - }; - - public static readonly Option SnapshotInstantRpRetentionDays = new($"--{SnapshotInstantRpRetentionDaysName}") - { - Description = "Snapshot instant RP retention range in days. RSV SAPHANA snapshot only.", - Required = false - }; - - public static readonly Option SnapshotInstantRpResourceGroup = new($"--{SnapshotInstantRpResourceGroupName}") - { - Description = "Resource group prefix for snapshot instant RPs. RSV SAPHANA snapshot only.", - Required = false - }; - - public static readonly Option EnableVaultTierCopy = new($"--{EnableVaultTierCopyName}") - { - Description = "Enable vault-tier copy of operational store backups. DPP AzureDisk only.", - Required = false - }; - - public static readonly Option VaultTierCopyAfterDays = new($"--{VaultTierCopyAfterDaysName}") - { - Description = "Days after which an operational backup is copied to the vault tier. DPP AzureDisk only.", - Required = false - }; - - public static readonly Option BackupMode = new($"--{BackupModeName}") - { - Description = "Backup mode for storage workloads: 'Continuous' (default for AzureBlob, ADLS) or 'Vaulted' (discrete recovery points). DPP AzureBlob, AzureDataLakeStorage.", - Required = false - }; - - public static readonly Option PitrRetentionDays = new($"--{PitrRetentionDaysName}") - { - Description = "Point-in-time restore retention in days for continuous backups. DPP AzureBlob, AzureDataLakeStorage.", - Required = false - }; - - public static readonly Option PolicyTags = new($"--{PolicyTagsName}") - { - Description = "Resource tags applied to the RSV backup policy as 'k1=v1,k2=v2'. RSV only.", - Required = false - }; - - public static readonly Option AksSnapshotResourceGroup = new($"--{AksSnapshotResourceGroupName}") - { - Description = "Resource group used to store AKS volume snapshots created by Backup. DPP AKS only.", - Required = false - }; - - public static readonly Option AksIncludedNamespaces = new($"--{AksIncludedNamespacesName}") - { - Description = "Comma-separated list of namespaces to include in the AKS backup policy default scope. DPP AKS only.", - Required = false - }; - - public static readonly Option AksExcludedNamespaces = new($"--{AksExcludedNamespacesName}") - { - Description = "Comma-separated list of namespaces to exclude from the AKS backup policy default scope. DPP AKS only.", - Required = false - }; - - public static readonly Option AksLabelSelectors = new($"--{AksLabelSelectorsName}") - { - Description = "Comma-separated label selectors (e.g. 'app=frontend,tier=web') applied to the AKS backup policy default scope. DPP AKS only.", - Required = false - }; - - public static readonly Option AksIncludeClusterScopeResources = new($"--{AksIncludeClusterScopeResourcesName}") - { - Description = "Include cluster-scoped resources in the AKS backup policy. DPP AKS only.", - Required = false - }; - - public static readonly Option ResourceGuardId = new($"--{ResourceGuardIdName}") - { - Description = "ARM resource ID of the Resource Guard to link for Multi-User Authorization (e.g., '/subscriptions/.../resourceGroups/.../providers/Microsoft.DataProtection/resourceGuards/myGuard').", - Required = false - }; - - public static readonly Option KeyVaultUri = new($"--{KeyVaultUriName}") - { - Description = "Key Vault URI (e.g., 'https://kv-security-prod.vault.azure.net/').", - Required = true - }; - - public static readonly Option KeyNameOption = new($"--{KeyNameOptionName}") - { - Description = "Name of the encryption key in the Key Vault.", - Required = true - }; - - public static readonly Option KeyVersion = new($"--{KeyVersionName}") - { - Description = "Specific key version. Omit to always use the latest version.", - Required = false - }; - - public static readonly Option UserAssignedIdentityId = new($"--{UserAssignedIdentityIdName}") - { - Description = "ARM resource ID of the user-assigned managed identity for Key Vault access. Required when --identity-type is 'UserAssigned'.", - Required = false - }; } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Backup/BackupStatusOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Backup/BackupStatusOptions.cs index 35655dc789..0c4a5bc9ac 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Backup/BackupStatusOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Backup/BackupStatusOptions.cs @@ -1,16 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Backup; -public class BackupStatusOptions : SubscriptionOptions +public sealed class BackupStatusOptions : ISubscriptionOption { - [JsonPropertyName(AzureBackupOptionDefinitions.DatasourceIdName)] - public string? DatasourceId { get; set; } + [Option(OptionDescriptions.Tenant)] + public string? Tenant { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.LocationName)] - public string? Location { get; set; } + [Option(OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Name = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } + + [Option(AzureBackupOptionDefinitions.DatasourceId)] + public required string DatasourceId { get; set; } + + [Option(AzureBackupOptionDefinitions.LocationName)] + public required string Location { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseAzureBackupOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseAzureBackupOptions.cs index ee9c29e404..02ab609a17 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseAzureBackupOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseAzureBackupOptions.cs @@ -1,16 +1,28 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options; -public class BaseAzureBackupOptions : SubscriptionOptions +public class BaseAzureBackupOptions : ISubscriptionOption { - [JsonPropertyName(AzureBackupOptionDefinitions.VaultName)] - public string? Vault { get; set; } + [Option(OptionDescriptions.Tenant)] + public string? Tenant { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.VaultTypeName)] + [Option(OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [Option(Name = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } + + [Option(AzureBackupOptionDefinitions.Vault)] + public required string Vault { get; set; } + + [Option(AzureBackupOptionDefinitions.VaultType)] public string? VaultType { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseProtectedItemOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseProtectedItemOptions.cs index c9277ce342..8bf423f6f8 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseProtectedItemOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/BaseProtectedItemOptions.cs @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options; public class BaseProtectedItemOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.ProtectedItemName)] + [Option(AzureBackupOptionDefinitions.ProtectedItem)] public string? ProtectedItem { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ContainerName)] + [Option(AzureBackupOptionDefinitions.Container)] public string? Container { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceFindUnprotectedOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceFindUnprotectedOptions.cs index d4371ffa68..2dc10d0c47 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceFindUnprotectedOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceFindUnprotectedOptions.cs @@ -1,16 +1,28 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Governance; -public class GovernanceFindUnprotectedOptions : SubscriptionOptions +public sealed class GovernanceFindUnprotectedOptions : ISubscriptionOption { - [JsonPropertyName(AzureBackupOptionDefinitions.ResourceTypeFilterName)] + [Option(OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(OptionDescriptions.ResourceGroup)] + public string? ResourceGroup { get; set; } + + [Option(Name = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } + + [Option("Resource types to filter (comma-separated).")] public string? ResourceTypeFilter { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.TagFilterName)] + [Option("Tag-based filter in key=value format (e.g., 'environment=production').")] public string? TagFilter { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceImmutabilityOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceImmutabilityOptions.cs index b975a54f6b..0bccda1c66 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceImmutabilityOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceImmutabilityOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Governance; public class GovernanceImmutabilityOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.ImmutabilityStateName)] - public string? ImmutabilityState { get; set; } + [Option(AzureBackupOptionDefinitions.ImmutabilityState)] + public required string ImmutabilityState { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceSoftDeleteOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceSoftDeleteOptions.cs index 0cc7adee94..079c88cf0e 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceSoftDeleteOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Governance/GovernanceSoftDeleteOptions.cs @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Governance; public class GovernanceSoftDeleteOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.SoftDeleteName)] - public string? SoftDeleteState { get; set; } + [Option(AzureBackupOptionDefinitions.SoftDelete)] + public required string SoftDelete { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SoftDeleteRetentionDaysName)] + [Option(AzureBackupOptionDefinitions.SoftDeleteRetentionDays)] public string? SoftDeleteRetentionDays { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Job/JobGetOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Job/JobGetOptions.cs index d0863f1014..a64f9018a2 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Job/JobGetOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Job/JobGetOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Job; public class JobGetOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.JobName)] + [Option("The backup job ID.")] public string? Job { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyCreateOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyCreateOptions.cs index 912cf3339a..9556bcef51 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyCreateOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyCreateOptions.cs @@ -1,154 +1,154 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Policy; public class PolicyCreateOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.PolicyName)] - public string? Policy { get; set; } + [Option(AzureBackupOptionDefinitions.Policy)] + public required string Policy { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.WorkloadTypeName)] - public string? WorkloadType { get; set; } + [Option(AzureBackupOptionDefinitions.WorkloadType)] + public required string WorkloadType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DailyRetentionDaysName)] + [Option(AzureBackupOptionDefinitions.DailyRetentionDays)] public string? DailyRetentionDays { get; set; } // Common schedule flags (new in policy create overhaul; not yet consumed by builders). - [JsonPropertyName(AzureBackupOptionDefinitions.TimeZoneName)] + [Option("Windows time-zone identifier for the backup schedule (e.g., 'UTC', 'Pacific Standard Time', 'India Standard Time'). If omitted, the schedule runs in UTC.")] public string? TimeZone { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ScheduleFrequencyName)] + [Option("Backup schedule frequency. RSV vaults accept 'Daily', 'Weekly', or 'Hourly'. DPP (Backup) vaults accept ISO 8601 intervals: 'PT4H', 'PT6H', 'PT8H', 'PT12H', 'P1D', 'P1W', 'P2W', or 'P1M'.")] public string? ScheduleFrequency { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ScheduleTimesName)] + [Option("Comma-separated list of backup times in 24h HH:mm format (e.g., '02:00' or '02:00,14:00'). Interpreted in --time-zone. Defaults to 02:00 UTC if not specified. Only the first time is used as the schedule start time.")] public string? ScheduleTimes { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ScheduleDaysOfWeekName)] + [Option("Comma-separated days of the week the backup should run (e.g., 'Monday,Wednesday,Friday'). Required for Weekly schedules.")] public string? ScheduleDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.HourlyIntervalHoursName)] + [Option("Interval in hours between hourly backups. Valid values: 4, 6, 8, 12. Used only when --schedule-frequency is 'Hourly' (RSV).")] public int HourlyIntervalHours { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.HourlyWindowStartTimeName)] + [Option("Start time of the hourly backup window in 24h HH:mm format (e.g., '08:00'). Used only when --schedule-frequency is 'Hourly' (RSV).")] public string? HourlyWindowStartTime { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.HourlyWindowDurationHoursName)] + [Option("Duration of the hourly backup window in hours (e.g., 12). Used only when --schedule-frequency is 'Hourly' (RSV).")] public int HourlyWindowDurationHours { get; set; } // Retention flags (new in policy create overhaul; not yet consumed by builders). - [JsonPropertyName(AzureBackupOptionDefinitions.WeeklyRetentionWeeksName)] + [Option("Number of weeks to keep weekly recovery points. Required alongside --weekly-retention-days-of-week.")] public int WeeklyRetentionWeeks { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.WeeklyRetentionDaysOfWeekName)] + [Option("Comma-separated days of the week tagged for weekly retention (e.g., 'Sunday' or 'Saturday,Sunday'). Required alongside --weekly-retention-weeks.")] public string? WeeklyRetentionDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.MonthlyRetentionMonthsName)] + [Option("Number of months to keep monthly recovery points. Combine with either --monthly-retention-days-of-month (absolute) OR --monthly-retention-week-of-month + --monthly-retention-days-of-week (relative).")] public int MonthlyRetentionMonths { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.MonthlyRetentionWeekOfMonthName)] + [Option("Which week of the month to tag for monthly retention: 'First', 'Second', 'Third', 'Fourth', or 'Last'. Use with --monthly-retention-days-of-week (relative scheme).")] public string? MonthlyRetentionWeekOfMonth { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfWeekName)] + [Option("Comma-separated days of the week for the monthly retention tag (e.g., 'Sunday'). Use with --monthly-retention-week-of-month (relative scheme).")] public string? MonthlyRetentionDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.MonthlyRetentionDaysOfMonthName)] + [Option("Comma-separated days of the month for monthly retention (1-28 or 'Last'; e.g., '1,15,Last'). Absolute scheme; mutually exclusive with --monthly-retention-week-of-month.")] public string? MonthlyRetentionDaysOfMonth { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.YearlyRetentionYearsName)] + [Option("Number of years to keep yearly recovery points. Combine with --yearly-retention-months and either --yearly-retention-days-of-month (absolute) OR --yearly-retention-week-of-month + --yearly-retention-days-of-week (relative).")] public int YearlyRetentionYears { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.YearlyRetentionMonthsName)] + [Option("Comma-separated months tagged for yearly retention (e.g., 'January' or 'January,July').")] public string? YearlyRetentionMonths { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.YearlyRetentionWeekOfMonthName)] + [Option("Which week of the selected month(s) to tag for yearly retention: 'First', 'Second', 'Third', 'Fourth', or 'Last'. Use with --yearly-retention-days-of-week (relative scheme).")] public string? YearlyRetentionWeekOfMonth { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.YearlyRetentionDaysOfWeekName)] + [Option("Comma-separated days of the week for the yearly retention tag (e.g., 'Sunday'). Use with --yearly-retention-week-of-month (relative scheme).")] public string? YearlyRetentionDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.YearlyRetentionDaysOfMonthName)] + [Option("Comma-separated days of the selected month(s) for yearly retention (1-28 or 'Last'; e.g., '1,Last'). Absolute scheme; mutually exclusive with --yearly-retention-week-of-month.")] public string? YearlyRetentionDaysOfMonth { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ArchiveTierAfterDaysName)] + [Option("Move recovery points to the archive tier after this many days. Pair with --archive-tier-mode.")] public string? ArchiveTierAfterDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ArchiveTierModeName)] + [Option("Archive tiering mode: 'TierAfter' (always tier after --archive-tier-after-days) or 'CopyOnExpiry' (copy to archive when the recovery point expires). Use --smart-tier for service-recommended tiering.")] public string? ArchiveTierMode { get; set; } // RSV-VM only flags (new in policy create overhaul; not yet consumed by builders). - [JsonPropertyName(AzureBackupOptionDefinitions.PolicySubTypeName)] + [Option("RSV VM policy sub-type: 'Standard' or 'Enhanced'. Enhanced is required for hourly schedules and Trusted Launch VMs. RSV VM only.")] public string? PolicySubType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.InstantRpRetentionDaysName)] + [Option("Instant recovery point retention in days (1-30 for Standard, 1-7 for Enhanced). RSV VM only.")] public string? InstantRpRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.InstantRpResourceGroupName)] + [Option("Resource group that hosts the instant recovery point snapshots. RSV VM only.")] public string? InstantRpResourceGroup { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SnapshotConsistencyName)] + [Option("Snapshot consistency mode for VM backups: 'ApplicationConsistent' or 'CrashConsistent'. RSV VM only.")] public string? SnapshotConsistency { get; set; } // RSV-VmWorkload (SQL / SAPHANA / SAPASE) flags. - [JsonPropertyName(AzureBackupOptionDefinitions.FullScheduleFrequencyName)] + [Option("Full backup schedule frequency for SQL/SAPHANA/SAPASE: 'Daily' or 'Weekly'. RSV VmWorkload only.")] public string? FullScheduleFrequency { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.FullScheduleDaysOfWeekName)] + [Option("Comma-separated days of the week for the Full backup (e.g., 'Sunday'). Required when --full-schedule-frequency is 'Weekly'. RSV VmWorkload only.")] public string? FullScheduleDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DifferentialScheduleDaysOfWeekName)] + [Option("Comma-separated days of the week for the Differential backup (e.g., 'Monday,Thursday'). RSV VmWorkload only.")] public string? DifferentialScheduleDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DifferentialRetentionDaysName)] + [Option("Retention period in days for Differential backups. RSV VmWorkload only.")] public int DifferentialRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IncrementalScheduleDaysOfWeekName)] + [Option("Comma-separated days of the week for the Incremental backup. RSV SAPHANA / SAPASE only.")] public string? IncrementalScheduleDaysOfWeek { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IncrementalRetentionDaysName)] + [Option("Retention period in days for Incremental backups. RSV SAPHANA / SAPASE only.")] public int IncrementalRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.LogFrequencyMinutesName)] + [Option("Transaction log backup frequency in minutes (e.g., 15, 30, 60). RSV VmWorkload only.")] public int LogFrequencyMinutes { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.LogRetentionDaysName)] + [Option("Retention period in days for transaction log backups. RSV VmWorkload only.")] public int LogRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IsCompressionName)] + [Option("Enable backup compression at the policy level. RSV VmWorkload only.")] public bool IsCompression { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IsSqlCompressionName)] + [Option("Enable SQL Server on VM native backup compression. RSV SQL only.")] public bool IsSqlCompression { get; set; } // ===== Stage 2 expansion ===== - [JsonPropertyName(AzureBackupOptionDefinitions.SmartTierName)] + [Option("Enable smart-tiering (ML-based archive recommendation). RSV VM only - equivalent to TieringMode=TierRecommended. Kept separate from --archive-tier-mode because it emits a structurally different tiering shape (Duration=0, DurationType=Invalid).")] public bool SmartTier { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.EnableSnapshotBackupName)] + [Option("Enable snapshot/instance backups (HANA System Replication snapshot RPs). RSV SAPHANA only.")] public bool EnableSnapshotBackup { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SnapshotInstantRpRetentionDaysName)] + [Option("Snapshot instant RP retention range in days. RSV SAPHANA snapshot only.")] public string? SnapshotInstantRpRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SnapshotInstantRpResourceGroupName)] + [Option("Resource group prefix for snapshot instant RPs. RSV SAPHANA snapshot only.")] public string? SnapshotInstantRpResourceGroup { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.EnableVaultTierCopyName)] + [Option("Enable vault-tier copy of operational store backups. DPP AzureDisk only.")] public bool EnableVaultTierCopy { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.VaultTierCopyAfterDaysName)] + [Option("Days after which an operational backup is copied to the vault tier. DPP AzureDisk only.")] public int VaultTierCopyAfterDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.BackupModeName)] + [Option("Backup mode for storage workloads: 'Continuous' (default for AzureBlob, ADLS) or 'Vaulted' (discrete recovery points). DPP AzureBlob, AzureDataLakeStorage.")] public string? BackupMode { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.PitrRetentionDaysName)] + [Option("Point-in-time restore retention in days for continuous backups. DPP AzureBlob, AzureDataLakeStorage.")] public int PitrRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.PolicyTagsName)] + [Option("Resource tags applied to the RSV backup policy as 'k1=v1,k2=v2'. RSV only.")] public string? PolicyTags { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyGetOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyGetOptions.cs index bb9ad96035..895de81966 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyGetOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyGetOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Policy; public class PolicyGetOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.PolicyName)] + [Option(AzureBackupOptionDefinitions.Policy)] public string? Policy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyUpdateOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyUpdateOptions.cs index eefddd353c..30c15c0819 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyUpdateOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Policy/PolicyUpdateOptions.cs @@ -1,18 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Policy; public class PolicyUpdateOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.PolicyName)] - public string? Policy { get; set; } + [Option(AzureBackupOptionDefinitions.Policy)] + public required string Policy { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ScheduleTimeName)] + [Option("Backup schedule time in 24h HH:mm format (e.g., '02:00'). Used for policy update.")] public string? ScheduleTime { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DailyRetentionDaysName)] + [Option(AzureBackupOptionDefinitions.DailyRetentionDays)] public string? DailyRetentionDays { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectableItem/ProtectableItemListOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectableItem/ProtectableItemListOptions.cs index 614e456394..90afc4d0b8 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectableItem/ProtectableItemListOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectableItem/ProtectableItemListOptions.cs @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.ProtectableItem; public class ProtectableItemListOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.WorkloadTypeName)] + [Option(AzureBackupOptionDefinitions.WorkloadType)] public string? WorkloadType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ContainerName)] + [Option(AzureBackupOptionDefinitions.Container)] public string? Container { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemProtectOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemProtectOptions.cs index 381ea7cbea..9da94b8a8a 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemProtectOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemProtectOptions.cs @@ -1,33 +1,33 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.ProtectedItem; public class ProtectedItemProtectOptions : BaseProtectedItemOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.PolicyName)] - public string? Policy { get; set; } + [Option(AzureBackupOptionDefinitions.Policy)] + public required string Policy { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DatasourceIdName)] - public string? DatasourceId { get; set; } + [Option(AzureBackupOptionDefinitions.DatasourceId)] + public required string DatasourceId { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.DatasourceTypeName)] + [Option("The workload type hint: VM, SQL, SAPHANA, SAPASE, AzureFileShare (RSV types); AzureDisk, AzureBlob, AKS, ElasticSAN, PostgreSQLFlexible, ADLS, CosmosDB (DPP types). Also accepts aliases like AzureVM, SQLDatabase, etc.")] public string? DatasourceType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.AksSnapshotResourceGroupName)] + [Option("Resource group used to store AKS volume snapshots created by Backup. DPP AKS only.")] public string? AksSnapshotResourceGroup { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.AksIncludedNamespacesName)] + [Option("Comma-separated list of namespaces to include in the AKS backup policy default scope. DPP AKS only.")] public string? AksIncludedNamespaces { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.AksExcludedNamespacesName)] + [Option("Resource group used to store AKS volume snapshots created by Backup. DPP AKS only.")] public string? AksExcludedNamespaces { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.AksLabelSelectorsName)] + [Option("Comma-separated label selectors (e.g. 'app=frontend,tier=web') applied to the AKS backup policy default scope. DPP AKS only.")] public string? AksLabelSelectors { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.AksIncludeClusterScopeResourcesName)] + [Option("Include cluster-scoped resources in the AKS backup policy. DPP AKS only.")] public bool AksIncludeClusterScopeResources { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemUndeleteOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemUndeleteOptions.cs index fb1d5ceeef..9f4bfe61da 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemUndeleteOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/ProtectedItem/ProtectedItemUndeleteOptions.cs @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.ProtectedItem; public class ProtectedItemUndeleteOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.DatasourceIdName)] - public string? DatasourceId { get; set; } + [Option(AzureBackupOptionDefinitions.DatasourceId)] + public required string DatasourceId { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ContainerName)] + [Option(AzureBackupOptionDefinitions.Container)] public string? Container { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/RecoveryPoint/RecoveryPointGetOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/RecoveryPoint/RecoveryPointGetOptions.cs index 449760ebf3..cf6f1d8b95 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/RecoveryPoint/RecoveryPointGetOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/RecoveryPoint/RecoveryPointGetOptions.cs @@ -1,12 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.RecoveryPoint; -public class RecoveryPointGetOptions : BaseProtectedItemOptions +public class RecoveryPointGetOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.RecoveryPointName)] + [Option(AzureBackupOptionDefinitions.ProtectedItem)] + public required string ProtectedItem { get; set; } + + [Option(AzureBackupOptionDefinitions.Container)] + public string? Container { get; set; } + + [Option("The recovery point ID.")] public string? RecoveryPoint { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureEncryptionOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureEncryptionOptions.cs index d704231405..4be26077d0 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureEncryptionOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureEncryptionOptions.cs @@ -1,24 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Security; public class SecurityConfigureEncryptionOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.KeyVaultUriName)] - public string? KeyVaultUri { get; set; } + [Option("Key Vault URI (e.g., 'https://kv-security-prod.vault.azure.net/').")] + public required string KeyVaultUri { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.KeyNameOptionName)] - public string? KeyName { get; set; } + [Option("Name of the encryption key in the Key Vault.")] + public required string KeyName { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.KeyVersionName)] + [Option("Specific key version. Omit to always use the latest version.")] public string? KeyVersion { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IdentityTypeName)] - public string? IdentityType { get; set; } + [Option("Managed identity type: 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned', or 'None'.")] + public required string IdentityType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.UserAssignedIdentityIdName)] + [Option("ARM resource ID of the user-assigned managed identity for Key Vault access. Required when --identity-type is 'UserAssigned'.")] public string? UserAssignedIdentityId { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureMuaOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureMuaOptions.cs index 457d241c2a..608f9fb1b3 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureMuaOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Security/SecurityConfigureMuaOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Security; public class SecurityConfigureMuaOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.ResourceGuardIdName)] + [Option("ARM resource ID of the Resource Guard to link for Multi-User Authorization (e.g., '/subscriptions/.../resourceGroups/.../providers/Microsoft.DataProtection/resourceGuards/myGuard').")] public string? ResourceGuardId { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultCreateOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultCreateOptions.cs index cb9db8b011..b30537b4c9 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultCreateOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultCreateOptions.cs @@ -1,18 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Vault; public class VaultCreateOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.LocationName)] - public string? Location { get; set; } + [Option(AzureBackupOptionDefinitions.Location)] + public required string Location { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SkuName)] + [Option("The vault SKU.")] public string? Sku { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.StorageTypeName)] + [Option("Storage redundancy: 'GeoRedundant', 'LocallyRedundant', or 'ZoneRedundant'.")] public string? StorageType { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultGetOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultGetOptions.cs new file mode 100644 index 0000000000..4460634340 --- /dev/null +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultGetOptions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; + +namespace Azure.Mcp.Tools.AzureBackup.Options.Vault; + +public class VaultGetOptions : ISubscriptionOption +{ + [Option(OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(OptionDescriptions.ResourceGroup)] + public string? ResourceGroup { get; set; } + + [Option(Name = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } + + [Option(AzureBackupOptionDefinitions.Vault)] + public string? Vault { get; set; } + + [Option(AzureBackupOptionDefinitions.VaultType)] + public string? VaultType { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultUpdateOptions.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultUpdateOptions.cs index 62a9f93d8b..4ef2f741b6 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultUpdateOptions.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Options/Vault/VaultUpdateOptions.cs @@ -1,27 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AzureBackup.Options.Vault; public class VaultUpdateOptions : BaseAzureBackupOptions { - [JsonPropertyName(AzureBackupOptionDefinitions.RedundancyName)] + [Option("Storage redundancy: 'GeoRedundant', 'LocallyRedundant', 'ZoneRedundant', or 'ReadAccessGeoZoneRedundant'.")] public string? Redundancy { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SoftDeleteName)] - public string? SoftDeleteState { get; set; } + [Option(AzureBackupOptionDefinitions.SoftDelete)] + public string? SoftDelete { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.SoftDeleteRetentionDaysName)] + [Option(AzureBackupOptionDefinitions.SoftDeleteRetentionDays)] public string? SoftDeleteRetentionDays { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.ImmutabilityStateName)] + [Option(AzureBackupOptionDefinitions.ImmutabilityState)] public string? ImmutabilityState { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.IdentityTypeName)] + [Option("Managed identity type: 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned', or 'None'.")] public string? IdentityType { get; set; } - [JsonPropertyName(AzureBackupOptionDefinitions.TagsName)] + [Option("Resource tags as JSON key-value object.")] public string? Tags { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs b/tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs index c9aba94420..14d1123004 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs @@ -7,7 +7,6 @@ using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Core.Services.Azure.Tenant; using Azure.Mcp.Tools.AzureBackup.Models; -using Azure.ResourceManager; using Azure.ResourceManager.RecoveryServicesBackup; using Azure.ResourceManager.RecoveryServicesBackup.Models; using Azure.ResourceManager.Resources; diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupSetupTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupSetupTests.cs index 4cef8a8553..f6678fd55d 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupSetupTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupSetupTests.cs @@ -139,6 +139,7 @@ private static IServiceProvider CreateServiceProvider(AzureBackupSetup setup) services.AddLogging(); services.AddSingleton(Substitute.For()); services.AddSingleton(Substitute.For()); + services.AddSingleton(Substitute.For()); setup.ConfigureServices(services); return services.BuildServiceProvider(); } diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Backup/BackupStatusCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Backup/BackupStatusCommandTests.cs index 462f2e23dd..d1b08dec96 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Backup/BackupStatusCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Backup/BackupStatusCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Backup; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Backup; -public class BackupStatusCommandTests : CommandUnitTestsBase +public class BackupStatusCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/DisasterRecovery/DisasterRecoveryEnableCrrCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/DisasterRecovery/DisasterRecoveryEnableCrrCommandTests.cs index 91da32520d..0d22fefa11 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/DisasterRecovery/DisasterRecoveryEnableCrrCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/DisasterRecovery/DisasterRecoveryEnableCrrCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.DisasterRecovery; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.DisasterRecovery; -public class DisasterRecoveryEnableCrrCommandTests : CommandUnitTestsBase +public class DisasterRecoveryEnableCrrCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceFindUnprotectedCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceFindUnprotectedCommandTests.cs index 8e4358e133..db1e74ef88 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceFindUnprotectedCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceFindUnprotectedCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Governance; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Governance; -public class GovernanceFindUnprotectedCommandTests : CommandUnitTestsBase +public class GovernanceFindUnprotectedCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceImmutabilityCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceImmutabilityCommandTests.cs index 8d01748c5c..aceadd3d77 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceImmutabilityCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceImmutabilityCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Governance; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Governance; -public class GovernanceImmutabilityCommandTests : CommandUnitTestsBase +public class GovernanceImmutabilityCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceSoftDeleteCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceSoftDeleteCommandTests.cs index 5212c87427..dfd067c3ef 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceSoftDeleteCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Governance/GovernanceSoftDeleteCommandTests.cs @@ -2,18 +2,18 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Governance; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Governance; -public class GovernanceSoftDeleteCommandTests : CommandUnitTestsBase +public class GovernanceSoftDeleteCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Job/JobGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Job/JobGetCommandTests.cs index b8f9d1a814..01d0a5f460 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Job/JobGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Job/JobGetCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Job; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Job; -public class JobGetCommandTests : CommandUnitTestsBase +public class JobGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.cs index 8c20e300ee..051107c071 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.cs @@ -2,20 +2,20 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Policy; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Azure.Mcp.Tools.AzureBackup.Services.Policy; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Policy; -public class PolicyCreateCommandTests : CommandUnitTestsBase +public class PolicyCreateCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyGetCommandTests.cs index 30e18be68f..632354b136 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyGetCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Policy; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Policy; -public class PolicyGetCommandTests : CommandUnitTestsBase +public class PolicyGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyUpdateCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyUpdateCommandTests.cs index 50c1e54b4a..b1b4db2721 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyUpdateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyUpdateCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Policy; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Policy; -public class PolicyUpdateCommandTests : CommandUnitTestsBase +public class PolicyUpdateCommandTests : SubscriptionCommandUnitTestsBase { [Fact] diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectableItem/ProtectableItemListCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectableItem/ProtectableItemListCommandTests.cs index 8111cc0b58..a02f34523d 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectableItem/ProtectableItemListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectableItem/ProtectableItemListCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.ProtectableItem; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.ProtectableItem; -public class ProtectableItemListCommandTests : CommandUnitTestsBase +public class ProtectableItemListCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemGetCommandTests.cs index d5b6884ff9..f1f27c8d80 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemGetCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.ProtectedItem; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.ProtectedItem; -public class ProtectedItemGetCommandTests : CommandUnitTestsBase +public class ProtectedItemGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemProtectCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemProtectCommandTests.cs index 50aeccff2b..67532bd266 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemProtectCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemProtectCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.ProtectedItem; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.ProtectedItem; -public class ProtectedItemProtectCommandTests : CommandUnitTestsBase +public class ProtectedItemProtectCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemUndeleteCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemUndeleteCommandTests.cs index 384669fb2e..fd0758eabb 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemUndeleteCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/ProtectedItem/ProtectedItemUndeleteCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.ProtectedItem; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.ProtectedItem; -public class ProtectedItemUndeleteCommandTests : CommandUnitTestsBase +public class ProtectedItemUndeleteCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/RecoveryPoint/RecoveryPointGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/RecoveryPoint/RecoveryPointGetCommandTests.cs index 23fb13af50..91bfd0afba 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/RecoveryPoint/RecoveryPointGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/RecoveryPoint/RecoveryPointGetCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.RecoveryPoint; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.RecoveryPoint; -public class RecoveryPointGetCommandTests : CommandUnitTestsBase +public class RecoveryPointGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureEncryptionCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureEncryptionCommandTests.cs index cd0e8e8038..5672b8fef5 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureEncryptionCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureEncryptionCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Security; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Security; -public class SecurityConfigureEncryptionCommandTests : CommandUnitTestsBase +public class SecurityConfigureEncryptionCommandTests : SubscriptionCommandUnitTestsBase { private const string TestKeyVaultUri = "https://kv-security-prod.vault.azure.net/"; private const string TestKeyName = "backup-cmk"; diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureMuaCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureMuaCommandTests.cs index 3eaae274f9..ca95e8779f 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureMuaCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Security/SecurityConfigureMuaCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Security; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Security; -public class SecurityConfigureMuaCommandTests : CommandUnitTestsBase +public class SecurityConfigureMuaCommandTests : SubscriptionCommandUnitTestsBase { private const string TestResourceGuardId = "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/rg-security/providers/Microsoft.DataProtection/resourceGuards/test-guard"; diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultCreateCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultCreateCommandTests.cs index 3f4cee5bb1..173df0df38 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultCreateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultCreateCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Vault; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Vault; -public class VaultCreateCommandTests : CommandUnitTestsBase +public class VaultCreateCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultGetCommandTests.cs index 01f1c9bc40..c62a2c35bf 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultGetCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Vault; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Vault; -public class VaultGetCommandTests : CommandUnitTestsBase +public class VaultGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly() diff --git a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultUpdateCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultUpdateCommandTests.cs index 45e00487e2..51dc23f454 100644 --- a/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultUpdateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.Tests/Vault/VaultUpdateCommandTests.cs @@ -2,19 +2,19 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AzureBackup.Commands; using Azure.Mcp.Tools.AzureBackup.Commands.Vault; using Azure.Mcp.Tools.AzureBackup.Models; using Azure.Mcp.Tools.AzureBackup.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.AzureBackup.Tests.Vault; -public class VaultUpdateCommandTests : CommandUnitTestsBase +public class VaultUpdateCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public void Constructor_InitializesCommandCorrectly()