Skip to content

Commit

Permalink
Basic CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
yorickdewid committed Dec 29, 2020
1 parent 7206782 commit 5081482
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 0 deletions.
7 changes: 7 additions & 0 deletions FunderMaps.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunderMaps.Portal", "src\Fu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.BatchNode", "src\FunderMaps.BatchNode\FunderMaps.BatchNode.csproj", "{532227C4-BAD4-46AB-B45D-65F15317AFFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.Cli", "src\FunderMaps.Cli\FunderMaps.Cli.csproj", "{ACDD237D-7C7E-425B-88CD-EF90A0037505}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -89,6 +91,10 @@ Global
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.Build.0 = Release|Any CPU
{ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -106,6 +112,7 @@ Global
{2A345D40-2842-4AAA-A552-630FAD97B9D5} = {0219D434-DD4F-4187-ADED-8CD4A62E90F1}
{CF0E7A1A-20A8-4A15-8B57-D7218B9F33D1} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
{532227C4-BAD4-46AB-B45D-65F15317AFFA} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
{ACDD237D-7C7E-425B-88CD-EF90A0037505} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CDA50611-E011-4C1E-8823-7A6943652DCE}
Expand Down
18 changes: 18 additions & 0 deletions src/FunderMaps.Cli/CommandDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace FunderMaps.Cli.Drivers
{
internal abstract class CommandDriver
{
protected static Task ResolveSelfScope<TDriver>(IServiceProvider services, Func<TDriver, Task> shadowFunc)
{
var scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
using var serviceScope = scopeFactory.CreateScope();
var serviceProvider = serviceScope.ServiceProvider;
TDriver driver = serviceProvider.GetService<TDriver>();
return shadowFunc(driver);
}
}
}
38 changes: 38 additions & 0 deletions src/FunderMaps.Cli/DriverHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

namespace FunderMaps.Cli
{
/// <summary>
/// Driver command handler.
/// </summary>
public static class DriverHandler
{
/// <summary>
/// Create delegate with default parametes.
/// </summary>
/// <param name="action">Handler action.</param>
/// <returns>See <see cref="ICommandHandler"/>.</returns>
public static ICommandHandler InstantiateDriver(Func<FileInfo, IHost, Task> action)
=> CommandHandler.Create<FileInfo, IHost>(action);

/// <summary>
/// Create delegate with default parametes.
/// </summary>
/// <param name="action">Handler action.</param>
/// <returns>See <see cref="ICommandHandler"/>.</returns>
public static ICommandHandler InstantiateDriver<T0>(Func<FileInfo, IHost, T0, Task> action)
=> CommandHandler.Create<FileInfo, IHost, T0>(action);

/// <summary>
/// Create delegate with default parametes.
/// </summary>
/// <param name="action">Handler action.</param>
/// <returns>See <see cref="ICommandHandler"/>.</returns>
public static ICommandHandler InstantiateDriver<T0, T1>(Func<FileInfo, IHost, T0, T1, Task> action)
=> CommandHandler.Create<FileInfo, IHost, T0, T1>(action);
}
}
68 changes: 68 additions & 0 deletions src/FunderMaps.Cli/Drivers/BatchDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FunderMaps.Core.Interfaces;
using FunderMaps.Core.MapBundle;
using FunderMaps.Core.Threading;
using FunderMaps.Core.Types;
using Microsoft.Extensions.Hosting;

namespace FunderMaps.Cli.Drivers
{
/// <summary>
/// GRPC channel factory.
/// </summary>
internal class BatchDriver : CommandDriver
{
private readonly IBatchService _batchService;
private readonly IBundleService _bundleService;

/// <summary>
/// Create new instance.
/// </summary>
public BatchDriver(IBatchService batchService, IBundleService bundleService)
=> (_batchService, _bundleService) = (batchService, bundleService);

private async Task StatusAsync(CancellationToken token = default)
{
DispatchManagerStatus status = await _batchService.StatusAsync(token);
Console.WriteLine($"Jobs failed: {status.JobsFailed}");
Console.WriteLine($"Jobs succeeded: {status.JobsSucceeded}");
}

private async Task BuildBundleAsync(IEnumerable<Guid> bundleIdList, CancellationToken token = default)
{
foreach (var bundleId in bundleIdList)
{
await _bundleService.BuildAsync(new BundleBuildingContext
{
BundleId = bundleId,
Formats = new List<GeometryFormat> { GeometryFormat.GeoPackage },
});
}
}

private async Task BuildAllAsync(CancellationToken token = default)
{
await _bundleService.BuildAllAsync(new BundleBuildingContext
{
Formats = new List<GeometryFormat> { GeometryFormat.GeoPackage },
});
}

#region Factory Methods

public static Task StatusAsync(FileInfo config, IHost host)
=> ResolveSelfScope<BatchDriver>(host.Services, self => self.StatusAsync());

public static Task BuildBundleAsync(FileInfo config, IHost host, IEnumerable<Guid> bundleId)
=> ResolveSelfScope<BatchDriver>(host.Services, self => self.BuildBundleAsync(bundleId));

public static Task BuildAllAsync(FileInfo config, IHost host)
=> ResolveSelfScope<BatchDriver>(host.Services, self => self.BuildAllAsync());

#endregion Factory Methods
}
}
23 changes: 23 additions & 0 deletions src/FunderMaps.Cli/FunderMaps.Cli.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>$(SolutionDir)DocumentationFunderMapsCli.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
<PackageReference Include="System.CommandLine.Hosting" Version="0.3.0-alpha.20574.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FunderMaps.Core\FunderMaps.Core.csproj" />
<ProjectReference Include="..\FunderMaps.Infrastructure\FunderMaps.Infrastructure.csproj" />
</ItemGroup>

</Project>
104 changes: 104 additions & 0 deletions src/FunderMaps.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using Microsoft.Extensions.Hosting;
using System.CommandLine.Parsing;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System;
using System.IO;
using FunderMaps.Cli.Drivers;
using Microsoft.Extensions.Logging;

namespace FunderMaps.Cli
{
/// <summary>
/// Application entry.
/// </summary>
public static class Program
{
/// <summary>
/// Application entry point.
/// </summary>
/// <param name="args">Commandline arguments.</param>
public static Task Main(string[] args)
=> CreateHostBuilder(args).Build().InvokeAsync(args);

/// <summary>
/// Build a host and run the application.
/// </summary>
/// <remarks>
/// The signature of this method should not be changed.
/// External tooling expects this function be present.
/// </remarks>
/// <param name="args">Commandline arguments.</param>
/// <returns>See <see cref="IHostBuilder"/>.</returns>
public static CommandLineBuilder CreateHostBuilder(string[] args)
=> BuildCommandLine()
.AddLocalCommands()
.UseHost(_ => Host.CreateDefaultBuilder(),
host =>
{
host.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Error);
});
host.ConfigureServices(services =>
{
// Configure FunderMaps services.
services.AddFunderMapsCoreServices();
services.AddFunderMapsInfrastructureServices();

services.AddTransient<BatchDriver>();
});
})
.UseDefaults();

