From 4743b1a74c95b450bf4e37305bd6b3a765b8b0f0 Mon Sep 17 00:00:00 2001 From: Yaakov Date: Sun, 26 Sep 2021 00:33:10 +1000 Subject: [PATCH] Minor refactoring, add enumeration of storage container by folder, by container, or by entire account --- .../CommandTree/ParsedArgumentsCommand.cs | 40 +++++++ src/console/Commands/Cdn/StorageLsCommand.cs | 101 ++++++++++-------- .../Extensions/ImmutableArrayExtensions.cs | 17 +++ src/console/Program.cs | 8 +- 4 files changed, 123 insertions(+), 43 deletions(-) create mode 100644 src/console/CommandTree/ParsedArgumentsCommand.cs create mode 100644 src/console/Extensions/ImmutableArrayExtensions.cs diff --git a/src/console/CommandTree/ParsedArgumentsCommand.cs b/src/console/CommandTree/ParsedArgumentsCommand.cs new file mode 100644 index 0000000..3e4c2df --- /dev/null +++ b/src/console/CommandTree/ParsedArgumentsCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using CommandLine; +using CommandLine.Text; +using Microsoft.Extensions.DependencyInjection; + +namespace Shamir.Console +{ + public abstract class ParsedArgumentsCommand : ICommand + { + public abstract string Name { get; } + public abstract string Description { get; } + public ImmutableArray Arguments { get; private set; } + + public void Initialize(ReadOnlySpan args) + { + Arguments = args.ToImmutableArray(); + } + + public async ValueTask ExecuteAsync(IServiceProvider serviceProvider) + { + using var parser = serviceProvider.GetRequiredService(); + var result = parser.ParseArguments(Arguments); + return await result.MapResult( + options => ExecuteAsync(serviceProvider, options), + errors => + { + var helpText = HelpText.AutoBuild(result); + helpText.Heading = string.Empty; + helpText.Copyright = string.Empty; + System.Console.Error.WriteLine(helpText); + return ValueTask.FromResult(1); + } + ); + } + + public abstract ValueTask ExecuteAsync(IServiceProvider serviceProvider, TOptions options); + } +} diff --git a/src/console/Commands/Cdn/StorageLsCommand.cs b/src/console/Commands/Cdn/StorageLsCommand.cs index 2a312cf..81f8445 100644 --- a/src/console/Commands/Cdn/StorageLsCommand.cs +++ b/src/console/Commands/Cdn/StorageLsCommand.cs @@ -1,48 +1,20 @@ using System; -using System.Collections.Immutable; using System.Threading.Tasks; using Azure.Storage.Blobs; using CommandLine; -using Microsoft.Extensions.DependencyInjection; namespace Shamir.Console { - public abstract class ParsedArgumentsCommand : ICommand - { - public abstract string Name { get; } - public abstract string Description { get; } - public ImmutableArray Arguments { get; private set; } - - public void Initialize(ReadOnlySpan args) - { - var array = ImmutableArray.CreateBuilder(args.Length); - foreach (var arg in args) - { - array.Add(arg); - } - Arguments = array.MoveToImmutable(); - } - - public async ValueTask ExecuteAsync(IServiceProvider serviceProvider) - { - using var parser = serviceProvider.GetRequiredService(); - var result = parser.ParseArguments(Arguments); - return await result.MapResult( - options => ExecuteAsync(serviceProvider, options), - errors => ValueTask.FromResult(1) - ); - } - - public abstract ValueTask ExecuteAsync(IServiceProvider serviceProvider, TOptions options); - } - public class StorageLsOptions { - [Option("connection-string", Required = false)] + [Option("connection-string", Required = false, HelpText = "Azure Storage connection string for the Storage Account backing the CDN.")] public string? ConnectionString { get; set; } - [Value(0)] - public string? ContainerName { get; set; } + [Option('a', "all", HelpText = "List all blobs in the container")] + public bool EnumerateAll { get; set; } + + [Value(0, MetaName = "Path", HelpText = "Path to enumerate, starting with the Azure Storage container name.")] + public string? Path { get; set; } } public sealed class StorageLsCommand : ParsedArgumentsCommand @@ -55,23 +27,68 @@ namespace Shamir.Console { var connectionString = options.ConnectionString ?? Environment.GetEnvironmentVariable("AZURE_CONNECTION_STRING"); - if (options.ContainerName is null) + if (options.Path is null) { - var client = new BlobServiceClient(connectionString); - await foreach (var container in client.GetBlobContainersAsync()) + if (options.EnumerateAll) { - System.Console.WriteLine(container.Name); + var client = new BlobServiceClient(connectionString); + await foreach (var container in client.GetBlobContainersAsync()) + { + var containerClient = new BlobContainerClient(connectionString, container.Name); + + await foreach (var blob in containerClient.GetBlobsAsync()) + { + System.Console.Write(container.Name); + System.Console.Write('/'); + System.Console.WriteLine(blob.Name); + } + } + } + else + { + var client = new BlobServiceClient(connectionString); + await foreach (var container in client.GetBlobContainersAsync()) + { + System.Console.WriteLine(container.Name); + } + } + } + else if (options.EnumerateAll) + { + var containerName = options.Path; + var client = new BlobContainerClient(connectionString, containerName); + + await foreach (var blob in client.GetBlobsAsync()) + { + System.Console.Write(containerName); + System.Console.Write('/'); + System.Console.WriteLine(blob.Name); } } else { - var client = new BlobContainerClient(connectionString, options.ContainerName); + var delimiterIndex = options.Path.IndexOf('/'); + var containerName = delimiterIndex > 0 ? options.Path[..delimiterIndex] : options.Path; + var client = new BlobContainerClient(connectionString, containerName); - await foreach (var blob in client.GetBlobsAsync()) + var prefix = delimiterIndex > 0 ? options.Path[(delimiterIndex + 1)..] : string.Empty; + await foreach (var blob in client.GetBlobsByHierarchyAsync(default, default, delimiter: "/", prefix)) { - System.Console.Write(options.ContainerName); + System.Console.Write(containerName); System.Console.Write('/'); - System.Console.WriteLine(blob.Name); + + if (blob.IsPrefix) + { + System.Console.WriteLine(blob.Prefix); + } + else if (blob.IsBlob) + { + System.Console.WriteLine(blob.Blob.Name); + } + else + { + throw new NotImplementedException("Unknown result - neither a blob nor a prefix (folder)."); + } } } diff --git a/src/console/Extensions/ImmutableArrayExtensions.cs b/src/console/Extensions/ImmutableArrayExtensions.cs new file mode 100644 index 0000000..353fa85 --- /dev/null +++ b/src/console/Extensions/ImmutableArrayExtensions.cs @@ -0,0 +1,17 @@ +namespace System.Collections.Immutable +{ + public static class ImmutableArrayExtensions + { + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) + { + var array = ImmutableArray.CreateBuilder(span.Length); + + foreach (var value in span) + { + array.Add(value); + } + + return array.MoveToImmutable(); + } + } +} \ No newline at end of file diff --git a/src/console/Program.cs b/src/console/Program.cs index 58028f9..0fa3077 100644 --- a/src/console/Program.cs +++ b/src/console/Program.cs @@ -10,7 +10,13 @@ namespace Shamir.Console public static async Task Main(string[] args) { await using var serviceProvider = new ServiceCollection() - .AddTransient(sp => new Parser(with => with.EnableDashDash = true)) + .AddTransient(sp => new Parser(with => + { + with.AutoHelp = true; + with.AutoVersion = true; + with.EnableDashDash = true; + with.IgnoreUnknownArguments = false; + })) .BuildServiceProvider(); var tree = new DefaultCommandTree(