Skip to content

Commit

Permalink
Initial CLI using CommandLineParser (#4197)
Browse files Browse the repository at this point in the history
* Initial CLI using CommandLineParser

* remove extra space

* Update options to be single, dash separated words
  • Loading branch information
JimSuplizio authored Sep 22, 2022
1 parent d7625df commit 11e902f
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 47 deletions.
6 changes: 3 additions & 3 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy.sln
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
# Visual Studio Version 17
VisualStudioVersion = 17.3.32804.467
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.TestProxy", "Azure.Sdk.Tools.TestProxy\Azure.Sdk.Tools.TestProxy.csproj", "{9E3819C4-4760-45C0-B7F3-2EF890EEE1FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Sdk.Tools.TestProxy.Tests", "Azure.Sdk.Tools.TestProxy.Tests\Azure.Sdk.Tools.TestProxy.Tests.csproj", "{CE39ECF2-480A-4659-96FC-9C4617C08666}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.TestProxy.Tests", "Azure.Sdk.Tools.TestProxy.Tests\Azure.Sdk.Tools.TestProxy.Tests.csproj", "{CE39ECF2-480A-4659-96FC-9C4617C08666}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
<ItemGroup Condition="'$(TargetFramework)'=='net5.0'">
<PackageReference Include="Azure.Core" Version="1.24.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.3.0-alpha.21216.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net6.0'">
<PackageReference Include="Azure.Core" Version="1.24.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.3.0-alpha.21216.1" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CommandLine;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
/// <summary>
/// CLICommandOptions contain options common to all CLI Commands (Push, Reset, Restore)
/// </summary>
class CLICommandOptions : DefaultOptions
{
[Option('a', "assets-json-path", Required = true, HelpText = "Required for Push/Reset/Restore. This should be a path to a valid assets.json within a language repository.")]
public string AssetsJsonPath { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CommandLine;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
/// <summary>
/// DefaultOptions is the base for all CommandParser Verbs. The only options that should go in here are ones common to everything.
/// </summary>
class DefaultOptions
{
[Option('l', "storage-location", Default = null, HelpText = "The path to the target local git repo. If not provided as an argument, Environment variable TEST_PROXY_FOLDER will be consumed. Lacking both, the current working directory will be utilized.")]
public string StorageLocation { get; set; }

[Option('p', "storage-plugin", Default = "GitStore", HelpText = "The plugin for the selected storage, default is Git storage is GitStore. (Currently the only option)")]
public string StoragePlugin { get; set; }

[Option('v', "version", Default = false, HelpText = "Flag. Output the TestProxy version.")]
public bool VersionFlag { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CommandLine.Text;
using CommandLine;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
/// <summary>
/// Any unique options to the push command will reside here.
/// </summary>
[Verb("push", HelpText = "Push the assets, referenced by assets.json, into git.")]
class PushOptions : CLICommandOptions
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using CommandLine;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
/// <summary>
/// Any unique options to the reset command will reside here.
/// </summary>
[Verb("reset", HelpText = "Reset the assets, referenced by assets.json, from git to their original files referenced by the tag. Will prompt if there's pending changes.")]
class ResetOptions : CLICommandOptions
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using CommandLine;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
/// <summary>
/// Any unique options to the restore command will reside here.
/// </summary>
[Verb("restore", HelpText = "Restore the assets, referenced by assets.json, from git.")]
class RestoreOptions : CLICommandOptions
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using CommandLine;
using System.Collections.Generic;

namespace Azure.Sdk.Tools.TestProxy.CommandParserOptions
{
[Verb("start", isDefault: true, HelpText = "Start the TestProxy.")]
class StartOptions : DefaultOptions
{
[Option('i', "insecure", Default = false, HelpText = "Flag; Allow insecure upstream SSL certs.")]
public bool Insecure { get; set; }

[Option('d', "dump", Default = false, HelpText = "Flag; Output configuration values when starting the Test-Proxy.")]
public bool Dump { get; set; }

// On the command line, use -- and everything after that becomes arguments to Host.CreateDefaultBuilder
// For example Test-Proxy -i -d -- --urls https://localhost:8002 would set AdditionaArgs to a list containing
// --urls and https://localhost:8002 as individual entries. This is converted to a string[] before being
// passed to Host.CreateDefaultBuilder
[CommandLine.Value(0)]
public IEnumerable<string> AdditionalArgs { get; set; }

}

}
2 changes: 1 addition & 1 deletion tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ To ensure that your local copy is up to date, run:

## Command line arguments

This is the help information for test-proxy. It uses the pre-release package [`System.CommandLine.DragonFruit`](https://github.com/dotnet/command-line-api) to parse arguments.
This is the help information for test-proxy. It uses the nuget package [`CommandLineParser`](https://www.nuget.org/packages/CommandLineParser) to parse arguments.

```text
Azure.Sdk.Tools.TestProxy
Expand Down
95 changes: 54 additions & 41 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using CommandLine;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Expand All @@ -19,6 +20,12 @@
using Azure.Sdk.Tools.TestProxy.Store;
using Azure.Sdk.Tools.TestProxy.Console;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;
using System.Linq;
using Azure.Sdk.Tools.TestProxy.CommandParserOptions;

namespace Azure.Sdk.Tools.TestProxy
{
Expand Down Expand Up @@ -46,54 +53,60 @@ private static string resolveRepoLocation(string storageLocation = null)
/// <summary>
/// test-proxy
/// </summary>
/// <param name="insecure">Allow untrusted SSL certs from upstream server</param>
/// <param name="storageLocation">The path to the target local git repo. If not provided as an argument, Environment variable TEST_PROXY_FOLDER will be consumed. Lacking both, the current working directory will be utilized.</param>
/// <param name="storagePlugin">Does the user have a preference as to a default storage plugin? Defaults to "No plugin" currently.</param>
/// <param name="command">A specific test-proxy action to be carried out. Supported options: ["Save", "Restore", "Reset"]</param>
/// <param name="assetsJsonPath">Only required if a "command" value is present. This should be a path to a valid assets.json within a language repository.</param>
/// <param name="dump">Flag. Pass to dump configuration values before starting the application.</param>
/// <param name="version">Flag. Pass to get the version of the tool.</param>
/// <param name="args">Unmapped arguments un-used by the test-proxy are sent directly to the ASPNET configuration provider.</param>
public static void Main(bool insecure = false, string storageLocation = null, string storagePlugin = null, string command = null, string assetsJsonPath = null, bool dump = false, bool version = false, string[] args = null)
/// <param name="args">CommandLineParser arguments. In server mode use double dash '--' and everything after that becomes additional arguments to Host.CreateDefaultBuilder. Ex. -- arg1 value1 arg2 value2 </param>
public static async Task Main(string[] args = null)
{
if (version)
// JRS - Temporarily disable this check because of https://github.com/Azure/azure-sdk-tools/issues/4116
// This throws and will exit
// new GitProcessHandler().VerifyGitMinVersion();
var parser = new Parser(settings =>
{
settings.AutoVersion = false;
settings.CaseSensitive = false;
settings.HelpWriter = System.Console.Out;
settings.EnableDashDash = true;
});

await parser.ParseArguments<StartOptions, PushOptions, ResetOptions, RestoreOptions>(args).WithParsedAsync(Run);
}

private static async Task Run(object commandObj)
{
DefaultOptions defaultOptions = (DefaultOptions)commandObj;
if (defaultOptions.VersionFlag)
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var semanticVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
var assemblyVersion = assembly.GetName().Version;

System.Console.WriteLine($"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}-dev.{semanticVersion}");

Environment.Exit(0);
}

// This throws and will exit
// JRS - Temporarily disable this check because of https://github.com/Azure/azure-sdk-tools/issues/4116
// new GitProcessHandler().VerifyGitMinVersion();

TargetLocation = resolveRepoLocation(storageLocation);
TargetLocation = resolveRepoLocation(defaultOptions.StorageLocation);
Resolver = new StoreResolver();
DefaultStore = Resolver.ResolveStore(storagePlugin ?? "GitStore");
DefaultStore = Resolver.ResolveStore(defaultOptions.StoragePlugin ?? "GitStore");

if (!String.IsNullOrWhiteSpace(command))
switch (commandObj)
{
switch (command.ToLowerInvariant())
{
case "save":
DefaultStore.Push(assetsJsonPath);
break;
case "restore":
DefaultStore.Restore(assetsJsonPath);
break;
case "reset":
DefaultStore.Reset(assetsJsonPath);
break;
default:
throw new Exception($"One must provide a valid value for argument \"command\". \"{command}\" is not a valid option.");
}
case StartOptions startOptions:
StartServer(startOptions);
break;
case PushOptions pushOptions:
await DefaultStore.Push(pushOptions.AssetsJsonPath);
break;
case ResetOptions resetOptions:
await DefaultStore.Reset(resetOptions.AssetsJsonPath);
break;
case RestoreOptions restoreOptions:
await DefaultStore.Restore(restoreOptions.AssetsJsonPath);
break;
default:
throw new ArgumentException("Invalid verb. The only supported verbs are start, push, reset and restore.");
}
}

_insecure = insecure;
private static void StartServer(StartOptions startOptions)
{
_insecure = startOptions.Insecure;
Regex.CacheSize = 0;

var statusThreadCts = new CancellationTokenSource();
Expand All @@ -102,7 +115,7 @@ public static void Main(bool insecure = false, string storageLocation = null, st
() => $"[{DateTime.UtcNow.ToString("HH:mm:ss")}] Recorded: {RequestsRecorded}\tPlayed Back: {RequestsPlayedBack}",
newLine: true, statusThreadCts.Token);

var host = Host.CreateDefaultBuilder(args);
var host = Host.CreateDefaultBuilder(startOptions.AdditionalArgs.ToArray());

host.ConfigureWebHostDefaults(
builder =>
Expand All @@ -112,22 +125,22 @@ public static void Main(bool insecure = false, string storageLocation = null, st
{
loggingBuilder.ClearProviders();
loggingBuilder.AddConfiguration(hostBuilder.Configuration.GetSection("Logging"));
loggingBuilder.AddSimpleConsole(options =>
loggingBuilder.AddSimpleConsole(formatterOptions =>
{
options.TimestampFormat = "[HH:mm:ss] ";
formatterOptions.TimestampFormat = "[HH:mm:ss] ";
});
loggingBuilder.AddDebug();
loggingBuilder.AddEventSourceLogger();
})
.ConfigureKestrel(options =>
.ConfigureKestrel(kestrelServerOptions =>
{
options.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http1);
kestrelServerOptions.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http1);
})
);

var app = host.Build();

if (dump)
if (startOptions.Dump)
{
var config = app.Services?.GetService<IConfiguration>();
System.Console.WriteLine("Dumping Resolved Configuration Values:");
Expand Down

0 comments on commit 11e902f

Please sign in to comment.