/// <summary>
/// Build commandline builder.
/// </summary>
/// <returns>See <see cref="CommandLineBuilder"/>.</returns>
private static CommandLineBuilder BuildCommandLine()
{
RootCommand root = new();
root.AddGlobalOption(new Option<FileInfo>(new[] { "-c", "--config" }, "An option whose argument is parsed as a FileInfo"));
root.Description = "FunderMaps CommandLine Interface";

return new(root);
}

/// <summary>
/// Register local commands.
/// </summary>
/// <returns>See <see cref="CommandLineBuilder"/>.</returns>
private static CommandLineBuilder AddLocalCommands(this CommandLineBuilder rootBuilder)
{
Command command = new("batch", "Run batch operations");
command.AddAlias("b");
command.AddOption(new Option<string>(new[] { "-H", "--host" }, "The batch host to execute the command on"));
command.AddOption(new Option<bool>("--ignore-cert", "Ignore TLS certificate checks"));

{
Command subcommand = new("build-bundle", "The maplayer bundle(s)");
subcommand.AddArgument(new Argument<Guid[]>("bundleId", "(Optional) Bundle identifiers to build")
{
Arity = ArgumentArity.OneOrMore,
});
subcommand.Handler = DriverHandler.InstantiateDriver<IEnumerable<Guid>>(BatchDriver.BuildBundleAsync);
command.AddCommand(subcommand);
}

{
Command subcommand = new("status", "Print the batch status reports");
subcommand.Handler = DriverHandler.InstantiateDriver(BatchDriver.StatusAsync);
command.AddCommand(subcommand);
}

rootBuilder.AddCommand(command);
return rootBuilder;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FunderMaps.Core.Components;
using FunderMaps.Core.Interfaces;
using FunderMaps.Core.MapBundle;
using FunderMaps.Core.Notification;
using FunderMaps.Core.Services;
using FunderMaps.Core.Threading;
Expand Down Expand Up @@ -88,6 +89,7 @@ public static IServiceCollection AddFunderMapsCoreServices(this IServiceCollecti
// Register core services in DI container.
services.AddScoped<IProductService, ProductService>();
services.AddScoped<INotifyService, NotificationHub>();
services.AddScoped<IBundleService, BundleHub>();

// Register core services in DI container.
// NOTE: These services take time to initialize are used more often. Registering
Expand Down
22 changes: 22 additions & 0 deletions src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FunderMaps.Core.Types;
using System;
using System.Collections.Generic;

namespace FunderMaps.Core.MapBundle
{
/// <summary>
/// Context for executing a bundle build task.
/// </summary>
public record BundleBuildingContext
{
/// <summary>
/// The id of the bundle to process.
/// </summary>
public Guid BundleId { get; init; }

/// <summary>
/// Contains all formats we wish to export.
/// </summary>
public IEnumerable<GeometryFormat> Formats { get; set; }
}
}
40 changes: 40 additions & 0 deletions src/FunderMaps.Core/MapBundle/BundleHub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using FunderMaps.Core.Abstractions;
using FunderMaps.Core.Interfaces;
using System;
using System.Threading.Tasks;

#pragma warning disable CA1812 // Internal class is never instantiated
namespace FunderMaps.Core.MapBundle
{
/// <summary>
/// Process bundles.
/// </summary>
internal class BundleHub : AppServiceBase, IBundleService
{
private const string TaskBuildName = "BUNDLE_BUILDING";
private const string TaskBuildAllName = "BUNDLE_BATCH";

private readonly IBatchService _batchService;

/// <summary>
/// Create new instance.
/// </summary>
public BundleHub(AppContext appContext, IBatchService batchService)
=> (AppContext, _batchService) = (appContext, batchService);

/// <summary>
/// Build a bundle.
/// </summary>
/// <param name="context">Bundle building context.</param>
public Task<Guid> BuildAsync(BundleBuildingContext context)
=> _batchService.EnqueueAsync(TaskBuildName, context, AppContext.CancellationToken);

/// <summary>
/// Build all bundles.
/// </summary>
/// <param name="context">Bundle building context.</param>
public Task<Guid> BuildAllAsync(BundleBuildingContext context)
=> _batchService.EnqueueAsync(TaskBuildAllName, context, AppContext.CancellationToken);
}
}
#pragma warning restore CA1812 // Internal class is never instantiated
23 changes: 23 additions & 0 deletions src/FunderMaps.Core/MapBundle/IBundleService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Threading.Tasks;

namespace FunderMaps.Core.MapBundle
{
/// <summary>
/// Bundle service.
/// </summary>
public interface IBundleService
{
/// <summary>
/// Build a bundle.
/// </summary>
/// <param name="context">Bundle building context.</param>
Task<Guid> BuildAsync(BundleBuildingContext context);

/// <summary>
/// Build all bundles.
/// </summary>
/// <param name="context">Bundle building context.</param>
Task<Guid> BuildAllAsync(BundleBuildingContext context);
}
}

0 comments on commit 5081482

Please sign in to comment.