diff --git a/Build.Docker.ps1 b/Build.Docker.ps1
index 4fc6c30d..88e62dc5 100644
--- a/Build.Docker.ps1
+++ b/Build.Docker.ps1
@@ -10,6 +10,13 @@ function Execute-Tests
{
& dotnet test ./test/SeqCli.Tests/SeqCli.Tests.csproj -c Release /p:Configuration=Release /p:Platform=x64 /p:VersionPrefix=$version
if ($LASTEXITCODE -ne 0) { exit 1 }
+
+ cd ./test/SeqCli.EndToEnd/
+ docker pull datalust/seq:latest
+ $env:ENDTOEND_USE_DOCKER_SEQ="Y"
+ & dotnet run
+ if ($LASTEXITCODE -ne 0) { exit 1 }
+ cd ../..
}
function Build-DockerImage
diff --git a/Build.ps1 b/Build.ps1
index 8a11f5af..2ec9db41 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -25,9 +25,9 @@ function Create-ArtifactDir
function Publish-Archives($version)
{
- $rids = @("linux-x64", "osx-x64", "win-x64")
+ $rids = @("linux-x64", "linux-musl-x64", "osx-x64", "win-x64")
foreach ($rid in $rids) {
- & dotnet publish src/SeqCli/SeqCli.csproj -c Release -f $framework -r $rid /p:VersionPrefix=$version /p:SeqCliRid=$rid /p:ShowLinkerSizeComparison=true
+ & dotnet publish ./src/SeqCli/SeqCli.csproj -c Release -f $framework -r $rid /p:VersionPrefix=$version /p:SeqCliRid=$rid /p:ShowLinkerSizeComparison=true
if($LASTEXITCODE -ne 0) { exit 4 }
# Make sure the archive contains a reasonable root filename
@@ -51,6 +51,11 @@ function Publish-Archives($version)
}
}
+function Publish-DotNetTool($version)
+{
+ dotnet pack ./src/SeqCli/SeqCli.csproj -c Release --output ./artifacts /p:VersionPrefix=$version
+}
+
Push-Location $PSScriptRoot
$version = @{ $true = $env:APPVEYOR_BUILD_VERSION; $false = "99.99.99" }[$env:APPVEYOR_BUILD_VERSION -ne $NULL];
@@ -60,6 +65,7 @@ Clean-Output
Create-ArtifactDir
Restore-Packages
Publish-Archives($version)
+Publish-DotNetTool($version)
Execute-Tests
Pop-Location
diff --git a/README.md b/README.md
index f12e6959..abd498f8 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,11 @@ The [Seq](https://datalust.co/seq) client command-line app. Supports logging (`s
## Getting started
-Install or unzip the [release for your operating system](https://github.com/datalust/seqcli/releases).
+Install or unzip the [release for your operating system](https://github.com/datalust/seqcli/releases). Or, if you have `dotnet` installed, `seqcli` can be installed as a global tool using:
+
+```
+dotnet tool install --global seqcli
+```
To set a default server URL, run:
@@ -443,6 +447,7 @@ seqcli signal import -i ./Exceptions.json
| `-s`, `--server=VALUE` | The URL of the Seq server; by default the `connection.serverUrl` config value will be used |
| `-a`, `--apikey=VALUE` | The API key to use when connecting to the server; by default the `connection.apiKey` config value will be used |
| `--profile=VALUE` | A connection profile to use; by default the `connection.serverUrl` and `connection.apiKey` config values will be used |
+| `--merge` | Update signals that have ids matching those in the imported data; the default is to always create new signals |
### `signal create`
diff --git a/SeqCli.Runtime.targets b/SeqCli.Runtime.targets
index ed4e9b56..8f9e8f8e 100644
--- a/SeqCli.Runtime.targets
+++ b/SeqCli.Runtime.targets
@@ -7,7 +7,7 @@
true
$(DefineConstants);WINDOWS
-
+
true
true
$(DefineConstants);LINUX;UNIX
diff --git a/appveyor.yml b/appveyor.yml
index dc077af7..bf52b72b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2020.2.{build}
+version: 2020.3.{build}
skip_tags: true
image:
- Visual Studio 2019
@@ -12,6 +12,7 @@ test: off
artifacts:
- path: artifacts/seqcli-*.zip
- path: artifacts/seqcli-*.tar.gz
+- path: artifacts/seqcli.*.nupkg
for:
-
@@ -26,10 +27,19 @@ for:
- ps: ./Build.ps1 -shortver "$($env:APPVEYOR_BUILD_VERSION)"
deploy:
+
+ - provider: NuGet
+ api_key:
+ secure: EVhhkxsbCh3YQw4IO7/d2paIjtjWiZGqs2pZl1l2Gkdz4DluOomXHA+wkPWHscj3
+ skip_symbols: true
+ artifact: /seqcli\..*\.nupkg/
+ on:
+ branch: main
+
- provider: GitHub
auth_token:
secure: Bo3ypKpKFxinjR9ShkNekNvkob2iklHJU+UlYyfHtcFFIAa58SV2TkEd0xWxz633
- artifact: /seqcli-.*\.(zip|tar\.gz)/
+ artifact: /seqcli-.*\.(nupkg|zip|tar\.gz)/
tag: v$(appveyor_build_version)
on:
branch: main
diff --git a/seqcli.sln b/seqcli.sln
index 5fce66ea..c49689d6 100644
--- a/seqcli.sln
+++ b/seqcli.sln
@@ -24,10 +24,9 @@ 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\SeqCli-Icon-128px.ico = asset\SeqCli-Icon-128px.ico
- asset\SeqCli-Icon-128px.png = asset\SeqCli-Icon-128px.png
- asset\SeqCli-Icon.svg = asset\SeqCli-Icon.svg
asset\SeqCliLicense.rtf = asset\SeqCliLicense.rtf
+ asset\SeqCli.ico = asset\SeqCli.ico
+ asset\SeqCli.png = asset\SeqCli.png
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SeqCli", "src\SeqCli\SeqCli.csproj", "{EBDBEED2-A1BC-4269-B6B0-EA908CE4D687}"
diff --git a/src/SeqCli/Cli/Commands/Signal/ImportCommand.cs b/src/SeqCli/Cli/Commands/Signal/ImportCommand.cs
index ef65baf8..ceaaa842 100644
--- a/src/SeqCli/Cli/Commands/Signal/ImportCommand.cs
+++ b/src/SeqCli/Cli/Commands/Signal/ImportCommand.cs
@@ -32,7 +32,9 @@ class ImportCommand : Command
readonly FileInputFeature _fileInputFeature;
readonly EntityOwnerFeature _entityOwner;
readonly ConnectionFeature _connection;
-
+
+ bool _merge;
+
readonly JsonSerializer _serializer = JsonSerializer.Create(
new JsonSerializerSettings{
Converters = { new StringEnumConverter() }
@@ -42,6 +44,12 @@ public ImportCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
{
if (config == null) throw new ArgumentNullException(nameof(config));
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
+
+ Options.Add(
+ "merge",
+ "Update signals that have ids matching those in the imported data; the default is to always create new signals",
+ _ => _merge = true);
+
_fileInputFeature = Enable(new FileInputFeature("File to import"));
_entityOwner = Enable(new EntityOwnerFeature("signal", "import"));
_connection = Enable();
@@ -51,31 +59,55 @@ protected override async Task Run()
{
var connection = _connectionFactory.Connect(_connection);
- using (var input = _fileInputFeature.OpenInput())
+ using var input = _fileInputFeature.OpenInput();
+ var line = await input.ReadLineAsync();
+ while (line != null)
{
- var line = input.ReadLine();
- while (line != null)
+ if (!string.IsNullOrWhiteSpace(line))
{
- if (!string.IsNullOrWhiteSpace(line))
+ // Explicitly copying fields here ensures we don't try copying links or ids; for other
+ // entity types it'll ensure we notice places that "referential integrity" has to be
+ // maintained.
+ var src = _serializer.Deserialize(new JsonTextReader(new StringReader(line)));
+ if (src == null) continue;
+
+ SignalEntity dest;
+ if (_merge)
{
- // Explicitly copying fields here ensures we don't try copying links or ids; for other
- // entity types it'll ensure we notice places that "referential integrity" has to be
- // maintained.
- var src = _serializer.Deserialize(new JsonTextReader(new StringReader(line)));
- var dest = await connection.Signals.TemplateAsync();
- dest.Title = src.Title;
- dest.Description = src.Description;
- dest.ExplicitGroupName = src.ExplicitGroupName;
- dest.Grouping = src.Grouping;
- dest.IsProtected = src.IsProtected;
- dest.Filters = src.Filters;
- dest.Columns = src.Columns;
- dest.OwnerId = _entityOwner.OwnerId;
- await connection.Signals.AddAsync(dest);
+ try
+ {
+ dest = await connection.Signals.FindAsync(src.Id);
+ }
+ catch (Exception)
+ {
+ dest = await connection.Signals.TemplateAsync();
+ }
+ }
+ else
+ {
+ dest = await connection.Signals.TemplateAsync();
}
+
+ dest.Title = src.Title;
+ dest.Description = src.Description;
+ dest.ExplicitGroupName = src.ExplicitGroupName;
+ dest.Grouping = src.Grouping;
+ dest.IsProtected = src.IsProtected;
+ dest.Filters = src.Filters;
+ dest.Columns = src.Columns;
+ dest.OwnerId = _entityOwner.OwnerId;
- line = input.ReadLine();
+ if (_merge && dest.Id != null)
+ {
+ await connection.Signals.UpdateAsync(dest);
+ }
+ else
+ {
+ await connection.Signals.AddAsync(dest);
+ }
}
+
+ line = await input.ReadLineAsync();
}
return 0;
diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj
index 8626d317..1f1a7669 100644
--- a/src/SeqCli/SeqCli.csproj
+++ b/src/SeqCli/SeqCli.csproj
@@ -4,12 +4,14 @@
netcoreapp3.1
seqcli
..\..\asset\SeqCli.ico
- win-x64;linux-x64;osx-x64
+ win-x64;linux-x64;linux-musl-x64;osx-x64
True
True
x64
8
+ true
+ seqcli
diff --git a/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj b/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj
index efd79dcb..d73fd8e7 100644
--- a/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj
+++ b/test/SeqCli.EndToEnd/SeqCli.EndToEnd.csproj
@@ -10,11 +10,13 @@
-
PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs b/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs
index c0f5c44f..1c44dced 100644
--- a/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs
+++ b/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs
@@ -9,6 +9,8 @@ namespace SeqCli.EndToEnd.Support
public sealed class CaptiveProcess : ITestProcess, IDisposable
{
readonly bool _captureOutput;
+ readonly string _stopCommandFullExePath;
+ readonly string _stopCommandArgs;
readonly Process _process;
readonly ManualResetEvent _outputComplete = new ManualResetEvent(false);
readonly ManualResetEvent _errorComplete = new ManualResetEvent(false);
@@ -20,10 +22,14 @@ public CaptiveProcess(
string fullExePath,
string args = null,
IDictionary environment = null,
- bool captureOutput = true)
+ bool captureOutput = true,
+ string stopCommandFullExePath = null,
+ string stopCommandArgs = null)
{
if (fullExePath == null) throw new ArgumentNullException(nameof(fullExePath));
_captureOutput = captureOutput;
+ _stopCommandFullExePath = stopCommandFullExePath;
+ _stopCommandArgs = stopCommandArgs;
var startInfo = new ProcessStartInfo
{
@@ -109,6 +115,23 @@ public void Dispose()
{
_process.Kill();
WaitForExit();
+ if (_stopCommandFullExePath != null)
+ {
+ var stopCommandStartInfo = new ProcessStartInfo
+ {
+ UseShellExecute = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ FileName = _stopCommandFullExePath,
+ Arguments = _stopCommandArgs ?? ""
+ };
+
+ using var stopCommandProcess = Process.Start(stopCommandStartInfo);
+ stopCommandProcess?.WaitForExit();
+ }
}
catch
{
diff --git a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs
index 618f0f2c..2b1536c9 100644
--- a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs
+++ b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs
@@ -6,7 +6,9 @@ namespace SeqCli.EndToEnd.Support
{
public class TestConfiguration
{
- public string ServerListenUrl { get; } = "http://localhost:9989";
+ public int ServerListenPort { get; } = 9989;
+
+ public string ServerListenUrl => $"http://localhost:{ServerListenPort}";
string EquivalentBaseDirectory { get; } = AppDomain.CurrentDomain.BaseDirectory
.Replace(Path.Combine("test", "SeqCli.EndToEnd"), Path.Combine("src", "SeqCli"));
@@ -30,7 +32,10 @@ public CaptiveProcess SpawnServerProcess(string storagePath)
if (storagePath == null) throw new ArgumentNullException(nameof(storagePath));
var commandWithArgs = $"run --listen=\"{ServerListenUrl}\" --storage=\"{storagePath}\"";
-
+ if (Environment.GetEnvironmentVariable("ENDTOEND_USE_DOCKER_SEQ") == "Y")
+ {
+ return new CaptiveProcess("docker", $"run --name seq -it --rm -e ACCEPT_EULA=Y -p {ServerListenPort}:80 datalust/seq:latest", stopCommandFullExePath: "docker", stopCommandArgs: "stop seq");
+ }
return new CaptiveProcess("seq", commandWithArgs);
}
}