Browse Source

Use commandline parser for commandline parsing

pull/437/head
Ryan Nowak 6 years ago
parent
commit
8561cb96ff
  1. 19
      src/Microsoft.Tye.Hosting.Diagnostics/DiagnosticOptions.cs
  2. 24
      src/Microsoft.Tye.Hosting/HostOptions.cs
  3. 10
      src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs
  4. 47
      src/Microsoft.Tye.Hosting/TyeHost.cs
  5. 151
      src/tye/Program.RunCommand.cs
  6. 2
      src/tye/Program.cs
  7. 4
      test/E2ETest/TyePurgeTests.cs
  8. 40
      test/E2ETest/TyeRunTests.cs

19
src/Microsoft.Tye.Hosting.Diagnostics/DiagnosticOptions.cs

@ -13,27 +13,14 @@ namespace Microsoft.Tye.Hosting.Diagnostics
public (string Key, string Value) DistributedTraceProvider { get; set; }
public (string Key, string Value) MetricsProvider { get; set; }
public static DiagnosticOptions FromConfiguration(IConfiguration configuration)
public static (string, string) GetProvider(string text)
{
return new DiagnosticOptions
{
LoggingProvider = GetProvider(configuration, "logs"),
DistributedTraceProvider = GetProvider(configuration, "dtrace"),
MetricsProvider = GetProvider(configuration, "metrics")
};
}
private static (string, string) GetProvider(IConfiguration configuration, string providerName)
{
var providerString = configuration[providerName];
if (string.IsNullOrEmpty(providerString))
if (string.IsNullOrEmpty(text))
{
return (null, null);
}
var pair = providerString.Split('=');
var pair = text.Split('=');
if (pair.Length < 2)
{
return (pair[0].Trim(), null);

24
src/Microsoft.Tye.Hosting/HostOptions.cs

@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.Tye.Hosting.Diagnostics;
namespace Microsoft.Tye.Hosting
{
public class HostOptions
{
public bool Dashboard { get; set; }
public List<string> Debug { get; } = new List<string>();
public DiagnosticOptions Diagnostics { get; } = new DiagnosticOptions();
public bool Docker { get; set; }
public bool NoBuild { get; set; }
public int? Port { get; set; }
}
}

10
src/Microsoft.Tye.Hosting/ProcessRunnerOptions.cs

@ -14,14 +14,14 @@ namespace Microsoft.Tye.Hosting
public string[]? ServicesToDebug { get; set; }
public bool DebugAllServices { get; set; }
public static ProcessRunnerOptions FromArgs(string[] args, string[] servicesToDebug)
public static ProcessRunnerOptions FromHostOptions(HostOptions options)
{
return new ProcessRunnerOptions
{
BuildProjects = !args.Contains("--no-build"),
DebugMode = args.Contains("--debug"),
ServicesToDebug = servicesToDebug,
DebugAllServices = servicesToDebug?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false
BuildProjects = !options.NoBuild,
DebugMode = options.Debug.Any(),
ServicesToDebug = options.Debug.ToArray(),
DebugAllServices = options.Debug?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false
};
}
}

47
src/Microsoft.Tye.Hosting/TyeHost.cs

@ -36,21 +36,13 @@ namespace Microsoft.Tye.Hosting
private AggregateApplicationProcessor? _processor;
private readonly Application _application;
private readonly string[] _args;
private readonly string[] _servicesToDebug;
private readonly HostOptions _options;
private ReplicaRegistry? _replicaRegistry;
public TyeHost(Application application, string[] args)
: this(application, args, new string[0])
{
}
public TyeHost(Application application, string[] args, string[] servicesToDebug)
public TyeHost(Application application, HostOptions options)
{
_application = application;
_args = args;
_servicesToDebug = servicesToDebug;
_options = options;
}
public Application Application => _application;
@ -78,7 +70,7 @@ namespace Microsoft.Tye.Hosting
public async Task<WebApplication> StartAsync()
{
var app = BuildWebApplication(_application, _args, Sink);
var app = BuildWebApplication(_application, _options, Sink);
DashboardWebApplication = app;
_logger = app.Logger;
@ -88,11 +80,9 @@ namespace Microsoft.Tye.Hosting
ConfigureApplication(app);
var configuration = app.Configuration;
_replicaRegistry = new ReplicaRegistry(_application.ContextDirectory, _logger);
_processor = CreateApplicationProcessor(_replicaRegistry, _args, _servicesToDebug, _logger, configuration);
_processor = CreateApplicationProcessor(_replicaRegistry, _options, _logger);
await app.StartAsync();
@ -100,7 +90,7 @@ namespace Microsoft.Tye.Hosting
await _processor.StartAsync(_application);
if (_args.Contains("--dashboard"))
if (_options.Dashboard)
{
OpenDashboard(app.Addresses.First());
}
@ -108,12 +98,16 @@ namespace Microsoft.Tye.Hosting
return app;
}
private static WebApplication BuildWebApplication(
Application application,
string[] args,
ILogEventSink? sink)
private static WebApplication BuildWebApplication(Application application, HostOptions options, ILogEventSink? sink)
{
var builder = WebApplication.CreateBuilder(args);
var args = new List<string>();
if (options.Port.HasValue)
{
args.Add("--port");
args.Add(options.Port.Value.ToString(CultureInfo.InvariantCulture));
}
var builder = WebApplication.CreateBuilder(args.ToArray());
// Logging for this application
builder.Host.UseSerilog((context, configuration) =>
@ -252,13 +246,12 @@ namespace Microsoft.Tye.Hosting
return false;
}
private static AggregateApplicationProcessor CreateApplicationProcessor(ReplicaRegistry replicaRegistry, string[] args, string[] servicesToDebug, Microsoft.Extensions.Logging.ILogger logger, IConfiguration configuration)
private static AggregateApplicationProcessor CreateApplicationProcessor(ReplicaRegistry replicaRegistry, HostOptions options, Microsoft.Extensions.Logging.ILogger logger)
{
var diagnosticOptions = DiagnosticOptions.FromConfiguration(configuration);
var diagnosticsCollector = new DiagnosticsCollector(logger, diagnosticOptions);
var diagnosticsCollector = new DiagnosticsCollector(logger, options.Diagnostics);
// Print out what providers were selected and their values
diagnosticOptions.DumpDiagnostics(logger);
options.Diagnostics.DumpDiagnostics(logger);
var processors = new List<IApplicationProcessor>
{
@ -268,11 +261,11 @@ namespace Microsoft.Tye.Hosting
new HttpProxyService(logger),
new DockerImagePuller(logger),
new DockerRunner(logger, replicaRegistry),
new ProcessRunner(logger, replicaRegistry, ProcessRunnerOptions.FromArgs(args, servicesToDebug))
new ProcessRunner(logger, replicaRegistry, ProcessRunnerOptions.FromHostOptions(options))
};
// If the docker command is specified then transform the ProjectRunInfo into DockerRunInfo
if (args.Contains("--docker"))
if (options.Docker)
{
processors.Insert(0, new TransformProjectsIntoContainers(logger));
}

151
src/tye/Program.RunCommand.cs

@ -11,77 +11,82 @@ using System.Threading;
using Microsoft.Tye.ConfigModel;
using Microsoft.Tye.Extensions;
using Microsoft.Tye.Hosting;
using Microsoft.Tye.Hosting.Diagnostics;
namespace Microsoft.Tye
{
static partial class Program
{
private static Command CreateRunCommand(string[] args)
private static Command CreateRunCommand()
{
var command = new Command("run", "run the application")
{
CommonArguments.Path_Required,
};
// TODO: We'll need to support a --build-args
command.AddOption(new Option("--no-build")
{
Description = "Do not build project files before running.",
Required = false
});
command.AddOption(new Option("--port")
{
Description = "The port to run control plane on.",
Argument = new Argument<int>("port"),
Required = false
});
command.AddOption(new Option("--logs")
{
Description = "Write structured application logs to the specified log providers. Supported providers are console, elastic (Elasticsearch), ai (ApplicationInsights), seq.",
Argument = new Argument<string>("logs"),
Required = false
});
command.AddOption(new Option("--dtrace")
{
Description = "Write distributed traces to the specified providers. Supported providers are zipkin.",
Argument = new Argument<string>("logs"),
Required = false
});
command.AddOption(new Option("--debug")
{
Argument = new Argument<string[]>("service"),
Description = "Wait for debugger attach to specific service. Specify \"*\" to wait for all services.",
Required = false
});
command.AddOption(new Option("--docker")
{
Description = "Run projects as docker containers.",
Required = false
});
new Option("--no-build")
{
Description = "Do not build project files before running.",
Required = false
},
new Option("--port")
{
Description = "The port to run control plane on.",
Argument = new Argument<int?>("port"),
Required = false
},
new Option("--logs")
{
Description = "Write structured application logs to the specified log provider. Supported providers are 'console', 'elastic' (Elasticsearch), 'ai' (ApplicationInsights), 'seq'.",
Argument = new Argument<string>("logs"),
Required = false
},
new Option("--dtrace")
{
Description = "Write distributed traces to the specified tracing provider. Supported providers are 'zipkin'.",
Argument = new Argument<string>("trace"),
Required = false,
},
new Option("--metrics")
{
Description = "Write metrics to the specified metrics provider.",
Argument = new Argument<string>("metrics"),
Required = false
},
new Option("--debug")
{
Argument = new Argument<string[]>("service")
{
Arity = ArgumentArity.ZeroOrMore,
},
Description = "Wait for debugger attach to specific service. Specify \"*\" to wait for all services.",
Required = false
},
new Option("--docker")
{
Description = "Run projects as docker containers.",
Required = false
},
new Option("--dashboard")
{
Description = "Launch dashboard on run.",
Required = false
},
command.AddOption(new Option("--dashboard")
{
Description = "Launch dashboard on run.",
Required = false
});
StandardOptions.Verbosity,
};
command.Handler = CommandHandler.Create<IConsole, FileInfo, string[]>(async (console, path, debug) =>
command.Handler = CommandHandler.Create<RunCommandArguments>(async args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
if (path is null)
if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
var output = new OutputContext(console, Verbosity.Info);
var output = new OutputContext(args.Console, Verbosity.Info);
output.WriteInfoLine("Loading Application Details...");
var application = await ApplicationFactory.CreateAsync(output, path);
var application = await ApplicationFactory.CreateAsync(output, args.Path);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
@ -93,7 +98,24 @@ namespace Microsoft.Tye
output.WriteInfoLine("Launching Tye Host...");
output.WriteInfoLine(string.Empty);
await using var host = new TyeHost(application.ToHostingApplication(), args, debug);
var options = new HostOptions()
{
Dashboard = args.Dashboard,
Docker = args.Docker,
NoBuild = args.NoBuild,
Port = args.Port,
Diagnostics =
{
DistributedTraceProvider = DiagnosticOptions.GetProvider(args.Dtrace),
LoggingProvider = DiagnosticOptions.GetProvider(args.Logs),
MetricsProvider = DiagnosticOptions.GetProvider(args.Metrics),
},
};
options.Debug.AddRange(args.Debug);
await using var host = new TyeHost(application.ToHostingApplication(), options);
await host.RunAsync();
});
@ -114,5 +136,30 @@ namespace Microsoft.Tye
// We use serviceCount * 4 because we currently launch multiple processes per service, this gives the dashboard some breathing room
}
// We have too many options to use the lambda form with each option as a parameter.
// This is slightly cleaner anyway.
private class RunCommandArguments
{
public IConsole Console { get; set; } = default!;
public bool Dashboard { get; set; }
public string[] Debug { get; set; } = Array.Empty<string>();
public string Dtrace { get; set; } = default!;
public bool Docker { get; set; }
public string Logs { get; set; } = default!;
public string Metrics { get; set; } = default!;
public bool NoBuild { get; set; }
public FileInfo Path { get; set; } = default!;
public int? Port { get; set; }
}
}
}

2
src/tye/Program.cs

@ -26,7 +26,7 @@ namespace Microsoft.Tye
command.AddCommand(CreateInitCommand());
command.AddCommand(CreateGenerateCommand());
command.AddCommand(CreateRunCommand(args));
command.AddCommand(CreateRunCommand());
command.AddCommand(CreateBuildCommand());
command.AddCommand(CreatePushCommand());
command.AddCommand(CreateDeployCommand());

4
test/E2ETest/TyePurgeTests.cs

@ -38,7 +38,7 @@ namespace E2ETest
var tyeDir = new DirectoryInfo(Path.Combine(projectDirectory.DirectoryPath, ".tye"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var host = new TyeHost(application.ToHostingApplication(), Array.Empty<string>())
var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
{
Sink = _sink,
};
@ -76,7 +76,7 @@ namespace E2ETest
var tyeDir = new DirectoryInfo(Path.Combine(projectDirectory.DirectoryPath, ".tye"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var host = new TyeHost(application.ToHostingApplication(), Array.Empty<string>())
var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
{
Sink = _sink,
};

40
test/E2ETest/TyeRunTests.cs

@ -61,7 +61,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
@ -92,7 +92,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new[] { "--docker" }, async (app, uri) =>
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
@ -128,7 +128,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new[] { "--docker" }, async (app, uri) =>
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
@ -181,7 +181,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
@ -226,7 +226,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
@ -263,9 +263,8 @@ namespace E2ETest
};
var client = new HttpClient(new RetryHandler(handler));
var args = new[] { "--docker" };
await RunHostingApplication(application, args, async (app, serviceApi) =>
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
@ -280,7 +279,7 @@ namespace E2ETest
Assert.Equal("Things saved to the volume!", await client.GetStringAsync(serviceUri));
});
await RunHostingApplication(application, args, async (app, serviceApi) =>
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
@ -322,7 +321,7 @@ namespace E2ETest
{
await RunHostingApplication(
application,
new[] { "--docker" },
new HostOptions() { Docker = true, },
async (app, uri) =>
{
// Make sure we're running containers
@ -374,7 +373,7 @@ namespace E2ETest
await RunHostingApplication(
application,
new[] { "--docker" },
new HostOptions() { Docker = true, },
async (app, uri) =>
{
// Make sure we're running containers
@ -423,9 +422,12 @@ namespace E2ETest
await File.WriteAllTextAsync(Path.Combine(tempDir.DirectoryPath, "file.txt"), "This content came from the host");
var client = new HttpClient(new RetryHandler(handler));
var args = new[] { "--docker" };
var options = new HostOptions()
{
Docker = true,
};
await RunHostingApplication(application, args, async (app, serviceApi) =>
await RunHostingApplication(application, options, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
@ -453,7 +455,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var appAUri = await GetServiceUrl(client, uri, "app-a");
@ -502,7 +504,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var nginxUri = await GetServiceUrl(client, uri, "nginx");
var appAUri = await GetServiceUrl(client, uri, "appA");
@ -534,7 +536,7 @@ namespace E2ETest
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
await using var host = new TyeHost(application.ToHostingApplication(), Array.Empty<string>())
await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
{
Sink = _sink,
};
@ -562,7 +564,7 @@ namespace E2ETest
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var votingUri = await GetServiceUrl(client, uri, "vote");
var workerUri = await GetServiceUrl(client, uri, "worker");
@ -607,7 +609,7 @@ services:
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var votingUri = await GetServiceUrl(client, uri, "vote");
var workerUri = await GetServiceUrl(client, uri, "worker");
@ -628,9 +630,9 @@ services:
return $"{binding.Protocol ?? "http"}://localhost:{binding.Port}";
}
private async Task RunHostingApplication(ApplicationBuilder application, string[] args, Func<Application, Uri, Task> execute)
private async Task RunHostingApplication(ApplicationBuilder application, HostOptions options, Func<Application, Uri, Task> execute)
{
await using var host = new TyeHost(application.ToHostingApplication(), args)
await using var host = new TyeHost(application.ToHostingApplication(), options)
{
Sink = _sink,
};

Loading…
Cancel
Save