diff --git a/.gitignore b/.gitignore
index 9f3b9ff4..fcfa8504 100644
--- a/.gitignore
+++ b/.gitignore
@@ -290,3 +290,5 @@ __pycache__/
# ci.global.json is used in CI; local builds are unconstrained
global.json
+
+.DS_Store/
diff --git a/build/Build.Windows.ps1 b/build/Build.Windows.ps1
index bc118297..93ebec75 100644
--- a/build/Build.Windows.ps1
+++ b/build/Build.Windows.ps1
@@ -39,7 +39,7 @@ function Create-ArtifactDir
function Publish-Archives($version)
{
- $rids = $([xml](Get-Content .\src\SeqCli\SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers.Split(';')
+ $rids = $([xml](Get-Content .\src\SeqCli\SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers[0].Split(';')
foreach ($rid in $rids) {
$tfm = $framework
if ($rid -eq "win-x64") {
diff --git a/seqcli.sln b/seqcli.sln
index 8f91a065..3d4abbf3 100644
--- a/seqcli.sln
+++ b/seqcli.sln
@@ -19,7 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3587B633-0
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "asset", "asset", "{438A0DA5-F3CF-4FCE-B43A-B6DA2981D4AE}"
ProjectSection(SolutionItems) = preProject
- asset\SeqCliLicense.rtf = asset\SeqCliLicense.rtf
asset\SeqCli.ico = asset\SeqCli.ico
asset\SeqCli.png = asset\SeqCli.png
EndProjectSection
diff --git a/seqcli.sln.DotSettings b/seqcli.sln.DotSettings
index 6c5c917e..95d327ba 100644
--- a/seqcli.sln.DotSettings
+++ b/seqcli.sln.DotSettings
@@ -1,4 +1,6 @@
+ IO
+ IP
MS
True
True
@@ -9,6 +11,7 @@
True
True
True
+ True
True
True
True
@@ -16,6 +19,7 @@
True
True
True
+ True
True
True
True
@@ -32,4 +36,5 @@
True
True
True
+ True
True
\ No newline at end of file
diff --git a/src/Roastery/Fake/Person.cs b/src/Roastery/Fake/Person.cs
index 53394561..56c0ae0c 100644
--- a/src/Roastery/Fake/Person.cs
+++ b/src/Roastery/Fake/Person.cs
@@ -14,7 +14,7 @@ public Person(string? name, string? address)
}
static readonly string[] Forenames =
- {
+ [
"Akeem",
"Alice",
"Alok",
@@ -40,10 +40,10 @@ public Person(string? name, string? address)
"Yoshi",
"Zach",
"Zeynep"
- };
+ ];
static readonly string[] Surnames =
- {
+ [
"Anderson",
"Alvarez",
"Brookes",
@@ -60,10 +60,10 @@ public Person(string? name, string? address)
"Smith",
"Xia",
"Zheng"
- };
+ ];
static readonly string[] Streets =
- {
+ [
"Lilac Road",
"Lilly Street",
"Carnation Street",
@@ -78,7 +78,7 @@ public Person(string? name, string? address)
"Trillium Creek Parkway",
"Grevillea Street",
"Kurrajong Street"
- };
+ ];
public static Person Generate()
{
diff --git a/src/Roastery/Web/FaultInjectionMiddleware.cs b/src/Roastery/Web/FaultInjectionMiddleware.cs
index 27dae6d8..0c6f472e 100644
--- a/src/Roastery/Web/FaultInjectionMiddleware.cs
+++ b/src/Roastery/Web/FaultInjectionMiddleware.cs
@@ -18,15 +18,15 @@ public FaultInjectionMiddleware(ILogger logger, HttpServer next)
{
_logger = logger.ForContext();
_next = next;
- _faults = new Func>[]
- {
+ _faults =
+ [
Unauthorized,
Unauthorized,
Unauthorized,
Timeout,
Timeout,
Disposed
- };
+ ];
}
Task Unauthorized(HttpRequest request)
diff --git a/src/Roastery/Web/Router.cs b/src/Roastery/Web/Router.cs
index 5df8d7ed..0c9f7be9 100644
--- a/src/Roastery/Web/Router.cs
+++ b/src/Roastery/Web/Router.cs
@@ -63,7 +63,7 @@ public Router(IEnumerable controllers, ILogger logger)
{
using var _ = LogContext.PushProperty("Controller", controllerName);
using var __ = LogContext.PushProperty("Action", actionName);
- return (Task) method.Invoke(controller, new object[] {r})!;
+ return (Task) method.Invoke(controller, [r])!;
});
_logger.Debug("Binding route HTTP {HttpMethod} {RouteTemplate} to action method {Controller}.{Action}()",
diff --git a/src/SeqCli/Apps/Definitions/AppMetadataReader.cs b/src/SeqCli/Apps/Definitions/AppMetadataReader.cs
index 8abd7be0..58269661 100644
--- a/src/SeqCli/Apps/Definitions/AppMetadataReader.cs
+++ b/src/SeqCli/Apps/Definitions/AppMetadataReader.cs
@@ -79,21 +79,15 @@ static Dictionary GetAvailableSettings(Type mainRe
});
}
- static readonly HashSet IntegerTypes = new()
- {
+ static readonly HashSet IntegerTypes =
+ [
typeof(short), typeof(ushort), typeof(int), typeof(uint),
typeof(long), typeof(ulong)
- };
+ ];
- static readonly HashSet DecimalTypes = new()
- {
- typeof(float), typeof(double), typeof(decimal)
- };
+ static readonly HashSet DecimalTypes = [typeof(float), typeof(double), typeof(decimal)];
- static readonly HashSet BooleanTypes = new()
- {
- typeof(bool)
- };
+ static readonly HashSet BooleanTypes = [typeof(bool)];
internal static AppSettingType GetSettingType(Type type)
{
diff --git a/src/SeqCli/Apps/Hosting/AppContainer.cs b/src/SeqCli/Apps/Hosting/AppContainer.cs
index fe23a843..72184492 100644
--- a/src/SeqCli/Apps/Hosting/AppContainer.cs
+++ b/src/SeqCli/Apps/Hosting/AppContainer.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/SeqCli/Cli/CommandAttribute.cs b/src/SeqCli/Cli/CommandAttribute.cs
index 775e03a5..a41f5422 100644
--- a/src/SeqCli/Cli/CommandAttribute.cs
+++ b/src/SeqCli/Cli/CommandAttribute.cs
@@ -22,8 +22,8 @@ public class CommandAttribute : Attribute, ICommandMetadata
public string Name { get; }
public string? SubCommand { get; }
public string HelpText { get; }
-
public string? Example { get; set; }
+ public bool IsPreview { get; set; }
public CommandAttribute(string name, string helpText)
{
diff --git a/src/SeqCli/Cli/CommandLineHost.cs b/src/SeqCli/Cli/CommandLineHost.cs
index e5c686fc..27d46fc8 100644
--- a/src/SeqCli/Cli/CommandLineHost.cs
+++ b/src/SeqCli/Cli/CommandLineHost.cs
@@ -39,30 +39,37 @@ public async Task Run(string[] args, LoggingLevelSwitch levelSwitch)
if (args.Length > 0)
{
+ const string prereleaseArg = "--pre", verboseArg = "--verbose";
+
var norm = args[0].ToLowerInvariant();
var subCommandNorm = args.Length > 1 && !args[1].Contains('-') ? args[1].ToLowerInvariant() : null;
-
+
+ var pre = args.Any(a => a == prereleaseArg);
+
var cmd = _availableCommands.SingleOrDefault(c =>
- c.Metadata.Name == norm && (c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));
+ (!c.Metadata.IsPreview || pre) &&
+ c.Metadata.Name == norm &&
+ (c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));
if (cmd != null)
{
var amountToSkip = cmd.Metadata.SubCommand == null ? 1 : 2;
- var commandSpecificArgs = args.Skip(amountToSkip).ToArray();
+ var commandSpecificArgs = args.Skip(amountToSkip).Where(arg => cmd.Metadata.Name == "help" || arg != prereleaseArg).ToArray();
- var verboseArg = commandSpecificArgs.FirstOrDefault(arg => arg == "--verbose");
- if (verboseArg != null)
+ var verbose = commandSpecificArgs.Any(arg => arg == verboseArg);
+ if (verbose)
{
levelSwitch.MinimumLevel = LogEventLevel.Information;
commandSpecificArgs = commandSpecificArgs.Where(arg => arg != verboseArg).ToArray();
}
- return await cmd.Value.Value.Invoke(commandSpecificArgs);
+ var impl = cmd.Value.Value;
+ return await impl.Invoke(commandSpecificArgs);
}
}
Console.WriteLine($"Usage: {name} []");
Console.WriteLine($"Type `{name} help` for available commands");
- return -1;
+ return 1;
}
}
\ No newline at end of file
diff --git a/src/SeqCli/Cli/CommandMetadata.cs b/src/SeqCli/Cli/CommandMetadata.cs
index 0feed60e..997c450a 100644
--- a/src/SeqCli/Cli/CommandMetadata.cs
+++ b/src/SeqCli/Cli/CommandMetadata.cs
@@ -20,4 +20,5 @@ public class CommandMetadata : ICommandMetadata
public string? SubCommand { get; set; }
public required string HelpText { get; set; }
public string? Example { get; set; }
-}
\ No newline at end of file
+ public bool IsPreview { get; set; }
+}
diff --git a/src/SeqCli/Cli/Commands/ApiKey/CreateCommand.cs b/src/SeqCli/Cli/Commands/ApiKey/CreateCommand.cs
index 68be490e..514daa0c 100644
--- a/src/SeqCli/Cli/Commands/ApiKey/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/ApiKey/CreateCommand.cs
@@ -32,20 +32,17 @@ namespace SeqCli.Cli.Commands.ApiKey;
Example = "seqcli apikey create -t 'Test API Key' -p Environment=Test")]
class CreateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly PropertiesFeature _properties;
readonly OutputFormatFeature _output;
+ readonly StoragePathFeature _storagePath;
string? _title, _token, _filter, _level, _connectUsername, _connectPassword;
string[]? _permissions;
bool _useServerTimestamps, _connectPasswordStdin;
- public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public CreateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"t=|title=",
"A title for the API key",
@@ -94,12 +91,15 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
_ => _connectPasswordStdin = true);
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = await TryConnectAsync();
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+
+ var connection = await TryConnectAsync(config);
if (connection == null)
return 1;
@@ -149,19 +149,21 @@ protected override async Task Run()
apiKey = await connection.ApiKeys.AddAsync(apiKey);
- if (_token == null && !_output.Json)
+ var output = _output.GetOutputFormat(config);
+
+ if (_token == null && !output.Json)
{
Console.WriteLine(apiKey.Token);
}
else
{
- _output.WriteEntity(apiKey);
+ output.WriteEntity(apiKey);
}
return 0;
}
- async Task TryConnectAsync()
+ async Task TryConnectAsync(SeqCliConfig config)
{
SeqConnection connection;
if (_connectUsername != null)
@@ -183,13 +185,13 @@ protected override async Task Run()
_connectPassword = await Console.In.ReadLineAsync();
}
- var (url, _) = _connectionFactory.GetConnectionDetails(_connection);
+ var (url, _) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
connection = new SeqConnection(url);
await connection.Users.LoginAsync(_connectUsername, _connectPassword ?? "");
}
else
{
- connection = _connectionFactory.Connect(_connection);
+ connection = SeqConnectionFactory.Connect(_connection, config);
}
return connection;
diff --git a/src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs b/src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs
index 5f07cb28..9a7d387a 100644
--- a/src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs
@@ -24,32 +24,30 @@ namespace SeqCli.Cli.Commands.ApiKey;
[Command("apikey", "list", "List available API keys", Example="seqcli apikey list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
+ readonly StoragePathFeature _storagePath;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("API key", "list"));
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var list = _entityIdentity.Id != null ?
new[] { await connection.ApiKeys.FindAsync(_entityIdentity.Id) } :
(await connection.ApiKeys.ListAsync())
.Where(ak => _entityIdentity.Title == null || _entityIdentity.Title == ak.Title);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/ApiKey/RemoveCommand.cs b/src/SeqCli/Cli/Commands/ApiKey/RemoveCommand.cs
index 2cac207d..d809ec7e 100644
--- a/src/SeqCli/Cli/Commands/ApiKey/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/ApiKey/RemoveCommand.cs
@@ -16,6 +16,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -25,17 +26,15 @@ namespace SeqCli.Cli.Commands.ApiKey;
Example="seqcli apikey remove -t 'Test API Key'")]
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly ConnectionFeature _connection;
+ readonly StoragePathFeature _storagePath;
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("API key", "remove"));
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -46,10 +45,11 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var toRemove = _entityIdentity.Id != null ?
- new[] {await connection.ApiKeys.FindAsync(_entityIdentity.Id)} :
+ var toRemove = _entityIdentity.Id != null ? [await connection.ApiKeys.FindAsync(_entityIdentity.Id)]
+ :
(await connection.ApiKeys.ListAsync())
.Where(ak => _entityIdentity.Title == ak.Title)
.ToArray();
diff --git a/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs b/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs
index b8ecff35..46a77309 100644
--- a/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs
+++ b/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs
@@ -20,6 +20,6 @@ namespace SeqCli.Cli.Commands.ApiKey;
[Command("apikey", "update",
"Update an existing API key",
Example="seqcli apikey update --json '{...}'")]
-class UpdateCommand(SeqConnectionFactory connectionFactory):
- Shared.UpdateCommand(connectionFactory, "apikey", nameof(SeqConnection.ApiKeys), "API key");
+class UpdateCommand():
+ Shared.UpdateCommand("apikey", nameof(SeqConnection.ApiKeys), "API key");
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/App/DefineCommand.cs b/src/SeqCli/Cli/Commands/App/DefineCommand.cs
index 16214070..56909bbb 100644
--- a/src/SeqCli/Cli/Commands/App/DefineCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/DefineCommand.cs
@@ -16,6 +16,7 @@
using System.Threading.Tasks;
using SeqCli.Apps;
using SeqCli.Apps.Definitions;
+using SeqCli.Cli.Features;
using SeqCli.Util;
namespace SeqCli.Cli.Commands.App;
diff --git a/src/SeqCli/Cli/Commands/App/InstallCommand.cs b/src/SeqCli/Cli/Commands/App/InstallCommand.cs
index 94d98482..4d284b29 100644
--- a/src/SeqCli/Cli/Commands/App/InstallCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/InstallCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -29,17 +29,14 @@ namespace SeqCli.Cli.Commands.App;
// ReSharper disable once UnusedType.Global
class InstallCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _packageId, _version, _feedId;
- public InstallCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public InstallCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"package-id=",
"The package id of the app to install",
@@ -56,7 +53,8 @@ public InstallCommand(SeqConnectionFactory connectionFactory, SeqCliConfig confi
feedId => _feedId = ArgumentString.Normalize(feedId));
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -67,7 +65,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var feedId = _feedId;
if (feedId == null)
@@ -83,7 +82,7 @@ protected override async Task Run()
}
var app = await connection.Apps.InstallPackageAsync(feedId, _packageId, _version);
- _output.WriteEntity(app);
+ _output.GetOutputFormat(config).WriteEntity(app);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/App/ListCommand.cs b/src/SeqCli/Cli/Commands/App/ListCommand.cs
index 3bd47736..166f3bc5 100644
--- a/src/SeqCli/Cli/Commands/App/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/ListCommand.cs
@@ -10,20 +10,16 @@ namespace SeqCli.Cli.Commands.App;
[Command("app", "list", "List installed app packages", Example="seqcli app list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
string? _title, _id;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? PackageId => string.IsNullOrWhiteSpace(_title) ? null : _title.Trim();
string? Id => string.IsNullOrWhiteSpace(_id) ? null : _id.Trim();
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"package-id=",
"The package id of the app(s) to list",
@@ -34,7 +30,8 @@ public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
"The id of a single app to list",
t => _id = t);
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
@@ -42,18 +39,19 @@ protected override async Task Run()
{
if (PackageId != null && Id != null)
{
- ShowUsageErrors(new[] {"Only one of either `package-id` or `id` can be specified"});
+ ShowUsageErrors(["Only one of either `package-id` or `id` can be specified"]);
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = Id != null ?
- new[] { await connection.Apps.FindAsync(Id) } :
+ var list = Id != null ? [await connection.Apps.FindAsync(Id)]
+ :
(await connection.Apps.ListAsync())
.Where(ak => PackageId == null || PackageId == ak.Package.PackageId);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/App/RunCommand.cs b/src/SeqCli/Cli/Commands/App/RunCommand.cs
index a680fd67..a3590f46 100644
--- a/src/SeqCli/Cli/Commands/App/RunCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/RunCommand.cs
@@ -16,6 +16,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SeqCli.Apps.Hosting;
+using SeqCli.Cli.Features;
using SeqCli.Config;
using SeqCli.Util;
@@ -39,10 +40,10 @@ class RunCommand : Command
readonly Dictionary _settings = new();
- public RunCommand(SeqCliConfig config)
+ public RunCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _serverUrl = config.Connection.ServerUrl;
+ // The usual `--storage` argument is not supported on this command (see notes on `--storage` arg below).
+ _serverUrl = RuntimeConfigurationLoader.Load(new StoragePathFeature()).Connection.ServerUrl;
Options.Add(
"d=|directory=",
@@ -64,6 +65,8 @@ public RunCommand(SeqCliConfig config)
_settings.Add(name, valueText ?? "");
});
+ // Important note, this conflicts with the `--storage` argument accepted by the majority of other commands; changing
+ // this requires an update to Seq, which uses this command for hosting .NET apps.
Options.Add(
"storage=",
"A directory in which app-specific data can be stored; defaults to the current directory",
diff --git a/src/SeqCli/Cli/Commands/App/UninstallCommand.cs b/src/SeqCli/Cli/Commands/App/UninstallCommand.cs
index c9d275a9..03f40b02 100644
--- a/src/SeqCli/Cli/Commands/App/UninstallCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/UninstallCommand.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using SeqCli.Util;
using Serilog;
@@ -13,15 +14,12 @@ namespace SeqCli.Cli.Commands.App;
// ReSharper disable once UnusedType.Global
class UninstallCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
string? _packageId, _id;
readonly ConnectionFeature _connection;
-
- public UninstallCommand(SeqConnectionFactory connectionFactory)
+ readonly StoragePathFeature _storagePath;
+
+ public UninstallCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"package-id=",
"The package id of the app package to uninstall",
@@ -33,6 +31,7 @@ public UninstallCommand(SeqConnectionFactory connectionFactory)
t => _id = ArgumentString.Normalize(t));
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -43,7 +42,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var toRemove = _id != null ? [await connection.Apps.FindAsync(_id)]
: (await connection.Apps.ListAsync())
diff --git a/src/SeqCli/Cli/Commands/App/UpdateCommand.cs b/src/SeqCli/Cli/Commands/App/UpdateCommand.cs
index bf7fb40f..4c30d62b 100644
--- a/src/SeqCli/Cli/Commands/App/UpdateCommand.cs
+++ b/src/SeqCli/Cli/Commands/App/UpdateCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -28,18 +28,15 @@ namespace SeqCli.Cli.Commands.App;
// ReSharper disable once UnusedType.Global
class UpdateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _id, _name, _version;
bool _all, _force;
- public UpdateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public UpdateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single installed app to update",
@@ -67,7 +64,8 @@ public UpdateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
_ => _force = true);
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -90,8 +88,10 @@ protected override async Task Run()
Log.Error("One of `id`, `name`, or `all` must be specified");
return 1;
}
-
- var connection = _connectionFactory.Connect(_connection);
+
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
+ var output = _output.GetOutputFormat(config);
var apps = await connection.Apps.ListAsync();
foreach (var app in apps)
@@ -99,7 +99,7 @@ protected override async Task Run()
if (_all || app.Id == _id || _name != null && _name.Equals(app.Name, StringComparison.OrdinalIgnoreCase))
{
var updated = await connection.Apps.UpdatePackageAsync(app, _version, _force);
- _output.WriteEntity(updated);
+ output.WriteEntity(updated);
}
}
diff --git a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs
index 5b71c2cc..af5aa731 100644
--- a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs
@@ -16,20 +16,17 @@ namespace SeqCli.Cli.Commands.AppInstance;
Example = "seqcli appinstance create -t 'Email Ops' --app hostedapp-314159 -p To=ops@example.com")]
class CreateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _title, _appId, _streamIncomingEventsSignal;
readonly Dictionary _settings = new();
readonly List _overridable = new();
bool _streamIncomingEvents;
- public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public CreateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"t=|title=",
"A title for the app instance",
@@ -70,12 +67,14 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
s => _overridable.Add(s));
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
AppInstanceEntity instance = await connection.AppInstances.TemplateAsync(_appId)!;
@@ -112,7 +111,7 @@ bool ValidateSettingName(string settingName)
instance = await connection.AppInstances.AddAsync(instance);
- _output.WriteEntity(instance);
+ _output.GetOutputFormat(config).WriteEntity(instance);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/AppInstance/ListCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/ListCommand.cs
index b9f9335e..2e9e2c45 100644
--- a/src/SeqCli/Cli/Commands/AppInstance/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/AppInstance/ListCommand.cs
@@ -10,32 +10,30 @@ namespace SeqCli.Cli.Commands.AppInstance;
[Command("appinstance", "list", "List instances of installed apps", Example="seqcli appinstance list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ readonly StoragePathFeature _storagePath;
+
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("app instance", "list"));
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = _entityIdentity.Id != null ?
- new[] { await connection.AppInstances.FindAsync(_entityIdentity.Id) } :
+ var list = _entityIdentity.Id != null ? [await connection.AppInstances.FindAsync(_entityIdentity.Id)]
+ :
(await connection.AppInstances.ListAsync())
.Where(d => _entityIdentity.Title == null || _entityIdentity.Title == d.Title);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/AppInstance/RemoveCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/RemoveCommand.cs
index 76e90915..33da86b2 100644
--- a/src/SeqCli/Cli/Commands/AppInstance/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/AppInstance/RemoveCommand.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -11,17 +12,15 @@ namespace SeqCli.Cli.Commands.AppInstance;
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly ConnectionFeature _connection;
-
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ readonly StoragePathFeature _storagePath;
+
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("app instance", "remove"));
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -32,10 +31,11 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var toRemove = _entityIdentity.Id != null ?
- new[] {await connection.AppInstances.FindAsync(_entityIdentity.Id)} :
+ var toRemove = _entityIdentity.Id != null ? [await connection.AppInstances.FindAsync(_entityIdentity.Id)]
+ :
(await connection.AppInstances.ListAsync())
.Where(ak => _entityIdentity.Title == ak.Title)
.ToArray();
diff --git a/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs
index f46d7760..22e44af1 100644
--- a/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs
+++ b/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs
@@ -20,6 +20,6 @@ namespace SeqCli.Cli.Commands.AppInstance;
[Command("appinstance", "update",
"Update an existing app instance",
Example="seqcli appinstance update --json '{...}'")]
-class UpdateCommand(SeqConnectionFactory connectionFactory):
- Shared.UpdateCommand(connectionFactory, "appinstance", nameof(SeqConnection.AppInstances), "app instance");
+class UpdateCommand():
+ Shared.UpdateCommand("appinstance", nameof(SeqConnection.AppInstances), "app instance");
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/Bench/BenchCasesCollection.cs b/src/SeqCli/Cli/Commands/Bench/BenchCasesCollection.cs
index 441b6ae0..a47c8206 100644
--- a/src/SeqCli/Cli/Commands/Bench/BenchCasesCollection.cs
+++ b/src/SeqCli/Cli/Commands/Bench/BenchCasesCollection.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/SeqCli/Cli/Commands/Bench/BenchCommand.cs b/src/SeqCli/Cli/Commands/Bench/BenchCommand.cs
index 606ac7a2..596bbbe4 100644
--- a/src/SeqCli/Cli/Commands/Bench/BenchCommand.cs
+++ b/src/SeqCli/Cli/Commands/Bench/BenchCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
using Seq.Api.Model.Data;
using Seq.Api.Model.Signals;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using SeqCli.Sample.Loader;
using SeqCli.Util;
@@ -65,11 +66,11 @@ namespace SeqCli.Cli.Commands.Bench;
[Command("bench", @"Measure query performance")]
class BenchCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
int _runs = 10;
readonly ConnectionFeature _connection;
readonly DateRangeFeature _range;
readonly TimeoutFeature _timeout;
+ readonly StoragePathFeature _storagePath;
string _cases = "";
string _reportingServerUrl = "";
string _reportingServerApiKey = "";
@@ -77,9 +78,8 @@ class BenchCommand : Command
bool _withIngestion = false;
bool _withQueries = false;
- public BenchCommand(SeqConnectionFactory connectionFactory)
+ public BenchCommand()
{
- _connectionFactory = connectionFactory;
Options.Add("r|runs=", "The number of runs to execute; the default is 10", r => _runs = int.Parse(r));
Options.Add(
@@ -111,6 +111,8 @@ public BenchCommand(SeqConnectionFactory connectionFactory)
"with-queries",
"Should the benchmark include querying Seq",
_ => _withQueries = true);
+
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -123,8 +125,9 @@ protected override async Task Run()
try
{
- var (_, apiKey) = _connectionFactory.GetConnectionDetails(_connection);
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var (_, apiKey) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
var seqVersion = (await connection.Client.GetRootAsync()).Version;
await using var reportingLogger = BuildReportingLogger();
@@ -159,9 +162,9 @@ protected override async Task Run()
if (!_withQueries)
{
- int benchDurationMs = 120_000;
- await Task.Delay(benchDurationMs);
- cancellationTokenSource.Cancel();
+ const int benchDurationMs = 120_000;
+ await Task.Delay(benchDurationMs, cancellationToken);
+ await cancellationTokenSource.CancelAsync();
var response = await connection.Data.QueryAsync(
"select count(*) from stream group by time(1s)",
@@ -199,7 +202,7 @@ protected override async Task Run()
{
var collectedTimings = await QueryBenchmark(reportingLogger, runId, connection, seqVersion, timeout);
collectedTimings.LogSummary(_description);
- cancellationTokenSource.Cancel();
+ await cancellationTokenSource.CancelAsync();
}
}
@@ -212,7 +215,7 @@ protected override async Task Run()
}
}
- async Task IngestionBenchmark(Logger reportingLogger, string runId, SeqConnection connection, string? apiKey,
+ static async Task IngestionBenchmark(Logger reportingLogger, string runId, SeqConnection connection, string? apiKey,
string seqVersion, bool isQueryBench, CancellationToken cancellationToken = default)
{
reportingLogger.Information(
@@ -224,7 +227,7 @@ async Task IngestionBenchmark(Logger reportingLogger, string runId, SeqConnectio
var simulationTasks = Enumerable.Range(1, 500)
.Select(i => Simulation.RunAsync(connection, apiKey, 10000, echoToStdout: false, cancellationToken))
.ToArray();
- await Task.Delay(20_000); // how long to ingest before beginning queries
+ await Task.Delay(20_000, cancellationToken); // how long to ingest before beginning queries
}
else
{
@@ -245,7 +248,7 @@ async Task QueryBenchmark(Logger reportingLogger, string r
foreach (var c in cases.Cases.OrderBy(c => c.Id)
- .Concat(new [] { QueryBenchRunResults.FINAL_COUNT_CASE }))
+ .Concat([QueryBenchRunResults.FINAL_COUNT_CASE]))
{
var timings = new QueryBenchCaseTimings(c);
queryBenchRunResults.Add(timings);
diff --git a/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs b/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs
index ce31c26e..55bee6d2 100644
--- a/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs
+++ b/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/SeqCli/Cli/Commands/Bench/QueryBenchCaseTimings.cs b/src/SeqCli/Cli/Commands/Bench/QueryBenchCaseTimings.cs
index a3bc8834..be8cd5ec 100644
--- a/src/SeqCli/Cli/Commands/Bench/QueryBenchCaseTimings.cs
+++ b/src/SeqCli/Cli/Commands/Bench/QueryBenchCaseTimings.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/SeqCli/Cli/Commands/Cluster/HealthCommand.cs b/src/SeqCli/Cli/Commands/Cluster/HealthCommand.cs
index c2888415..a868cb52 100644
--- a/src/SeqCli/Cli/Commands/Cluster/HealthCommand.cs
+++ b/src/SeqCli/Cli/Commands/Cluster/HealthCommand.cs
@@ -31,38 +31,37 @@ namespace SeqCli.Cli.Commands.Cluster;
Example = "seqcli cluster health -s https://seq.example.com --wait-until-healthy")]
class HealthCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
readonly TimeoutFeature _timeout;
readonly WaitUntilHealthyFeature _waitUntilHealthy;
+ readonly StoragePathFeature _storagePath;
- public HealthCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig outputConfig)
+ public HealthCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_waitUntilHealthy = Enable(new WaitUntilHealthyFeature("cluster"));
_timeout = Enable(new TimeoutFeature());
- _output = Enable(new OutputFormatFeature(outputConfig));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
if (_waitUntilHealthy.ShouldWait)
{
- return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30));
+ return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30), _output.GetOutputFormat(config));
}
- return await RunOnce(connection);
+ return await RunOnce(connection, _output.GetOutputFormat(config));
}
- async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
+ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout, OutputFormat outputFormat)
{
using var ct = new CancellationTokenSource(timeout);
@@ -78,7 +77,7 @@ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
{
try
{
- if (await RunOnce(connection) == 0)
+ if (await RunOnce(connection, outputFormat) == 0)
{
return 0;
}
@@ -98,13 +97,13 @@ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
}
}
- async Task RunOnce(SeqConnection connection)
+ static async Task RunOnce(SeqConnection connection, OutputFormat output)
{
var health = await connection.Cluster.CheckHealthAsync();
- if (_output.Json)
+ if (output.Json)
{
- _output.WriteObject(health);
+ output.WriteObject(health);
} else if (!string.IsNullOrWhiteSpace(health.Description)) {
Console.WriteLine($"{health.Status}: {health.Description}");
} else {
diff --git a/src/SeqCli/Cli/Commands/ConfigCommand.cs b/src/SeqCli/Cli/Commands/ConfigCommand.cs
index c4b1142b..08249429 100644
--- a/src/SeqCli/Cli/Commands/ConfigCommand.cs
+++ b/src/SeqCli/Cli/Commands/ConfigCommand.cs
@@ -13,12 +13,8 @@
// limitations under the License.
using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
using System.Threading.Tasks;
+using SeqCli.Cli.Features;
using SeqCli.Config;
using SeqCli.Util;
using Serilog;
@@ -30,12 +26,14 @@ class ConfigCommand : Command
{
string? _key, _value;
bool _clear;
+ readonly StoragePathFeature _storagePath;
public ConfigCommand()
{
Options.Add("k|key=", "The field, for example `connection.serverUrl`", k => _key = k);
Options.Add("v|value=", "The field value; if not specified, the command will print the current value", v => _value = v);
Options.Add("c|clear", "Clear the field", _ => _clear = true);
+ _storagePath = Enable();
}
protected override Task Run()
@@ -44,7 +42,7 @@ protected override Task Run()
try
{
- var config = SeqCliConfig.ReadFromFile(RuntimeConfigurationLoader.DefaultConfigFilename);
+ var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
if (_key != null)
{
@@ -52,13 +50,13 @@ protected override Task Run()
{
verb = "clear";
KeyValueSettings.Clear(config, _key);
- SeqCliConfig.WriteToFile(config, RuntimeConfigurationLoader.DefaultConfigFilename);
+ SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
}
else if (_value != null)
{
verb = "update";
KeyValueSettings.Set(config, _key, _value);
- SeqCliConfig.WriteToFile(config, RuntimeConfigurationLoader.DefaultConfigFilename);
+ SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
}
else
{
diff --git a/src/SeqCli/Cli/Commands/Dashboard/ListCommand.cs b/src/SeqCli/Cli/Commands/Dashboard/ListCommand.cs
index 1325511c..087f9fc4 100644
--- a/src/SeqCli/Cli/Commands/Dashboard/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/Dashboard/ListCommand.cs
@@ -24,34 +24,32 @@ namespace SeqCli.Cli.Commands.Dashboard;
[Command("dashboard", "list", "List dashboards", Example="seqcli dashboard list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly EntityOwnerFeature _entityOwner;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ readonly StoragePathFeature _storagePath;
+
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("dashboard", "list"));
_entityOwner = Enable(new EntityOwnerFeature("dashboard", "list", "listed", _entityIdentity));
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = _entityIdentity.Id != null ?
- new[] { await connection.Dashboards.FindAsync(_entityIdentity.Id) } :
+ var list = _entityIdentity.Id != null ? [await connection.Dashboards.FindAsync(_entityIdentity.Id)]
+ :
(await connection.Dashboards.ListAsync(ownerId: _entityOwner.OwnerId, shared: _entityOwner.IncludeShared))
.Where(d => _entityIdentity.Title == null || _entityIdentity.Title == d.Title);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/Dashboard/RemoveCommand.cs b/src/SeqCli/Cli/Commands/Dashboard/RemoveCommand.cs
index b8de3709..61022b26 100644
--- a/src/SeqCli/Cli/Commands/Dashboard/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/Dashboard/RemoveCommand.cs
@@ -16,6 +16,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -25,19 +26,17 @@ namespace SeqCli.Cli.Commands.Dashboard;
Example="seqcli dashboard remove -i dashboard-159")]
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly EntityIdentityFeature _entityIdentity;
readonly EntityOwnerFeature _entityOwner;
readonly ConnectionFeature _connection;
-
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ readonly StoragePathFeature _storagePath;
+
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_entityIdentity = Enable(new EntityIdentityFeature("dashboard", "remove"));
_entityOwner = Enable(new EntityOwnerFeature("dashboard", "remove", "removed", _entityIdentity));
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -48,10 +47,11 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var toRemove = _entityIdentity.Id != null ?
- new[] { await connection.Dashboards.FindAsync(_entityIdentity.Id) } :
+ var toRemove = _entityIdentity.Id != null ? [await connection.Dashboards.FindAsync(_entityIdentity.Id)]
+ :
(await connection.Dashboards.ListAsync(ownerId: _entityOwner.OwnerId, shared: _entityOwner.IncludeShared))
.Where(dashboard => _entityIdentity.Title == dashboard.Title)
.ToArray();
diff --git a/src/SeqCli/Cli/Commands/Dashboard/RenderCommand.cs b/src/SeqCli/Cli/Commands/Dashboard/RenderCommand.cs
index 83c4150d..652066a0 100644
--- a/src/SeqCli/Cli/Commands/Dashboard/RenderCommand.cs
+++ b/src/SeqCli/Cli/Commands/Dashboard/RenderCommand.cs
@@ -33,21 +33,17 @@ class RenderCommand : Command
{
const int MaximumReturnedHitRows = 10000;
- readonly SeqConnectionFactory _connectionFactory;
-
readonly DateRangeFeature _range;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
readonly SignalExpressionFeature _signal;
readonly TimeoutFeature _timeout;
-
+ readonly StoragePathFeature _storagePath;
+
string? _id, _lastDuration, _intervalDuration, _chartTitle;
- public RenderCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public RenderCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single dashboard to render",
@@ -63,13 +59,15 @@ public RenderCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
_range = Enable();
_signal = Enable();
_timeout = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
if (_id == null)
{
@@ -158,8 +156,9 @@ protected override async Task Run()
var q = BuildSqlQuery(query, rangeStart, rangeEnd, timeGrouping);
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
-
- if (_output.Json)
+
+ var output = _output.GetOutputFormat(config);
+ if (output.Json)
{
var result = await connection.Data.QueryAsync(q, signal: signal, timeout: timeout);
@@ -169,7 +168,7 @@ protected override async Task Run()
else
{
var result = await connection.Data.QueryCsvAsync(q, signal: signal, timeout: timeout);
- _output.WriteCsv(result);
+ output.WriteCsv(result);
}
return 0;
diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs
index 5fc39086..d0760376 100644
--- a/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs
@@ -29,29 +29,28 @@ namespace SeqCli.Cli.Commands.ExpressionIndex;
Example = "seqcli expressionindex create --expression \"ServerName\"")]
class CreateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _expression;
- public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public CreateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"e=|expression=",
"The expression to index",
v => _expression = ArgumentString.Normalize(v));
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
if (string.IsNullOrEmpty(_expression))
{
@@ -63,7 +62,7 @@ protected override async Task Run()
index.Expression = _expression;
index = await connection.ExpressionIndexes.AddAsync(index);
- _output.WriteEntity(index);
+ _output.GetOutputFormat(config).WriteEntity(index);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs
index 6836d3f1..16b00cb8 100644
--- a/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs
@@ -9,33 +9,32 @@ namespace SeqCli.Cli.Commands.ExpressionIndex;
[Command("expressionindex", "list", "List expression indexes", Example="seqcli expressionindex list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
+ readonly StoragePathFeature _storagePath;
+
string? _id;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single expression index to list",
id => _id = id);
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var list = _id is not null
? [await connection.ExpressionIndexes.FindAsync(_id)]
: await connection.ExpressionIndexes.ListAsync();
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
}
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs
index c7ebee37..b00e7a41 100644
--- a/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System;
-using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -25,21 +24,19 @@ namespace SeqCli.Cli.Commands.ExpressionIndex;
Example = "seqcli expressionindex -i expressionindex-2529")]
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
+ readonly StoragePathFeature _storagePath;
string? _id;
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of an expression index to remove",
id => _id = id);
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -50,7 +47,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var toRemove = await connection.ExpressionIndexes.FindAsync(_id);
await connection.ExpressionIndexes.RemoveAsync(toRemove);
diff --git a/src/SeqCli/Cli/Commands/Feed/CreateCommand.cs b/src/SeqCli/Cli/Commands/Feed/CreateCommand.cs
index 28a891c0..9d4aea80 100644
--- a/src/SeqCli/Cli/Commands/Feed/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/Feed/CreateCommand.cs
@@ -26,18 +26,15 @@ namespace SeqCli.Cli.Commands.Feed;
Example = "seqcli feed create -n 'CI' --location=\"https://f.feedz.io/example/ci\" -u Seq --password-stdin")]
class CreateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _name, _location, _username, _password;
bool _passwordStdin;
- public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public CreateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"n=|name=",
"A unique name for the feed",
@@ -64,12 +61,14 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
_ => _passwordStdin = true);
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var feed = await connection.Feeds.TemplateAsync();
feed.Name = _name;
@@ -94,7 +93,7 @@ protected override async Task Run()
feed = await connection.Feeds.AddAsync(feed);
- _output.WriteEntity(feed);
+ _output.GetOutputFormat(config).WriteEntity(feed);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/Feed/ListCommand.cs b/src/SeqCli/Cli/Commands/Feed/ListCommand.cs
index 31e27116..a7d45220 100644
--- a/src/SeqCli/Cli/Commands/Feed/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/Feed/ListCommand.cs
@@ -24,18 +24,14 @@ namespace SeqCli.Cli.Commands.Feed;
[Command("feed", "list", "List NuGet feeds", Example="seqcli feed list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _name, _id;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"n=|name=",
"The name of the feed to list",
@@ -46,20 +42,22 @@ public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
"The id of a single feed to list",
id => _id = id);
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = _id != null ?
- new[] { await connection.Feeds.FindAsync(_id) } :
+ var list = _id != null ? [await connection.Feeds.FindAsync(_id)]
+ :
(await connection.Feeds.ListAsync())
.Where(f => _name == null || _name == f.Name);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/Feed/RemoveCommand.cs b/src/SeqCli/Cli/Commands/Feed/RemoveCommand.cs
index 4f0ce4b4..3180bfb5 100644
--- a/src/SeqCli/Cli/Commands/Feed/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/Feed/RemoveCommand.cs
@@ -16,6 +16,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -25,16 +26,13 @@ namespace SeqCli.Cli.Commands.Feed;
Example="seqcli feed remove -n CI")]
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
-
+ readonly StoragePathFeature _storagePath;
+
string? _name, _id;
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"n=|name=",
"The name of the feed to remove",
@@ -46,6 +44,7 @@ public RemoveCommand(SeqConnectionFactory connectionFactory)
id => _id = id);
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -56,10 +55,11 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var toRemove = _id != null ?
- new[] {await connection.Feeds.FindAsync(_id)} :
+ var toRemove = _id != null ? [await connection.Feeds.FindAsync(_id)]
+ :
(await connection.Feeds.ListAsync())
.Where(f => _name == f.Name)
.ToArray();
diff --git a/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs b/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs
index 1dd5d265..59ed98b3 100644
--- a/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs
+++ b/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs
@@ -20,6 +20,6 @@ namespace SeqCli.Cli.Commands.Feed;
[Command("feed", "update",
"Update an existing NuGet feed",
Example="seqcli feed update --json '{...}'")]
-class UpdateCommand(SeqConnectionFactory connectionFactory):
- Shared.UpdateCommand(connectionFactory, "feed", nameof(SeqConnection.Feeds), "NuGet feed");
+class UpdateCommand():
+ Shared.UpdateCommand("feed", nameof(SeqConnection.Feeds), "NuGet feed");
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs
new file mode 100644
index 00000000..03d6d32e
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs
@@ -0,0 +1,263 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using SeqCli.Cli.Features;
+using SeqCli.Config;
+using SeqCli.Forwarder.Cli.Features;
+using SeqCli.Forwarder.ServiceProcess;
+using SeqCli.Forwarder.Util;
+
+// ReSharper disable once ClassNeverInstantiated.Global
+
+namespace SeqCli.Cli.Commands.Forwarder
+{
+ [Command("forwarder", "install", "Install the forwarder as a Windows service", IsPreview = true)]
+ [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+ class InstallCommand : Command
+ {
+ readonly StoragePathFeature _storagePath;
+ readonly ServiceCredentialsFeature _serviceCredentials;
+ readonly ListenUriFeature _listenUri;
+
+ bool _setup;
+
+ public InstallCommand()
+ {
+ _storagePath = Enable();
+ _listenUri = Enable();
+ _serviceCredentials = Enable();
+
+ Options.Add(
+ "setup",
+ "Install and start the service only if it does not exist; otherwise reconfigure the binary location",
+ _ => _setup = true);
+ }
+
+ string ServiceUsername => _serviceCredentials.IsUsernameSpecified ? _serviceCredentials.Username : "NT AUTHORITY\\LocalService";
+
+ protected override Task Run()
+ {
+ try
+ {
+ if (!_setup)
+ {
+ Install();
+ return Task.FromResult(0);
+ }
+
+ var exit = Setup();
+ if (exit == 0)
+ {
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("Setup completed successfully.");
+ Console.ResetColor();
+ }
+ return Task.FromResult(exit);
+ }
+ catch (DirectoryNotFoundException dex)
+ {
+ Console.WriteLine("Could not install the service, directory not found: " + dex.Message);
+ return Task.FromResult(-1);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Could not install the service: " + ex.Message);
+ return Task.FromResult(-1);
+ }
+ }
+
+ int Setup()
+ {
+ ServiceController controller;
+ try
+ {
+ Console.WriteLine($"Checking the status of the {SeqCliForwarderWindowsService.WindowsServiceName} service...");
+
+ controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+ Console.WriteLine("Status is {0}", controller.Status);
+ }
+ catch (InvalidOperationException)
+ {
+ Install();
+ var controller2 = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+ return Start(controller2);
+ }
+
+ Console.WriteLine("Service is installed; checking path and dependency configuration...");
+ Reconfigure(controller);
+
+ return controller.Status != ServiceControllerStatus.Running ? Start(controller) : 0;
+ }
+
+ static void Reconfigure(ServiceController controller)
+ {
+ var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
+ if (0 != CaptiveProcess.Run(sc, "config \"" + controller.ServiceName + "\" depend= Winmgmt/Tcpip/CryptSvc", Console.WriteLine, Console.WriteLine))
+ Console.WriteLine("Could not reconfigure service dependencies; ignoring.");
+
+ if (!ServiceConfiguration.GetServiceBinaryPath(controller, out var path))
+ return;
+
+ var current = "\"" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Program.BinaryName) + "\"";
+ if (path.StartsWith(current))
+ return;
+
+ var seqRun = path.IndexOf(Program.BinaryName + "\" run", StringComparison.OrdinalIgnoreCase);
+ if (seqRun == -1)
+ {
+ Console.WriteLine("Current binary path is an unrecognized format.");
+ return;
+ }
+
+ Console.WriteLine("Existing service binary path is: {0}", path);
+
+ var trimmed = path.Substring((seqRun + Program.BinaryName + " ").Length);
+ var newPath = current + trimmed;
+ Console.WriteLine("Updating service binary path configuration to: {0}", newPath);
+
+ var escaped = newPath.Replace("\"", "\\\"");
+ if (0 != CaptiveProcess.Run(sc, "config \"" + controller.ServiceName + "\" binPath= \"" + escaped + "\"", Console.WriteLine, Console.WriteLine))
+ {
+ Console.WriteLine("Could not reconfigure service path; ignoring.");
+ return;
+ }
+
+ Console.WriteLine("Service binary path reconfigured successfully.");
+ }
+
+ static int Start(ServiceController controller)
+ {
+ controller.Start();
+
+ if (controller.Status != ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Waiting up to 60 seconds for the service to start (currently: " + controller.Status + ")...");
+ controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60));
+ }
+
+ if (controller.Status == ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Started.");
+ return 0;
+ }
+
+ Console.WriteLine("The service hasn't started successfully.");
+ return -1;
+ }
+
+ [DllImport("shlwapi.dll")]
+ static extern bool PathIsNetworkPath(string pszPath);
+
+ void Install()
+ {
+ Console.WriteLine("Installing service...");
+
+ if (PathIsNetworkPath(_storagePath.StorageRootPath))
+ throw new ArgumentException("Seq requires a local (or SAN) storage location; network shares are not supported.");
+
+ Console.WriteLine($"Updating the configuration in {_storagePath.ConfigFilePath}...");
+ var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
+
+ if (!string.IsNullOrEmpty(_listenUri.ListenUri))
+ {
+ config.Forwarder.Api.ListenUri = _listenUri.ListenUri;
+ SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
+ }
+
+ if (_serviceCredentials.IsUsernameSpecified)
+ {
+ if (!_serviceCredentials.IsPasswordSpecified)
+ throw new ArgumentException(
+ "If a service user account is specified, a password for the account must also be specified.");
+
+ // https://technet.microsoft.com/en-us/library/cc794944(v=ws.10).aspx
+ Console.WriteLine($"Ensuring {_serviceCredentials.Username} is granted 'Log on as a Service' rights...");
+ AccountRightsHelper.EnsureServiceLogOnRights(_serviceCredentials.Username);
+ }
+
+ Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.StorageRootPath}...");
+ GiveFullControl(_storagePath.StorageRootPath);
+
+ Console.WriteLine($"Granting {ServiceUsername} rights to {_storagePath.InternalLogPath}...");
+ GiveFullControl(_storagePath.InternalLogPath);
+
+ var listenUri = MakeListenUriReservationPattern(config.Forwarder.Api.ListenUri);
+ Console.WriteLine($"Adding URL reservation at {listenUri} for {ServiceUsername}...");
+ var netshResult = CaptiveProcess.Run("netsh", $"http add urlacl url={listenUri} user=\"{ServiceUsername}\"", Console.WriteLine, Console.WriteLine);
+ if (netshResult != 0)
+ Console.WriteLine($"Could not add URL reservation for {listenUri}: `netsh` returned {netshResult}; ignoring");
+
+ var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, Program.BinaryName);
+ var forwarderRunCmdline = $"\"{exePath}\" run --storage=\"{_storagePath.StorageRootPath}\"";
+
+ var binPath = forwarderRunCmdline.Replace("\"", "\\\"");
+
+ var scCmdline = "create \"" + SeqCliForwarderWindowsService.WindowsServiceName + "\"" +
+ " binPath= \"" + binPath + "\"" +
+ " start= auto" +
+ " depend= Winmgmt/Tcpip/CryptSvc";
+
+ if (_serviceCredentials.IsUsernameSpecified)
+ scCmdline += $" obj= {_serviceCredentials.Username} password= {_serviceCredentials.Password}";
+
+ var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
+ if (0 != CaptiveProcess.Run(sc, scCmdline, Console.WriteLine, Console.WriteLine))
+ {
+ throw new ArgumentException("Service setup failed");
+ }
+
+ Console.WriteLine("Setting service restart policy...");
+ if (0 != CaptiveProcess.Run(sc, $"failure \"{SeqCliForwarderWindowsService.WindowsServiceName}\" actions= restart/60000/restart/60000/restart/60000// reset= 600000", Console.WriteLine, Console.WriteLine))
+ Console.WriteLine("Could not set service restart policy; ignoring");
+ Console.WriteLine("Setting service description...");
+ if (0 != CaptiveProcess.Run(sc, $"description \"{SeqCliForwarderWindowsService.WindowsServiceName}\" \"Durable storage and forwarding of application log events\"", Console.WriteLine, Console.WriteLine))
+ Console.WriteLine("Could not set service description; ignoring");
+
+ Console.WriteLine("Service installed successfully.");
+ }
+
+ void GiveFullControl(string target)
+ {
+ if (target == null) throw new ArgumentNullException(nameof(target));
+
+ if (!Directory.Exists(target))
+ Directory.CreateDirectory(target);
+
+ var storageInfo = new DirectoryInfo(target);
+ var storageAccessControl = storageInfo.GetAccessControl();
+ storageAccessControl.AddAccessRule(new FileSystemAccessRule(ServiceUsername,
+ FileSystemRights.FullControl, AccessControlType.Allow));
+ storageInfo.SetAccessControl(storageAccessControl);
+ }
+
+ static string MakeListenUriReservationPattern(string uri)
+ {
+ var listenUri = uri.Replace("localhost", "+");
+ if (!listenUri.EndsWith("/"))
+ listenUri += "/";
+ return listenUri;
+ }
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs
new file mode 100644
index 00000000..ef028c90
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs
@@ -0,0 +1,84 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using SeqCli.Forwarder.ServiceProcess;
+
+// ReSharper disable UnusedType.Global
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "restart", "Restart the forwarder Windows service", IsPreview = true)]
+[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+class RestartCommand : Command
+{
+ protected override Task Run()
+ {
+ try
+ {
+ var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+
+ if (controller.Status != ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("Stopping {0}...", controller.ServiceName);
+ controller.Stop();
+
+ if (controller.Status != ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("Waiting up to 60 seconds for the service to stop (currently: " +
+ controller.Status + ")...");
+ controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
+ }
+
+ if (controller.Status != ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("The service hasn't stopped successfully.");
+ return Task.FromResult(-1);
+ }
+ }
+
+ Console.WriteLine("Starting {0}...", controller.ServiceName);
+ controller.Start();
+
+ if (controller.Status != ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Waiting up to 15 seconds for the service to start (currently: " + controller.Status + ")...");
+ controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(15));
+ }
+
+ if (controller.Status == ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Started.");
+ return Task.FromResult(0);
+ }
+
+ Console.WriteLine("The service hasn't started successfully.");
+ return Task.FromResult(-1);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ Console.WriteLine(ex.InnerException.Message);
+ return Task.FromResult(1);
+ }
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs
new file mode 100644
index 00000000..d0ab573f
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs
@@ -0,0 +1,285 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Autofac;
+using Autofac.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using SeqCli.Cli.Features;
+using SeqCli.Config;
+using SeqCli.Connection;
+using SeqCli.Forwarder;
+using SeqCli.Forwarder.Util;
+using SeqCli.Forwarder.Web.Api;
+using SeqCli.Forwarder.Web.Host;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting.Compact;
+
+#if WINDOWS
+using System.Security.Cryptography.X509Certificates;
+using SeqCli.Forwarder.ServiceProcess;
+#endif
+
+// ReSharper disable UnusedType.Global
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "run", "Listen on an HTTP endpoint and forward ingested logs to Seq", IsPreview = true)]
+class RunCommand : Command
+{
+ readonly StoragePathFeature _storagePath;
+ readonly ListenUriFeature _listenUri;
+ readonly ConnectionFeature _connection;
+
+ bool _noLogo;
+
+ public RunCommand()
+ {
+ Options.Add("nologo", _ => _noLogo = true);
+ _listenUri = Enable();
+ _connection = Enable();
+ _storagePath = Enable();
+ }
+
+ protected override async Task Run(string[] unrecognized)
+ {
+ if (Environment.UserInteractive)
+ {
+ if (!_noLogo)
+ {
+ WriteBanner();
+ Console.WriteLine();
+ }
+
+ Console.WriteLine("Running as server; press Ctrl+C to exit.");
+ Console.WriteLine();
+ }
+
+ SeqCliConfig config;
+ try
+ {
+ config = RuntimeConfigurationLoader.Load(_storagePath);
+ }
+ catch (Exception ex)
+ {
+ await using var logger = CreateLogger(
+ LogEventLevel.Information,
+ _storagePath.InternalLogPath);
+
+ logger.Fatal(ex, "Failed to load configuration from {ConfigFilePath}", _storagePath.ConfigFilePath);
+ return 1;
+ }
+
+ var connection = SeqConnectionFactory.Connect(_connection, config);
+
+ // The API key is passed through separately because `SeqConnection` doesn't expose a batched ingestion
+ // mechanism and so we manually construct `HttpRequestMessage`s deeper in the stack. Nice feature gap to
+ // close at some point!
+ var (serverUrl, apiKey) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
+
+ Log.Logger = CreateLogger(
+ config.Forwarder.Diagnostics.InternalLoggingLevel,
+ _storagePath.InternalLogPath,
+ config.Forwarder.Diagnostics.InternalLogServerUri,
+ config.Forwarder.Diagnostics.InternalLogServerApiKey);
+
+ Log.Information("Loaded configuration from {ConfigFilePath}", _storagePath.ConfigFilePath);
+ Log.Information("Forwarding to {ServerUrl}", serverUrl);
+
+ var listenUri = _listenUri.ListenUri ?? config.Forwarder.Api.ListenUri;
+
+ try
+ {
+ ILifetimeScope? container = null;
+ var builder = WebApplication.CreateBuilder();
+ builder.WebHost.UseKestrel(options =>
+ {
+ options.AddServerHeader = false;
+ options.AllowSynchronousIO = true;
+ }).ConfigureKestrel((_, options) =>
+ {
+ var apiListenUri = new Uri(listenUri);
+
+ var ipAddress = apiListenUri.HostNameType switch
+ {
+ UriHostNameType.Basic => IPAddress.Any,
+ UriHostNameType.Dns => IPAddress.Any,
+ UriHostNameType.IPv4 => IPAddress.Parse(apiListenUri.Host),
+ UriHostNameType.IPv6 => IPAddress.Parse(apiListenUri.Host),
+ _ => throw new NotSupportedException($"Listen URI type `{apiListenUri.HostNameType}` is not supported.")
+ };
+
+ if (apiListenUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
+ {
+ options.Listen(ipAddress, apiListenUri.Port, listenOptions =>
+ {
+#if WINDOWS
+ listenOptions.UseHttps(StoreName.My, apiListenUri.Host,
+ location: StoreLocation.LocalMachine, allowInvalid: true);
+#else
+ listenOptions.UseHttps();
+#endif
+ });
+ }
+ else
+ {
+ options.Listen(ipAddress, apiListenUri.Port);
+ }
+ });
+
+ builder.Services.AddSerilog();
+
+ builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
+ .ConfigureContainer(containerBuilder =>
+ {
+ containerBuilder.RegisterBuildCallback(ls => container = ls);
+ containerBuilder.RegisterModule(new ForwarderModule(_storagePath.BufferPath, config, connection, apiKey));
+ });
+
+ await using var app = builder.Build();
+
+ if (container == null) throw new Exception("Host did not build container.");
+
+ foreach (var mapper in container.Resolve>())
+ {
+ mapper.MapEndpoints(app);
+ }
+
+ var service = container.Resolve(
+ new TypedParameter(typeof(IHost), app),
+ new NamedParameter("listenUri", listenUri));
+
+ var exit = ExecutionEnvironment.SupportsStandardIO
+ ? await RunStandardIOAsync(service, Console.Out)
+ : RunService(service);
+
+ Log.Information("Exiting with status code {StatusCode}", exit);
+
+ return exit;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Unhandled exception");
+ return -1;
+ }
+ finally
+ {
+ await Log.CloseAndFlushAsync();
+ }
+ }
+
+ [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+ // ReSharper disable once UnusedParameter.Local
+ static int RunService(ServerService service)
+ {
+#if WINDOWS
+ System.ServiceProcess.ServiceBase.Run([
+ new SeqCliForwarderWindowsService(service)
+ ]);
+ return 0;
+#else
+ throw new NotSupportedException("Windows services are not supported on this platform.");
+#endif
+ }
+
+ static async Task RunStandardIOAsync(ServerService service, TextWriter cout)
+ {
+ service.Start();
+
+ try
+ {
+ Console.TreatControlCAsInput = true;
+ var k = Console.ReadKey(true);
+ while (k.Key != ConsoleKey.C || !k.Modifiers.HasFlag(ConsoleModifiers.Control))
+ k = Console.ReadKey(true);
+
+ cout.WriteLine("Ctrl+C pressed; stopping...");
+ Console.TreatControlCAsInput = false;
+ }
+ catch (Exception ex)
+ {
+ Log.Debug(ex, "Console not attached, waiting for any input");
+ Console.Read();
+ }
+
+ await service.StopAsync();
+
+ return 0;
+ }
+
+ static void WriteBanner()
+ {
+ Write("─", ConsoleColor.DarkGray, 47);
+ Console.WriteLine();
+ Write(" SeqCli Forwarder", ConsoleColor.White);
+ Write(" ──", ConsoleColor.DarkGray);
+ Write(" © Datalust Pty Ltd and Contributors", ConsoleColor.Gray);
+ Console.WriteLine();
+ Write("─", ConsoleColor.DarkGray, 47);
+ Console.WriteLine();
+ }
+
+ static void Write(string s, ConsoleColor color, int repeats = 1)
+ {
+ Console.ForegroundColor = color;
+ for (var i = 0; i < repeats; ++i)
+ Console.Write(s);
+ Console.ResetColor();
+ }
+
+ static Logger CreateLogger(
+ LogEventLevel internalLoggingLevel,
+ string internalLogPath,
+ string? internalLogServerUri = null,
+ string? internalLogServerApiKey = null)
+ {
+ var loggerConfiguration = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .Enrich.WithProperty("MachineName", Environment.MachineName)
+ .Enrich.WithProperty("Application", "SeqCli Forwarder")
+ .MinimumLevel.Is(internalLoggingLevel)
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+ .WriteTo.File(
+ new RenderedCompactJsonFormatter(),
+ GetRollingLogFilePathFormat(internalLogPath),
+ rollingInterval: RollingInterval.Day,
+ fileSizeLimitBytes: 1024 * 1024);
+
+ if (Environment.UserInteractive)
+ loggerConfiguration.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information);
+
+ if (!string.IsNullOrWhiteSpace(internalLogServerUri))
+ loggerConfiguration.WriteTo.Seq(
+ internalLogServerUri,
+ apiKey: internalLogServerApiKey);
+
+ return loggerConfiguration.CreateLogger();
+ }
+
+ static string GetRollingLogFilePathFormat(string internalLogPath)
+ {
+ if (internalLogPath == null) throw new ArgumentNullException(nameof(internalLogPath));
+
+ return Path.Combine(internalLogPath, "seq-forwarder-.log");
+ }
+}
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs
new file mode 100644
index 00000000..0d6d0a0b
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs
@@ -0,0 +1,68 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using SeqCli.Forwarder.ServiceProcess;
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "start", "Start the forwarder Windows service", IsPreview = true)]
+[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+class StartCommand : Command
+{
+ protected override Task Run()
+ {
+ try
+ {
+ var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+ if (controller.Status != ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("Cannot start {0}, current status is: {1}", controller.ServiceName, controller.Status);
+ return Task.FromResult(-1);
+ }
+
+ Console.WriteLine("Starting {0}...", controller.ServiceName);
+ controller.Start();
+
+ if (controller.Status != ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Waiting up to 15 seconds for the service to start (currently: " + controller.Status + ")...");
+ controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(15));
+ }
+
+ if (controller.Status == ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Started.");
+ return Task.FromResult(0);
+ }
+
+ Console.WriteLine("The service hasn't started successfully.");
+ return Task.FromResult(-1);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ Console.WriteLine(ex.InnerException.Message);
+ return Task.FromResult(-1);
+ }
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs
new file mode 100644
index 00000000..f7733460
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs
@@ -0,0 +1,52 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using SeqCli.Forwarder.ServiceProcess;
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "status", "Show the status of the forwarder Windows service", IsPreview = true)]
+[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+class StatusCommand : Command
+{
+ protected override Task Run()
+ {
+ try
+ {
+ var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+ Console.WriteLine($"The {SeqCliForwarderWindowsService.WindowsServiceName} service is installed and {controller.Status.ToString().ToLowerInvariant()}.");
+ }
+ catch (InvalidOperationException)
+ {
+ Console.WriteLine($"The {SeqCliForwarderWindowsService.WindowsServiceName} service is not installed.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ Console.WriteLine(ex.InnerException.Message);
+ return Task.FromResult(1);
+ }
+
+ return Task.FromResult(1);
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs
new file mode 100644
index 00000000..0dc72a45
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs
@@ -0,0 +1,69 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using SeqCli.Forwarder.ServiceProcess;
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "stop", "Stop the forwarder Windows service", IsPreview = true)]
+[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
+class StopCommand : Command
+{
+ protected override Task Run()
+ {
+ try
+ {
+ var controller = new ServiceController(SeqCliForwarderWindowsService.WindowsServiceName);
+
+ if (controller.Status != ServiceControllerStatus.Running)
+ {
+ Console.WriteLine("Cannot stop {0}, current status is: {1}", controller.ServiceName, controller.Status);
+ return Task.FromResult(-1);
+ }
+
+ Console.WriteLine("Stopping {0}...", controller.ServiceName);
+ controller.Stop();
+
+ if (controller.Status != ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("Waiting up to 60 seconds for the service to stop (currently: " + controller.Status + ")...");
+ controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
+ }
+
+ if (controller.Status == ServiceControllerStatus.Stopped)
+ {
+ Console.WriteLine("Stopped.");
+ return Task.FromResult(0);
+ }
+
+ Console.WriteLine("The service hasn't stopped successfully.");
+ return Task.FromResult(-1);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ Console.WriteLine(ex.InnerException.Message);
+ return Task.FromResult(-1);
+ }
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/Forwarder/TruncateCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/TruncateCommand.cs
new file mode 100644
index 00000000..bf50a2ae
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/TruncateCommand.cs
@@ -0,0 +1,51 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Threading.Tasks;
+using SeqCli.Cli.Features;
+using Serilog;
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "truncate", "Empty the forwarder's persistent log buffer", IsPreview = true)]
+class TruncateCommand : Command
+{
+ readonly StoragePathFeature _storagePath;
+ readonly ConfirmFeature _confirm;
+
+ public TruncateCommand()
+ {
+ _storagePath = Enable();
+ _confirm = Enable();
+ }
+
+ protected override async Task Run(string[] args)
+ {
+ try
+ {
+ if (!_confirm.TryConfirm("All data in the forwarder's log buffer will be deleted. This cannot be undone."))
+ return 1;
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ await using var logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
+
+ logger.Fatal(ex, "Could not truncate log buffer");
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs
new file mode 100644
index 00000000..eb3299b1
--- /dev/null
+++ b/src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs
@@ -0,0 +1,50 @@
+// Copyright © Datalust Pty Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if WINDOWS
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using SeqCli.Forwarder.ServiceProcess;
+using SeqCli.Forwarder.Util;
+
+namespace SeqCli.Cli.Commands.Forwarder;
+
+[Command("forwarder", "uninstall", "Uninstall the forwarder Windows service", IsPreview = true)]
+class UninstallCommand : Command
+{
+ protected override Task Run()
+ {
+ try
+ {
+ Console.WriteLine("Uninstalling service...");
+
+ var sc = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
+ var exitCode = CaptiveProcess.Run(sc, $"delete \"{SeqCliForwarderWindowsService.WindowsServiceName}\"", Console.WriteLine, Console.WriteLine);
+ if (exitCode != 0)
+ throw new InvalidOperationException($"The `sc.exe delete` call failed with exit code {exitCode}.");
+
+ Console.WriteLine("Service uninstalled successfully.");
+ return Task.FromResult(0);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Could not uninstall the service: " + ex.Message);
+ return Task.FromResult(-1);
+ }
+ }
+}
+
+#endif
diff --git a/src/SeqCli/Cli/Commands/HelpCommand.cs b/src/SeqCli/Cli/Commands/HelpCommand.cs
index 026cfcac..3aab532b 100644
--- a/src/SeqCli/Cli/Commands/HelpCommand.cs
+++ b/src/SeqCli/Cli/Commands/HelpCommand.cs
@@ -18,23 +18,31 @@
using System.Reflection;
using System.Threading.Tasks;
using Autofac.Features.Metadata;
+using CommandList = System.Collections.Generic.List, SeqCli.Cli.CommandMetadata>>;
namespace SeqCli.Cli.Commands;
[Command("help", "Show information about available commands", Example = "seqcli help search")]
class HelpCommand : Command
{
- readonly List, CommandMetadata>> _orderedCommands;
- bool _markdown;
+ readonly IEnumerable, CommandMetadata>> _availableCommands;
+ bool _markdown, _pre;
public HelpCommand(IEnumerable, CommandMetadata>> availableCommands)
{
+ _availableCommands = availableCommands;
+ Options.Add("pre", "Show preview commands", _ => _pre = true);
Options.Add("m|markdown", "Generate markdown for use in documentation", _ => _markdown = true);
- _orderedCommands = availableCommands.OrderBy(c => c.Metadata.Name).ThenBy(c => c.Metadata.SubCommand).ToList();
}
protected override Task Run(string[] unrecognized)
{
+ var orderedCommands = _availableCommands
+ .Where(c => !c.Metadata.IsPreview || _pre)
+ .OrderBy(c => c.Metadata.Name)
+ .ThenBy(c => c.Metadata.SubCommand)
+ .ToList();
+
var ea = Assembly.GetEntryAssembly();
// ReSharper disable once PossibleNullReferenceException
var name = ea!.GetName().Name!;
@@ -44,7 +52,7 @@ protected override Task Run(string[] unrecognized)
if (unrecognized.Length != 0)
return base.Run(unrecognized);
- PrintMarkdownHelp(name);
+ PrintMarkdownHelp(name, orderedCommands);
return Task.FromResult(0);
}
@@ -53,7 +61,7 @@ protected override Task Run(string[] unrecognized)
{
topLevelCommand = unrecognized[0].ToLowerInvariant();
var subCommand = unrecognized.Length > 1 && !unrecognized[1].Contains("-") ? unrecognized[1] : null;
- var cmds = _orderedCommands.Where(c => c.Metadata.Name == topLevelCommand &&
+ var cmds = orderedCommands.Where(c => c.Metadata.Name == topLevelCommand &&
(subCommand == null || subCommand == c.Metadata.SubCommand)).ToArray();
if (cmds.Length == 1 && cmds[0].Metadata.SubCommand == subCommand)
@@ -79,15 +87,15 @@ protected override Task Run(string[] unrecognized)
}
}
- if (topLevelCommand != null && _orderedCommands.Any(a => a.Metadata.Name == topLevelCommand))
- PrintHelp(name, topLevelCommand);
+ if (topLevelCommand != null && orderedCommands.Any(a => a.Metadata.Name == topLevelCommand))
+ PrintHelp(name, topLevelCommand, orderedCommands);
else
- PrintHelp(name);
+ PrintHelp(name, orderedCommands);
return Task.FromResult(0);
}
- void PrintMarkdownHelp(string executableName)
+ static void PrintMarkdownHelp(string executableName, CommandList orderedCommands)
{
Console.WriteLine("## Commands");
Console.WriteLine();
@@ -101,7 +109,7 @@ void PrintMarkdownHelp(string executableName)
Console.WriteLine("Available commands:");
Console.WriteLine();
- foreach (var cmd in _orderedCommands.GroupBy(cmd => cmd.Metadata.Name).OrderBy(c => c.Key))
+ foreach (var cmd in orderedCommands.GroupBy(cmd => cmd.Metadata.Name).OrderBy(c => c.Key))
{
if (cmd.Count() == 1)
{
@@ -122,7 +130,7 @@ void PrintMarkdownHelp(string executableName)
}
Console.WriteLine();
- foreach (var cmd in _orderedCommands)
+ foreach (var cmd in orderedCommands)
{
if (cmd.Metadata.SubCommand != null)
Console.WriteLine($"### `{cmd.Metadata.Name} {cmd.Metadata.SubCommand}`");
@@ -166,14 +174,14 @@ void PrintMarkdownHelp(string executableName)
}
}
- void PrintHelp(string executableName)
+ static void PrintHelp(string executableName, CommandList orderedCommands)
{
Console.WriteLine($"Usage: {executableName} []");
Console.WriteLine();
Console.WriteLine("Available commands are:");
var printedGroups = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (var avail in _orderedCommands)
+ foreach (var avail in orderedCommands)
{
if (avail.Metadata.SubCommand != null)
{
@@ -193,13 +201,13 @@ void PrintHelp(string executableName)
Console.WriteLine($"Type `{executableName} help ` for detailed help");
}
- void PrintHelp(string executableName, string topLevelCommand)
+ static void PrintHelp(string executableName, string topLevelCommand, CommandList orderedCommands)
{
Console.WriteLine($"Usage: {executableName} {topLevelCommand} []");
Console.WriteLine();
Console.WriteLine("Available sub-commands are:");
- foreach (var avail in _orderedCommands.Where(c => c.Metadata.Name == topLevelCommand))
+ foreach (var avail in orderedCommands.Where(c => c.Metadata.Name == topLevelCommand))
{
Printing.Define($" {avail.Metadata.SubCommand}", avail.Metadata.HelpText, Console.Out);
}
diff --git a/src/SeqCli/Cli/Commands/Index/ListCommand.cs b/src/SeqCli/Cli/Commands/Index/ListCommand.cs
index 958451b5..78138f70 100644
--- a/src/SeqCli/Cli/Commands/Index/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/Index/ListCommand.cs
@@ -26,35 +26,34 @@ namespace SeqCli.Cli.Commands.Index;
[Command("index", "list", "List indexes", Example="seqcli index list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
+ readonly StoragePathFeature _storagePath;
+
string? _id;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single index to list",
id => _id = id);
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var list = _id is not null
? [await connection.Indexes.FindAsync(_id)]
: await connection.Indexes.ListAsync();
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs
index 5aa495bd..bd330179 100644
--- a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs
+++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs
@@ -25,20 +25,19 @@ namespace SeqCli.Cli.Commands.Index;
[Command("index", "suppress", "Suppress an index", Example="seqcli index suppress -i index-2191448f1d9b4f22bd32c6edef752748")]
class SuppressCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
+ readonly StoragePathFeature _storagePath;
+
string? _id;
- public SuppressCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public SuppressCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of an index to suppress",
id => _id = id);
+ _storagePath = Enable();
_connection = Enable();
}
@@ -50,7 +49,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var toSuppress = await connection.Indexes.FindAsync(_id);
await connection.Indexes.SuppressAsync(toSuppress);
diff --git a/src/SeqCli/Cli/Commands/IngestCommand.cs b/src/SeqCli/Cli/Commands/IngestCommand.cs
index ad52e0ba..1827ed59 100644
--- a/src/SeqCli/Cli/Commands/IngestCommand.cs
+++ b/src/SeqCli/Cli/Commands/IngestCommand.cs
@@ -16,6 +16,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using SeqCli.Ingestion;
using SeqCli.Levels;
@@ -33,20 +34,19 @@ class IngestCommand : Command
{
const string DefaultPattern = "{@m:line}";
- readonly SeqConnectionFactory _connectionFactory;
readonly InvalidDataHandlingFeature _invalidDataHandlingFeature;
readonly FileInputFeature _fileInputFeature;
readonly PropertiesFeature _properties;
readonly SendFailureHandlingFeature _sendFailureHandlingFeature;
readonly ConnectionFeature _connection;
readonly BatchSizeFeature _batchSize;
+ readonly StoragePathFeature _storagePath;
string? _filter, _level, _message;
string _pattern = DefaultPattern;
bool _json;
- public IngestCommand(SeqConnectionFactory connectionFactory)
+ public IngestCommand()
{
- _connectionFactory = connectionFactory;
_fileInputFeature = Enable(new FileInputFeature("File(s) to ingest", allowMultiple: true));
_invalidDataHandlingFeature = Enable();
_properties = Enable();
@@ -76,6 +76,7 @@ public IngestCommand(SeqConnectionFactory connectionFactory)
_sendFailureHandlingFeature = Enable();
_connection = Enable();
_batchSize = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -97,16 +98,21 @@ protected override async Task Run()
filter = evt => Seq.Syntax.Expressions.ExpressionResult.IsTrue(eval(evt));
}
- var connection = _connectionFactory.Connect(_connection);
- var (_, apiKey) = _connectionFactory.GetConnectionDetails(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
+
+ // The API key is passed through separately because `SeqConnection` doesn't expose a batched ingestion
+ // mechanism and so we manually construct `HttpRequestMessage`s deeper in the stack. Nice feature gap to
+ // close at some point!
+ var (_, apiKey) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
var batchSize = _batchSize.Value;
foreach (var input in _fileInputFeature.OpenInputs())
{
using (input)
{
- var reader = _json
- ? (ILogEventReader) new JsonLogEventReader(input)
+ ILogEventReader reader = _json
+ ? new JsonLogEventReader(input)
: new PlainTextLogEventReader(input, _pattern);
reader = new EnrichingReader(reader, enrichers);
diff --git a/src/SeqCli/Cli/Commands/License/ApplyCommand.cs b/src/SeqCli/Cli/Commands/License/ApplyCommand.cs
index 7f2970b7..53b50da9 100644
--- a/src/SeqCli/Cli/Commands/License/ApplyCommand.cs
+++ b/src/SeqCli/Cli/Commands/License/ApplyCommand.cs
@@ -3,6 +3,7 @@
using System.Text;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using SeqCli.Util;
using Serilog;
@@ -15,17 +16,15 @@ namespace SeqCli.Cli.Commands.License;
Example = "seqcli license apply --certificate=\"license.txt\"")]
class ApplyCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
-
+ readonly StoragePathFeature _storagePath;
+
string? _certificateFilename;
bool _certificateStdin;
bool _automaticallyRefresh;
- public ApplyCommand(SeqConnectionFactory connectionFactory)
+ public ApplyCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add("c=|certificate=",
"Certificate file; the file must be UTF-8 text",
v => _certificateFilename = ArgumentString.Normalize(v));
@@ -39,6 +38,7 @@ public ApplyCommand(SeqConnectionFactory connectionFactory)
"the certificate when the subscription is changed or renewed",
_ => _automaticallyRefresh = true);
+ _storagePath = Enable();
_connection = Enable();
}
@@ -71,7 +71,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var license = await connection.Licenses.FindCurrentAsync();
license.LicenseText = certificate;
license.AutomaticallyRefresh = _automaticallyRefresh;
diff --git a/src/SeqCli/Cli/Commands/License/ShowCommand.cs b/src/SeqCli/Cli/Commands/License/ShowCommand.cs
index 1db3bc6d..7f163efb 100644
--- a/src/SeqCli/Cli/Commands/License/ShowCommand.cs
+++ b/src/SeqCli/Cli/Commands/License/ShowCommand.cs
@@ -18,29 +18,32 @@ namespace SeqCli.Cli.Commands.License;
Example = "seqcli license show")]
class ShowCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
+ readonly StoragePathFeature _storage;
- public ShowCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ShowCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storage = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storage);
+ var output = _output.GetOutputFormat(config);
+
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var license = await connection.Licenses.FindCurrentAsync();
- if (_output.Json)
+ if (output.Json)
{
- _output.WriteEntity(license);
+ output.WriteEntity(license);
}
else
{
- _output.WriteText(license?.LicenseText);
+ output.WriteText(license?.LicenseText);
}
return 0;
diff --git a/src/SeqCli/Cli/Commands/LogCommand.cs b/src/SeqCli/Cli/Commands/LogCommand.cs
index 2d6002ae..be3c5f8e 100644
--- a/src/SeqCli/Cli/Commands/LogCommand.cs
+++ b/src/SeqCli/Cli/Commands/LogCommand.cs
@@ -22,6 +22,7 @@
using Newtonsoft.Json.Linq;
using SeqCli.Api;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -32,15 +33,13 @@ namespace SeqCli.Cli.Commands;
[Command("log", "Send a structured log event to the server", Example = "seqcli log -m 'Hello, {Name}!' -p Name=World -p App=Test")]
class LogCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
readonly PropertiesFeature _properties;
readonly ConnectionFeature _connection;
+ readonly StoragePathFeature _storagePath;
string? _message, _level, _timestamp, _exception;
- public LogCommand(SeqConnectionFactory connectionFactory)
+ public LogCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"m=|message=",
"A message to associate with the event (the default is to send no message); https://messagetemplates.org syntax is supported",
@@ -63,6 +62,7 @@ public LogCommand(SeqConnectionFactory connectionFactory)
_properties = Enable();
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -103,8 +103,9 @@ protected override async Task Run()
content = new StringContent(builder.ToString(), Encoding.UTF8, ApiConstants.ClefMediaType);
}
- var connection = _connectionFactory.Connect(_connection);
- var (_, apiKey) = _connectionFactory.GetConnectionDetails(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
+ var (_, apiKey) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
var request = new HttpRequestMessage(HttpMethod.Post, ApiConstants.IngestionEndpoint) {Content = content};
if (apiKey != null)
diff --git a/src/SeqCli/Cli/Commands/Node/HealthCommand.cs b/src/SeqCli/Cli/Commands/Node/HealthCommand.cs
index ef910c39..60a82b76 100644
--- a/src/SeqCli/Cli/Commands/Node/HealthCommand.cs
+++ b/src/SeqCli/Cli/Commands/Node/HealthCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -31,38 +31,38 @@ namespace SeqCli.Cli.Commands.Node;
Example = "seqcli node health -s https://seq-2.example.com")]
class HealthCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly WaitUntilHealthyFeature _waitUntilHealthy;
readonly TimeoutFeature _timeout;
readonly OutputFormatFeature _output;
-
- public HealthCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig outputConfig)
+ readonly StoragePathFeature _storagePath;
+
+ public HealthCommand()
{
- _connectionFactory = connectionFactory;
_waitUntilHealthy = Enable(new WaitUntilHealthyFeature("node"));
_timeout = Enable(new TimeoutFeature());
_connection = Enable();
- _output = Enable(new OutputFormatFeature(outputConfig));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
if (_waitUntilHealthy.ShouldWait)
{
- return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30));
+ return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30), _output.GetOutputFormat(config));
}
- return await RunOnce(connection);
+ return await RunOnce(connection, _output.GetOutputFormat(config));
}
- async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
+ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout, OutputFormat outputFormat)
{
using var ct = new CancellationTokenSource(timeout);
@@ -76,7 +76,7 @@ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
{
while (true)
{
- if (await RunOnce(connection) == 0)
+ if (await RunOnce(connection, outputFormat) == 0)
{
return 0;
}
@@ -91,7 +91,7 @@ async Task RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
}
}
- async Task RunOnce(SeqConnection connection)
+ async Task RunOnce(SeqConnection connection, OutputFormat outputFormat)
{
try
{
@@ -104,17 +104,17 @@ async Task RunOnce(SeqConnection connection)
Log.Information("{HeaderName}: {HeaderValue}", key, value);
}
- if (_output.Json)
+ if (outputFormat.Json)
{
var shouldBeJson = await response.Content.ReadAsStringAsync();
try
{
var obj = JsonConvert.DeserializeObject(shouldBeJson) ?? throw new InvalidDataException();
- _output.WriteObject(obj);
+ outputFormat.WriteObject(obj);
}
catch
{
- _output.WriteObject(new { Response = shouldBeJson });
+ outputFormat.WriteObject(new { Response = shouldBeJson });
}
}
else
diff --git a/src/SeqCli/Cli/Commands/Node/ListCommand.cs b/src/SeqCli/Cli/Commands/Node/ListCommand.cs
index 358db970..c374c6a0 100644
--- a/src/SeqCli/Cli/Commands/Node/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/Node/ListCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,17 +25,14 @@ namespace SeqCli.Cli.Commands.Node;
Example = "seqcli node list --json")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _name, _id;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig outputConfig)
+ public ListCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"n=|name=",
"The name of the cluster node to list",
@@ -46,20 +43,22 @@ public ListCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig ou
"The id of a single cluster node to list",
id => _id = id);
- _output = Enable(new OutputFormatFeature(outputConfig));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = _id != null ?
- new[] { await connection.Cluster.FindAsync(_id) } :
+ var list = _id != null ? [await connection.Cluster.FindAsync(_id)]
+ :
(await connection.Cluster.ListAsync())
.Where(n => _name == null || _name == n.Name);
- _output.ListEntities(list);
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/PrintCommand.cs b/src/SeqCli/Cli/Commands/PrintCommand.cs
index c277914d..51d845ab 100644
--- a/src/SeqCli/Cli/Commands/PrintCommand.cs
+++ b/src/SeqCli/Cli/Commands/PrintCommand.cs
@@ -34,16 +34,13 @@ class PrintCommand : Command
{
readonly FileInputFeature _fileInputFeature;
readonly InvalidDataHandlingFeature _invalidDataHandlingFeature;
+ readonly StoragePathFeature _storage;
- string? _filter, _template = OutputFormatFeature.DefaultOutputTemplate;
- bool _noColor, _forceColor;
+ string? _filter, _template = OutputFormat.DefaultOutputTemplate;
+ bool? _noColor, _forceColor;
- public PrintCommand(SeqCliOutputConfig outputConfig)
+ public PrintCommand()
{
- if (outputConfig == null) throw new ArgumentNullException(nameof(outputConfig));
- _noColor = outputConfig.DisableColor;
- _forceColor = outputConfig.ForceColor;
-
_fileInputFeature = Enable(new FileInputFeature("CLEF file to read", allowMultiple: true));
Options.Add("f=|filter=",
@@ -56,28 +53,32 @@ public PrintCommand(SeqCliOutputConfig outputConfig)
_invalidDataHandlingFeature = Enable();
+ // These should be ported to use `OutputFormatFeature`.
Options.Add("no-color", "Don't colorize text output", _ => _noColor = true);
-
Options.Add("force-color",
"Force redirected output to have ANSI color (unless `--no-color` is also specified)",
_ => _forceColor = true);
+
+ _storage = Enable();
}
protected override async Task Run()
{
+ var config = RuntimeConfigurationLoader.Load(_storage);
+
var applyThemeToRedirectedOutput
- = !_noColor && _forceColor;
+ = !(_noColor ?? config.Output.DisableColor) && (_forceColor ?? config.Output.ForceColor);
var theme
- = _noColor ? ConsoleTheme.None
- : applyThemeToRedirectedOutput ? OutputFormatFeature.DefaultAnsiTheme
- : OutputFormatFeature.DefaultTheme;
+ = _noColor ?? config.Output.DisableColor ? ConsoleTheme.None
+ : applyThemeToRedirectedOutput ? OutputFormat.DefaultAnsiTheme
+ : OutputFormat.DefaultTheme;
var outputConfiguration = new LoggerConfiguration()
.MinimumLevel.Is(LevelAlias.Minimum)
.Enrich.With()
.WriteTo.Console(
- outputTemplate: _template ?? OutputFormatFeature.DefaultOutputTemplate,
+ outputTemplate: _template ?? OutputFormat.DefaultOutputTemplate,
theme: theme,
applyThemeToRedirectedOutput: applyThemeToRedirectedOutput);
diff --git a/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs b/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs
index f4289763..ba2dfc5d 100644
--- a/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/Profile/CreateCommand.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using SeqCli.Cli.Features;
using SeqCli.Config;
using SeqCli.Util;
using Serilog;
@@ -11,7 +12,8 @@ namespace SeqCli.Cli.Commands.Profile;
class CreateCommand : Command
{
string? _url, _apiKey, _name;
-
+ readonly StoragePathFeature _storagePath;
+
public CreateCommand()
{
Options.Add("n=|name=",
@@ -25,6 +27,8 @@ public CreateCommand()
Options.Add("a=|apikey=",
"The API key to use when connecting to the server, if required",
v => _apiKey = ArgumentString.Normalize(v));
+
+ _storagePath = Enable();
}
protected override Task Run()
@@ -48,9 +52,11 @@ int RunSync()
try
{
- var config = SeqCliConfig.ReadFromFile(RuntimeConfigurationLoader.DefaultConfigFilename);
- config.Profiles[_name] = new SeqCliConnectionConfig { ServerUrl = _url, ApiKey = _apiKey };
- SeqCliConfig.WriteToFile(config, RuntimeConfigurationLoader.DefaultConfigFilename);
+ var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
+ var connectionConfig = new SeqCliConnectionConfig { ServerUrl = _url };
+ connectionConfig.EncodeApiKey(_apiKey, config.Encryption.DataProtector());
+ config.Profiles[_name] = connectionConfig;
+ SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
return 0;
}
catch (Exception ex)
diff --git a/src/SeqCli/Cli/Commands/Profile/ListCommand.cs b/src/SeqCli/Cli/Commands/Profile/ListCommand.cs
index 8d3b8048..c43d3324 100644
--- a/src/SeqCli/Cli/Commands/Profile/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/Profile/ListCommand.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using SeqCli.Cli.Features;
using SeqCli.Config;
namespace SeqCli.Cli.Commands.Profile;
@@ -9,9 +10,16 @@ namespace SeqCli.Cli.Commands.Profile;
Example = "seqcli profile list")]
class ListCommand : Command
{
+ readonly StoragePathFeature _storagePath;
+
+ public ListCommand()
+ {
+ _storagePath = Enable();
+ }
+
protected override Task Run()
{
- var config = RuntimeConfigurationLoader.Load();
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
foreach (var profile in config.Profiles.OrderBy(p => p.Key))
{
diff --git a/src/SeqCli/Cli/Commands/Profile/RemoveCommand.cs b/src/SeqCli/Cli/Commands/Profile/RemoveCommand.cs
index a112bdb1..244b520f 100644
--- a/src/SeqCli/Cli/Commands/Profile/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/Profile/RemoveCommand.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using SeqCli.Cli.Features;
using SeqCli.Config;
using SeqCli.Util;
using Serilog;
@@ -11,12 +12,15 @@ namespace SeqCli.Cli.Commands.Profile;
class RemoveCommand : Command
{
string? _name;
+ readonly StoragePathFeature _storagePath;
public RemoveCommand()
{
Options.Add("n=|name=",
"The name of the connection profile to remove",
v => _name = ArgumentString.Normalize(v));
+
+ _storagePath = Enable();
}
protected override Task Run()
@@ -34,14 +38,14 @@ int RunSync()
try
{
- var config = SeqCliConfig.ReadFromFile(RuntimeConfigurationLoader.DefaultConfigFilename);
+ var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
if (!config.Profiles.Remove(_name))
{
Log.Error("No profile with name {ProfileName} was found", _name);
return 1;
}
- SeqCliConfig.WriteToFile(config, RuntimeConfigurationLoader.DefaultConfigFilename);
+ SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/QueryCommand.cs b/src/SeqCli/Cli/Commands/QueryCommand.cs
index 2dfd8058..1c54110f 100644
--- a/src/SeqCli/Cli/Commands/QueryCommand.cs
+++ b/src/SeqCli/Cli/Commands/QueryCommand.cs
@@ -29,23 +29,22 @@ namespace SeqCli.Cli.Commands;
class QueryCommand : Command
{
readonly OutputFormatFeature _output;
- readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
readonly DateRangeFeature _range;
readonly SignalExpressionFeature _signal;
readonly TimeoutFeature _timeout;
+ readonly StoragePathFeature _storagePath;
string? _query;
bool _trace;
- public QueryCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public QueryCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
Options.Add("q=|query=", "The query to execute", v => _query = v);
_range = Enable();
_signal = Enable();
_timeout = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
Options.Add("trace", "Enable detailed (server-side) query tracing", _ => _trace = true);
_connection = Enable();
}
@@ -58,11 +57,13 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
-
- if (_output.Json)
+
+ var output = _output.GetOutputFormat(config);
+ if (output.Json)
{
var result = await connection.Data.QueryAsync(_query, _range.Start, _range.End, _signal.Signal, timeout: timeout, trace: _trace);
@@ -72,7 +73,7 @@ protected override async Task Run()
else
{
var result = await connection.Data.QueryCsvAsync(_query, _range.Start, _range.End, _signal.Signal, timeout: timeout, trace: _trace);
- _output.WriteCsv(result);
+ output.WriteCsv(result);
}
return 0;
diff --git a/src/SeqCli/Cli/Commands/RetentionPolicy/CreateCommand.cs b/src/SeqCli/Cli/Commands/RetentionPolicy/CreateCommand.cs
index 6ead5878..aa5c6a07 100644
--- a/src/SeqCli/Cli/Commands/RetentionPolicy/CreateCommand.cs
+++ b/src/SeqCli/Cli/Commands/RetentionPolicy/CreateCommand.cs
@@ -29,19 +29,16 @@ namespace SeqCli.Cli.Commands.RetentionPolicy;
Example = "seqcli retention create --after 30d --delete-all-events")]
class CreateCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _afterDuration;
bool _deleteAllEvents;
string? _deleteMatchingSignal;
- public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public CreateCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"after=",
"A duration after which the policy will delete events, e.g. `7d`",
@@ -62,12 +59,14 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
);
_connection = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
SignalExpressionPart? removedSignalExpression;
@@ -106,7 +105,7 @@ protected override async Task Run()
policy = await connection.RetentionPolicies.AddAsync(policy);
- _output.WriteEntity(policy);
+ _output.GetOutputFormat(config).WriteEntity(policy);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/RetentionPolicy/ListCommand.cs b/src/SeqCli/Cli/Commands/RetentionPolicy/ListCommand.cs
index 57702a93..391866ad 100644
--- a/src/SeqCli/Cli/Commands/RetentionPolicy/ListCommand.cs
+++ b/src/SeqCli/Cli/Commands/RetentionPolicy/ListCommand.cs
@@ -13,7 +13,6 @@
// limitations under the License.
using System;
-using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
using SeqCli.Config;
@@ -24,37 +23,35 @@ namespace SeqCli.Cli.Commands.RetentionPolicy;
[Command("retention", "list", "List retention policies", Example="seqcli retention list")]
class ListCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
-
+ readonly StoragePathFeature _storagePath;
+
string? _id;
- public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public ListCommand()
{
- if (config == null) throw new ArgumentNullException(nameof(config));
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single retention policy to list",
id => _id = id);
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_connection = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var list = _id != null ?
- new[] { await connection.RetentionPolicies.FindAsync(_id) } :
+ var list = _id != null ? [await connection.RetentionPolicies.FindAsync(_id)]
+ :
(await connection.RetentionPolicies.ListAsync())
.ToArray();
-
- _output.ListEntities(list);
+
+ _output.GetOutputFormat(config).ListEntities(list);
return 0;
}
diff --git a/src/SeqCli/Cli/Commands/RetentionPolicy/RemoveCommand.cs b/src/SeqCli/Cli/Commands/RetentionPolicy/RemoveCommand.cs
index e083d779..3d9266e0 100644
--- a/src/SeqCli/Cli/Commands/RetentionPolicy/RemoveCommand.cs
+++ b/src/SeqCli/Cli/Commands/RetentionPolicy/RemoveCommand.cs
@@ -15,6 +15,7 @@
using System;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using Serilog;
@@ -24,22 +25,20 @@ namespace SeqCli.Cli.Commands.RetentionPolicy;
Example="seqcli retention remove -i retentionpolicy-17")]
class RemoveCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
-
+ readonly StoragePathFeature _storagePath;
+
string? _id;
- public RemoveCommand(SeqConnectionFactory connectionFactory)
+ public RemoveCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"i=|id=",
"The id of a single retention policy to remove",
id => _id = id);
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
@@ -50,7 +49,8 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
var toRemove = await connection.RetentionPolicies.FindAsync(_id);
diff --git a/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs b/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs
index 12dded9e..ca6503ce 100644
--- a/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs
+++ b/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs
@@ -20,6 +20,6 @@ namespace SeqCli.Cli.Commands.RetentionPolicy;
[Command("retention", "update",
"Update an existing retention policy",
Example="seqcli retention update --json '{...}'")]
-class UpdateCommand(SeqConnectionFactory connectionFactory):
- Shared.UpdateCommand(connectionFactory, "retention", nameof(SeqConnection.RetentionPolicies), "retention policy");
+class UpdateCommand():
+ Shared.UpdateCommand("retention", nameof(SeqConnection.RetentionPolicies), "retention policy");
\ No newline at end of file
diff --git a/src/SeqCli/Cli/Commands/Sample/IngestCommand.cs b/src/SeqCli/Cli/Commands/Sample/IngestCommand.cs
index 251c00a0..a5dd3790 100644
--- a/src/SeqCli/Cli/Commands/Sample/IngestCommand.cs
+++ b/src/SeqCli/Cli/Commands/Sample/IngestCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
using System.Linq;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
using SeqCli.Sample.Loader;
@@ -25,19 +26,17 @@ namespace SeqCli.Cli.Commands.Sample;
Example = "seqcli sample ingest")]
class IngestCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly ConfirmFeature _confirm;
readonly BatchSizeFeature _batchSize;
-
+ readonly StoragePathFeature _storagePath;
+
bool _quiet;
bool _setup;
int _simulations = 1;
- public IngestCommand(SeqConnectionFactory connectionFactory)
+ public IngestCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_confirm = Enable();
_connection = Enable();
@@ -47,11 +46,13 @@ public IngestCommand(SeqConnectionFactory connectionFactory)
v => _simulations = int.Parse(v));
_batchSize = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var (url, apiKey) = _connectionFactory.GetConnectionDetails(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var (url, apiKey) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
var batchSize = _batchSize.Value;
if (!_confirm.TryConfirm(_setup
@@ -62,7 +63,7 @@ protected override async Task Run()
return 1;
}
- var connection = _connectionFactory.Connect(_connection);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
if (_setup)
{
diff --git a/src/SeqCli/Cli/Commands/Sample/SetupCommand.cs b/src/SeqCli/Cli/Commands/Sample/SetupCommand.cs
index e801ca10..fb0c0722 100644
--- a/src/SeqCli/Cli/Commands/Sample/SetupCommand.cs
+++ b/src/SeqCli/Cli/Commands/Sample/SetupCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
using SeqCli.Templates.Import;
using SeqCli.Util;
using Seq.Api;
+using SeqCli.Config;
// ReSharper disable once UnusedType.Global
@@ -32,27 +33,26 @@ namespace SeqCli.Cli.Commands.Sample;
Example = "seqcli sample setup")]
class SetupCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly ConfirmFeature _confirm;
-
- public SetupCommand(SeqConnectionFactory connectionFactory)
+ readonly StoragePathFeature _storagePath;
+
+ public SetupCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
// The command will also at some point accept an `--allow-outbound-requests` flag, which will cause sample
// apps to be installed, and a health check to be set up.
_confirm = Enable();
_connection = Enable();
+ _storagePath = Enable();
}
protected override async Task Run()
{
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ var connection = SeqConnectionFactory.Connect(_connection, config);
- var (url, _) = _connectionFactory.GetConnectionDetails(_connection);
+ var (url, _) = SeqConnectionFactory.GetConnectionDetails(_connection, config);
if (!_confirm.TryConfirm($"This will apply sample configuration items to the Seq server at {url}."))
{
await Console.Error.WriteLineAsync("Canceled by user.");
@@ -64,8 +64,10 @@ protected override async Task Run()
internal static async Task ImportTemplates(SeqConnection connection)
{
- var templateArgs = new Dictionary();
- templateArgs["ownerId"] = new JsonTemplateNull();
+ var templateArgs = new Dictionary
+ {
+ ["ownerId"] = new JsonTemplateNull()
+ };
var templatesPath = Content.GetPath(Path.Combine("Sample", "Templates"));
var templateFiles = Directory.GetFiles(templatesPath);
diff --git a/src/SeqCli/Cli/Commands/SearchCommand.cs b/src/SeqCli/Cli/Commands/SearchCommand.cs
index 001bca4d..151be6eb 100644
--- a/src/SeqCli/Cli/Commands/SearchCommand.cs
+++ b/src/SeqCli/Cli/Commands/SearchCommand.cs
@@ -34,20 +34,18 @@ namespace SeqCli.Cli.Commands;
Example = "seqcli search -f \"@Exception like '%TimeoutException%'\" -c 30")]
class SearchCommand : Command
{
- readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;
readonly DateRangeFeature _range;
readonly SignalExpressionFeature _signal;
+ readonly StoragePathFeature _storagePath;
string? _filter;
int _count = 1;
int _httpClientTimeout = 100000;
bool _trace, _noWebSockets;
- public SearchCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
+ public SearchCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
Options.Add(
"f=|filter=",
"A filter to apply to the search, for example `Host = 'xmpweb-01.example.com'`",
@@ -58,7 +56,8 @@ public SearchCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
v => _count = int.Parse(v, CultureInfo.InvariantCulture));
_range = Enable();
- _output = Enable(new OutputFormatFeature(config.Output));
+ _output = Enable();
+ _storagePath = Enable();
_signal = Enable();
Options.Add(
@@ -77,8 +76,9 @@ protected override async Task Run()
{
try
{
- await using var output = _output.CreateOutputLogger();
- var connection = _connectionFactory.Connect(_connection);
+ var config = RuntimeConfigurationLoader.Load(_storagePath);
+ await using var output = _output.GetOutputFormat(config).CreateOutputLogger();
+ var connection = SeqConnectionFactory.Connect(_connection, config);
connection.Client.HttpClient.Timeout = TimeSpan.FromMilliseconds(_httpClientTimeout);
string? filter = null;
diff --git a/src/SeqCli/Cli/Commands/Settings/ClearCommand.cs b/src/SeqCli/Cli/Commands/Settings/ClearCommand.cs
index e4025de8..7d527e43 100644
--- a/src/SeqCli/Cli/Commands/Settings/ClearCommand.cs
+++ b/src/SeqCli/Cli/Commands/Settings/ClearCommand.cs
@@ -1,4 +1,4 @@
-// Copyright Datalust Pty Ltd and Contributors
+// Copyright © Datalust Pty Ltd and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
using System;
using System.Threading.Tasks;
using SeqCli.Cli.Features;
+using SeqCli.Config;
using SeqCli.Connection;
namespace SeqCli.Cli.Commands.Settings;
@@ -22,22 +23,21 @@ namespace SeqCli.Cli.Commands.Settings;
[Command("setting", "clear", "Clear a runtime-configurable server setting")]
class ClearCommand: Command
{
- readonly SeqConnectionFactory _connectionFactory;
-
readonly ConnectionFeature _connection;
readonly SettingNameFeature _name;
-
- public ClearCommand(SeqConnectionFactory connectionFactory)
+ readonly StoragePathFeature _storagePath;
+
+ public ClearCommand()
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
-
_name = Enable