|  | 
|  | 1 | +// Copyright (c) Microsoft Corporation. | 
|  | 2 | +// Licensed under the MIT License. | 
|  | 3 | + | 
|  | 4 | +using System.Net; | 
|  | 5 | +using Azure.Mcp.Core.Commands; | 
|  | 6 | +using Azure.Mcp.Core.Extensions; | 
|  | 7 | +using Azure.Mcp.Core.Models.Option; | 
|  | 8 | +using Azure.Mcp.Tools.ManagedLustre.Options; | 
|  | 9 | +using Azure.Mcp.Tools.ManagedLustre.Options.FileSystem; | 
|  | 10 | +using Azure.Mcp.Tools.ManagedLustre.Services; | 
|  | 11 | +using Microsoft.Extensions.Logging; | 
|  | 12 | + | 
|  | 13 | +namespace Azure.Mcp.Tools.ManagedLustre.Commands.FileSystem; | 
|  | 14 | + | 
|  | 15 | +public sealed class FileSystemImportJobCreateCommand(ILogger<FileSystemImportJobCreateCommand> logger) | 
|  | 16 | +    : BaseManagedLustreCommand<FileSystemImportJobCreateOptions>(logger) | 
|  | 17 | +{ | 
|  | 18 | +    private const string CommandTitle = "Create AMLFS Import Job"; | 
|  | 19 | + | 
|  | 20 | +    public override string Name => "create"; | 
|  | 21 | + | 
|  | 22 | +    public override string Description => | 
|  | 23 | +        """ | 
|  | 24 | +        Creates a manual import job for an Azure Managed Lustre (AMLFS) file system. The import job scans the linked HSM/Blob container and imports specified path prefixes (or all when omitted) honoring the chosen conflict resolution mode. Use to hydrate the AMLFS namespace or refresh content. | 
|  | 25 | +        """; | 
|  | 26 | + | 
|  | 27 | +    public override string Title => CommandTitle; | 
|  | 28 | + | 
|  | 29 | +    public override ToolMetadata Metadata => new() | 
|  | 30 | +    { | 
|  | 31 | +        Destructive = false, | 
|  | 32 | +        Idempotent = true, | 
|  | 33 | +        OpenWorld = true, | 
|  | 34 | +        ReadOnly = false, | 
|  | 35 | +        LocalRequired = false, | 
|  | 36 | +        Secret = false | 
|  | 37 | +    }; | 
|  | 38 | + | 
|  | 39 | +    protected override void RegisterOptions(Command command) | 
|  | 40 | +    { | 
|  | 41 | +        base.RegisterOptions(command); | 
|  | 42 | +        // Required common option | 
|  | 43 | +        command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); | 
|  | 44 | +        // Service-specific options | 
|  | 45 | +        command.Options.Add(ManagedLustreOptionDefinitions.FileSystemOption); | 
|  | 46 | +        command.Options.Add(ManagedLustreOptionDefinitions.ImportPrefixesOption); | 
|  | 47 | +        command.Options.Add(ManagedLustreOptionDefinitions.ConflictResolutionModeOption); | 
|  | 48 | +        command.Options.Add(ManagedLustreOptionDefinitions.MaximumErrorsOption); | 
|  | 49 | +        command.Options.Add(ManagedLustreOptionDefinitions.JobNameOption); | 
|  | 50 | + | 
|  | 51 | +        // Validation for conflict resolution mode (Skip|Fail) – consistent with validator style in SubnetSizeAskCommand | 
|  | 52 | +        command.Validators.Add(cmdResult => | 
|  | 53 | +        { | 
|  | 54 | +            if (cmdResult.TryGetValue(ManagedLustreOptionDefinitions.ConflictResolutionModeOption, out var mode) | 
|  | 55 | +                && !string.IsNullOrWhiteSpace(mode) | 
|  | 56 | +                && !string.Equals(mode, "Skip", StringComparison.OrdinalIgnoreCase) | 
|  | 57 | +                && !string.Equals(mode, "Fail", StringComparison.OrdinalIgnoreCase)) | 
|  | 58 | +            { | 
|  | 59 | +                cmdResult.AddError("Invalid conflict resolution mode. Allowed values: Skip, Fail."); | 
|  | 60 | +            } | 
|  | 61 | +        }); | 
|  | 62 | +    } | 
|  | 63 | + | 
|  | 64 | +    protected override FileSystemImportJobCreateOptions BindOptions(ParseResult parseResult) | 
|  | 65 | +    { | 
|  | 66 | +        var options = base.BindOptions(parseResult); | 
|  | 67 | +        options.FileSystem = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.FileSystemOption.Name); | 
|  | 68 | +        options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name); | 
|  | 69 | +        var prefixes = parseResult.GetValueOrDefault<string[]>(ManagedLustreOptionDefinitions.ImportPrefixesOption.Name); | 
|  | 70 | +        if (prefixes == null || prefixes.Length == 0) | 
|  | 71 | +        { | 
|  | 72 | +            options.ImportPrefixes = new List<string> { "/" }; | 
|  | 73 | +        } | 
|  | 74 | +        else | 
|  | 75 | +        { | 
|  | 76 | +            options.ImportPrefixes = prefixes.ToList(); | 
|  | 77 | +        } | 
|  | 78 | +        var conflictMode = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.ConflictResolutionModeOption.Name); | 
|  | 79 | +        conflictMode = string.IsNullOrWhiteSpace(conflictMode) | 
|  | 80 | +            ? "Skip" | 
|  | 81 | +            : char.ToUpperInvariant(conflictMode[0]) + conflictMode.Substring(1).ToLowerInvariant(); | 
|  | 82 | +        options.ConflictResolutionMode = conflictMode; | 
|  | 83 | +        options.MaximumErrors = parseResult.GetValueOrDefault<int?>(ManagedLustreOptionDefinitions.MaximumErrorsOption.Name) ?? -1; | 
|  | 84 | +        options.AdminStatus = "Active"; // Hard-coded since service no longer accepts parameter | 
|  | 85 | +        options.Name = parseResult.GetValueOrDefault<string>(ManagedLustreOptionDefinitions.JobNameOption.Name); | 
|  | 86 | +        return options; | 
|  | 87 | +    } | 
|  | 88 | + | 
|  | 89 | +    public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult) | 
|  | 90 | +    { | 
|  | 91 | +        var options = BindOptions(parseResult); | 
|  | 92 | +        try | 
|  | 93 | +        { | 
|  | 94 | +            if (!Validate(parseResult.CommandResult, context.Response).IsValid) | 
|  | 95 | +            { | 
|  | 96 | +                return context.Response; | 
|  | 97 | +            } | 
|  | 98 | + | 
|  | 99 | +            var svc = context.GetService<IManagedLustreService>(); | 
|  | 100 | +            var result = await svc.CreateImportJobAsync( | 
|  | 101 | +                options.Subscription!, | 
|  | 102 | +                options.ResourceGroup!, | 
|  | 103 | +                options.FileSystem!, | 
|  | 104 | +                options.Name, | 
|  | 105 | +                options.ImportPrefixes, | 
|  | 106 | +                options.ConflictResolutionMode!, | 
|  | 107 | +                options.MaximumErrors, | 
|  | 108 | +                options.Tenant, | 
|  | 109 | +                options.RetryPolicy); | 
|  | 110 | + | 
|  | 111 | +            context.Response.Results = ResponseResult.Create( | 
|  | 112 | +                new FileSystemImportJobCreateResult(result), | 
|  | 113 | +                ManagedLustreJsonContext.Default.FileSystemImportJobCreateResult); | 
|  | 114 | +        } | 
|  | 115 | +        catch (Exception ex) | 
|  | 116 | +        { | 
|  | 117 | +            _logger.LogError(ex, | 
|  | 118 | +                "Error creating AMLFS import job. FileSystem: {FileSystem} ResourceGroup: {ResourceGroup} Options: {@Options}", | 
|  | 119 | +                options.FileSystem, options.ResourceGroup, options); | 
|  | 120 | +            HandleException(context, ex); | 
|  | 121 | +        } | 
|  | 122 | + | 
|  | 123 | +        return context.Response; | 
|  | 124 | +    } | 
|  | 125 | + | 
|  | 126 | +    protected override HttpStatusCode GetStatusCode(Exception ex) => ex switch | 
|  | 127 | +    { | 
|  | 128 | +        Azure.RequestFailedException reqEx => (HttpStatusCode)reqEx.Status, | 
|  | 129 | +        _ => base.GetStatusCode(ex) | 
|  | 130 | +    }; | 
|  | 131 | + | 
|  | 132 | +    internal record FileSystemImportJobCreateResult(Models.ImportJobInfo ImportJob); | 
|  | 133 | +} | 
0 commit comments