From 63f414aadea41b068dfd79d01bd7bd3736dfdaff Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Fri, 19 Apr 2024 16:00:56 +1000 Subject: [PATCH 01/16] stubbed index commands --- global.json | 2 +- src/SeqCli/Cli/Commands/Index/ListCommand.cs | 55 +++++++++++++++++ .../Cli/Commands/Index/SuppressCommand.cs | 59 +++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/SeqCli/Cli/Commands/Index/ListCommand.cs create mode 100644 src/SeqCli/Cli/Commands/Index/SuppressCommand.cs diff --git a/global.json b/global.json index 1ee82c37..1658e451 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.203" + "version": "8.0.204" } } diff --git a/src/SeqCli/Cli/Commands/Index/ListCommand.cs b/src/SeqCli/Cli/Commands/Index/ListCommand.cs new file mode 100644 index 00000000..a3eacb61 --- /dev/null +++ b/src/SeqCli/Cli/Commands/Index/ListCommand.cs @@ -0,0 +1,55 @@ +// Copyright 2018 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. +// 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.Linq; +using System.Threading.Tasks; +using SeqCli.Cli.Features; +using SeqCli.Config; +using SeqCli.Connection; + +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; + + public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) + { + if (config == null) throw new ArgumentNullException(nameof(config)); + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + _output = Enable(new OutputFormatFeature(config.Output)); + _connection = Enable(); + } + + protected override async Task Run() + { + var connection = _connectionFactory.Connect(_connection); + + // var list = _userIdentity.Id != null ? + // new[] { await connection.Users.FindAsync(_userIdentity.Id) } : + // (await connection.Users.ListAsync()) + // .Where(u => _userIdentity.Name == null || _userIdentity.Name == u.Username); + // + // _output.ListEntities(list); + + await Task.Delay(1); + return 0; + } +} \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs new file mode 100644 index 00000000..0b390791 --- /dev/null +++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs @@ -0,0 +1,59 @@ +// Copyright 2018 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. +// 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 SeqCli.Config; +using SeqCli.Connection; +using Serilog; + +namespace SeqCli.Cli.Commands.Index; + +[Command("index", "suppress", "Suppress index", Example="seqcli index suppress -i signal-6543")] +class SuppressCommand : Command +{ + readonly SeqConnectionFactory _connectionFactory; + readonly ConnectionFeature _connection; + string? _id; + + public SuppressCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) + { + if (config == null) throw new ArgumentNullException(nameof(config)); + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + Options.Add( + "i=|id=", + "The id of an entity with an index to suppress", + id => _id = id); + + _connection = Enable(); + } + + protected override async Task Run() + { + if (_id == null) + { + Log.Error("An `id` must be specified"); + return 1; + } + + // var connection = _connectionFactory.Connect(_connection); + // var toRemove = await connection.RetentionPolicies.FindAsync(_id); + // await connection.RetentionPolicies.RemoveAsync(toRemove); + + await Task.Delay(1); + return 0; + } +} \ No newline at end of file From 1b26754d7f328f5f6af1409e1ad9663c4dab47b3 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Mon, 22 Apr 2024 16:01:04 +1000 Subject: [PATCH 02/16] Adding index commands --- .../Commands/ExpressionIndex/ListCommand.cs | 33 +++++++++++++++++++ src/SeqCli/Cli/Commands/Index/ListCommand.cs | 9 ++--- .../Cli/Commands/Index/SuppressCommand.cs | 14 +++++--- src/SeqCli/SeqCli.csproj | 2 +- .../Index/IndexBasicsTestCase.cs | 22 +++++++++++++ 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs create mode 100644 test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs new file mode 100644 index 00000000..172cb04a --- /dev/null +++ b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using SeqCli.Cli.Features; +using SeqCli.Config; +using SeqCli.Connection; + +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; + + public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) + { + if (config == null) throw new ArgumentNullException(nameof(config)); + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + _output = Enable(new OutputFormatFeature(config.Output)); + _connection = Enable(); + } + + protected override async Task Run() + { + var connection = _connectionFactory.Connect(_connection); + var list = await connection.ExpressionIndexes.ListAsync(); + _output.ListEntities(list); + return 0; + } +} \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Index/ListCommand.cs b/src/SeqCli/Cli/Commands/Index/ListCommand.cs index a3eacb61..6780f17a 100644 --- a/src/SeqCli/Cli/Commands/Index/ListCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/ListCommand.cs @@ -42,12 +42,9 @@ protected override async Task Run() { var connection = _connectionFactory.Connect(_connection); - // var list = _userIdentity.Id != null ? - // new[] { await connection.Users.FindAsync(_userIdentity.Id) } : - // (await connection.Users.ListAsync()) - // .Where(u => _userIdentity.Name == null || _userIdentity.Name == u.Username); - // - // _output.ListEntities(list); + var list = await connection.Indexes.ListAsync(); + + _output.ListEntities(list); await Task.Delay(1); return 0; diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs index 0b390791..a24c6b5c 100644 --- a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs @@ -14,6 +14,7 @@ using System; using System.Threading.Tasks; +using Seq.Api.Model.Indexes; using SeqCli.Cli.Features; using SeqCli.Config; using SeqCli.Connection; @@ -35,7 +36,7 @@ public SuppressCommand(SeqConnectionFactory connectionFactory, SeqCliConfig conf Options.Add( "i=|id=", - "The id of an entity with an index to suppress", + "The id of a signal index to suppress", id => _id = id); _connection = Enable(); @@ -49,9 +50,14 @@ protected override async Task Run() return 1; } - // var connection = _connectionFactory.Connect(_connection); - // var toRemove = await connection.RetentionPolicies.FindAsync(_id); - // await connection.RetentionPolicies.RemoveAsync(toRemove); + var connection = _connectionFactory.Connect(_connection); + var toSuppress = await connection.Indexes.FindAsync(_id); + if (toSuppress.IndexedEntityType != IndexedEntityType.Signal) + { + Log.Error("Only Signal indexes may be suppressed; to delete an expression index or an alert index remove the expression index or alert"); + return 1; + } + await connection.Indexes.SuppressAsync(toSuppress); await Task.Delay(1); return 0; diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj index 69b925f7..52507e05 100644 --- a/src/SeqCli/SeqCli.csproj +++ b/src/SeqCli/SeqCli.csproj @@ -28,6 +28,7 @@ + @@ -38,7 +39,6 @@ - diff --git a/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs b/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs new file mode 100644 index 00000000..5969524c --- /dev/null +++ b/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Seq.Api; +using SeqCli.EndToEnd.Support; +using Serilog; +using Xunit; + +namespace SeqCli.EndToEnd.Index; + +public class IndexBasicsTestCase : ICliTestCase +{ + public Task ExecuteAsync( + SeqConnection connection, + ILogger logger, + CliCommandRunner runner) + { + var exit = runner.Exec("index list", ""); + Assert.Equal(0, exit); + var output = runner.LastRunProcess?.Output; + Assert.Equal("list output goes here", output?.Trim()); + return Task.CompletedTask; + } +} \ No newline at end of file From 0e985ff38125bbcaee3a50b22a50357d1addd835 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Tue, 23 Apr 2024 13:57:52 +1000 Subject: [PATCH 03/16] Complete index and expression index commands --- .../Commands/ExpressionIndex/CreateCommand.cs | 70 +++++++++++++++++++ .../Commands/ExpressionIndex/ListCommand.cs | 10 ++- .../Commands/ExpressionIndex/RemoveCommand.cs | 59 ++++++++++++++++ src/SeqCli/Cli/Commands/Index/ListCommand.cs | 12 +++- .../Cli/Commands/Index/SuppressCommand.cs | 4 +- src/SeqCli/SeqCli.csproj | 2 +- src/SeqCli/Templates/Export/EntityName.cs | 5 ++ .../Templates/Export/TemplateSetExporter.cs | 8 +++ .../Templates/Import/TemplateSetImporter.cs | 2 +- 9 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs create mode 100644 src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs new file mode 100644 index 00000000..e72199b5 --- /dev/null +++ b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs @@ -0,0 +1,70 @@ +// 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. +// 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 Seq.Api.Model.Signals; +using SeqCli.Cli.Features; +using SeqCli.Config; +using SeqCli.Connection; +using SeqCli.Signals; +using SeqCli.Syntax; +using SeqCli.Util; +using Serilog; + +namespace SeqCli.Cli.Commands.ExpressionIndex; + +[Command("expressionindex", "create", "Create an expression index", + Example = "seqcli expressionindex create --expression \"ServerName\"")] +class CreateCommand : Command +{ + readonly SeqConnectionFactory _connectionFactory; + + readonly ConnectionFeature _connection; + readonly OutputFormatFeature _output; + + string? _expression; + + public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + Options.Add( + "expression=", + "The expression to index", + v => _expression = ArgumentString.Normalize(v)); + + _connection = Enable(); + _output = Enable(new OutputFormatFeature(config.Output)); + } + + protected override async Task Run() + { + var connection = _connectionFactory.Connect(_connection); + + if (string.IsNullOrEmpty(_expression)) + { + Log.Error("An `expression` must be specified"); + return 1; + } + + var index = await connection.ExpressionIndexes.TemplateAsync(); + index.Expression = _expression; + index = await connection.ExpressionIndexes.AddAsync(index); + + _output.WriteEntity(index); + + return 0; + } +} \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs index 172cb04a..6836d3f1 100644 --- a/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs +++ b/src/SeqCli/Cli/Commands/ExpressionIndex/ListCommand.cs @@ -13,12 +13,18 @@ class ListCommand : Command readonly ConnectionFeature _connection; readonly OutputFormatFeature _output; + string? _id; public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) { 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)); _connection = Enable(); } @@ -26,7 +32,9 @@ public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) protected override async Task Run() { var connection = _connectionFactory.Connect(_connection); - var list = await connection.ExpressionIndexes.ListAsync(); + var list = _id is not null + ? [await connection.ExpressionIndexes.FindAsync(_id)] + : await connection.ExpressionIndexes.ListAsync(); _output.ListEntities(list); return 0; } diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs new file mode 100644 index 00000000..c7ebee37 --- /dev/null +++ b/src/SeqCli/Cli/Commands/ExpressionIndex/RemoveCommand.cs @@ -0,0 +1,59 @@ +// Copyright 2018 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. +// 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.Linq; +using System.Threading.Tasks; +using SeqCli.Cli.Features; +using SeqCli.Connection; +using Serilog; + +namespace SeqCli.Cli.Commands.ExpressionIndex; + +[Command("expressionindex", "remove", "Remove an expression index from the server", + Example = "seqcli expressionindex -i expressionindex-2529")] +class RemoveCommand : Command +{ + readonly SeqConnectionFactory _connectionFactory; + + readonly ConnectionFeature _connection; + string? _id; + + public RemoveCommand(SeqConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + Options.Add( + "i=|id=", + "The id of an expression index to remove", + id => _id = id); + + _connection = Enable(); + } + + protected override async Task Run() + { + if (_id == null) + { + Log.Error("An `id` must be specified"); + return 1; + } + + var connection = _connectionFactory.Connect(_connection); + var toRemove = await connection.ExpressionIndexes.FindAsync(_id); + await connection.ExpressionIndexes.RemoveAsync(toRemove); + + return 0; + } +} \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Index/ListCommand.cs b/src/SeqCli/Cli/Commands/Index/ListCommand.cs index 6780f17a..ec8dc446 100644 --- a/src/SeqCli/Cli/Commands/Index/ListCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/ListCommand.cs @@ -13,8 +13,10 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Seq.Api.Model.Indexes; using SeqCli.Cli.Features; using SeqCli.Config; using SeqCli.Connection; @@ -28,11 +30,17 @@ class ListCommand : Command readonly ConnectionFeature _connection; readonly OutputFormatFeature _output; + string? _id; public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config) { 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)); _connection = Enable(); @@ -42,7 +50,9 @@ protected override async Task Run() { var connection = _connectionFactory.Connect(_connection); - var list = await connection.Indexes.ListAsync(); + var list = _id is not null + ? [await connection.Indexes.FindAsync(_id)] + : await connection.Indexes.ListAsync(); _output.ListEntities(list); diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs index a24c6b5c..4dd760db 100644 --- a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs @@ -22,7 +22,7 @@ namespace SeqCli.Cli.Commands.Index; -[Command("index", "suppress", "Suppress index", Example="seqcli index suppress -i signal-6543")] +[Command("index", "suppress", "Suppress index", Example="seqcli index suppress -i index-2191448f1d9b4f22bd32c6edef752748")] class SuppressCommand : Command { readonly SeqConnectionFactory _connectionFactory; @@ -36,7 +36,7 @@ public SuppressCommand(SeqConnectionFactory connectionFactory, SeqCliConfig conf Options.Add( "i=|id=", - "The id of a signal index to suppress", + "The id of an index of a signal to suppress", id => _id = id); _connection = Enable(); diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj index 52507e05..983bf911 100644 --- a/src/SeqCli/SeqCli.csproj +++ b/src/SeqCli/SeqCli.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/SeqCli/Templates/Export/EntityName.cs b/src/SeqCli/Templates/Export/EntityName.cs index 34512914..cad514d3 100644 --- a/src/SeqCli/Templates/Export/EntityName.cs +++ b/src/SeqCli/Templates/Export/EntityName.cs @@ -15,6 +15,11 @@ public static string FromEntityType(Type entityType) public static string ToResourceGroup(string resource) { + if (resource.Equals("expressionindex")) + { + return "expressionindexes"; + } + if (!resource.EndsWith("y")) return resource + "s"; diff --git a/src/SeqCli/Templates/Export/TemplateSetExporter.cs b/src/SeqCli/Templates/Export/TemplateSetExporter.cs index 051a3cda..0bfa38c5 100644 --- a/src/SeqCli/Templates/Export/TemplateSetExporter.cs +++ b/src/SeqCli/Templates/Export/TemplateSetExporter.cs @@ -8,6 +8,7 @@ using Seq.Api.Model; using Seq.Api.Model.Alerting; using Seq.Api.Model.Dashboarding; +using Seq.Api.Model.Indexing; using Seq.Api.Model.Retention; using Seq.Api.Model.Signals; using Seq.Api.Model.SqlQueries; @@ -78,6 +79,13 @@ await ExportTemplates( () => _connection.RetentionPolicies.ListAsync(), retentionPolicy => retentionPolicy.Id.Replace("retentionpolicy-", ""), templateValueMap); + + await ExportTemplates( + id => _connection.ExpressionIndexes.FindAsync(id), + () => _connection.ExpressionIndexes.ListAsync(), + expressionIndex => expressionIndex.Id.Replace("expressionindex-", ""), + templateValueMap); + } async Task ExportTemplates( diff --git a/src/SeqCli/Templates/Import/TemplateSetImporter.cs b/src/SeqCli/Templates/Import/TemplateSetImporter.cs index 8ac069c8..dd04ce6d 100644 --- a/src/SeqCli/Templates/Import/TemplateSetImporter.cs +++ b/src/SeqCli/Templates/Import/TemplateSetImporter.cs @@ -39,7 +39,7 @@ static class TemplateSetImporter bool merge) { var ordering = new[] {"users", "signals", "apps", "appinstances", - "dashboards", "sqlqueries", "workspaces", "retentionpolicies", "alerts"}.ToList(); + "dashboards", "sqlqueries", "workspaces", "retentionpolicies", "alerts", "expressionindexes"}.ToList(); var sorted = templates.OrderBy(t => ordering.IndexOf(t.ResourceGroup)); From 494999a16ff2ea4ba96a3bb162c87ba3dcce69e9 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Tue, 23 Apr 2024 13:59:48 +1000 Subject: [PATCH 04/16] Delete test --- .../Index/IndexBasicsTestCase.cs | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs diff --git a/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs b/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs deleted file mode 100644 index 5969524c..00000000 --- a/test/SeqCli.EndToEnd/Index/IndexBasicsTestCase.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Seq.Api; -using SeqCli.EndToEnd.Support; -using Serilog; -using Xunit; - -namespace SeqCli.EndToEnd.Index; - -public class IndexBasicsTestCase : ICliTestCase -{ - public Task ExecuteAsync( - SeqConnection connection, - ILogger logger, - CliCommandRunner runner) - { - var exit = runner.Exec("index list", ""); - Assert.Equal(0, exit); - var output = runner.LastRunProcess?.Output; - Assert.Equal("list output goes here", output?.Trim()); - return Task.CompletedTask; - } -} \ No newline at end of file From 917cf128bdb0863028cf4ad8999409eca015b0d4 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Tue, 23 Apr 2024 16:30:38 +1000 Subject: [PATCH 05/16] PR feedback --- .../Cli/Commands/ExpressionIndex/CreateCommand.cs | 2 +- src/SeqCli/Cli/Commands/Index/ListCommand.cs | 1 - src/SeqCli/Cli/Commands/Index/SuppressCommand.cs | 8 +------- src/SeqCli/Templates/Export/EntityName.cs | 14 ++++++++------ src/SeqCli/Templates/Export/TemplateSetExporter.cs | 7 ++++--- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs index e72199b5..5fc39086 100644 --- a/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs +++ b/src/SeqCli/Cli/Commands/ExpressionIndex/CreateCommand.cs @@ -41,7 +41,7 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); Options.Add( - "expression=", + "e=|expression=", "The expression to index", v => _expression = ArgumentString.Normalize(v)); diff --git a/src/SeqCli/Cli/Commands/Index/ListCommand.cs b/src/SeqCli/Cli/Commands/Index/ListCommand.cs index ec8dc446..958451b5 100644 --- a/src/SeqCli/Cli/Commands/Index/ListCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/ListCommand.cs @@ -56,7 +56,6 @@ protected override async Task Run() _output.ListEntities(list); - await Task.Delay(1); return 0; } } \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs index 4dd760db..84f5d628 100644 --- a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs @@ -36,7 +36,7 @@ public SuppressCommand(SeqConnectionFactory connectionFactory, SeqCliConfig conf Options.Add( "i=|id=", - "The id of an index of a signal to suppress", + "The id of an index to suppress", id => _id = id); _connection = Enable(); @@ -52,14 +52,8 @@ protected override async Task Run() var connection = _connectionFactory.Connect(_connection); var toSuppress = await connection.Indexes.FindAsync(_id); - if (toSuppress.IndexedEntityType != IndexedEntityType.Signal) - { - Log.Error("Only Signal indexes may be suppressed; to delete an expression index or an alert index remove the expression index or alert"); - return 1; - } await connection.Indexes.SuppressAsync(toSuppress); - await Task.Delay(1); return 0; } } \ No newline at end of file diff --git a/src/SeqCli/Templates/Export/EntityName.cs b/src/SeqCli/Templates/Export/EntityName.cs index cad514d3..c4bd5c40 100644 --- a/src/SeqCli/Templates/Export/EntityName.cs +++ b/src/SeqCli/Templates/Export/EntityName.cs @@ -15,14 +15,16 @@ public static string FromEntityType(Type entityType) public static string ToResourceGroup(string resource) { - if (resource.Equals("expressionindex")) + if (resource.EndsWith('y')) { - return "expressionindexes"; + return resource.TrimEnd('y') + "ies"; } - - if (!resource.EndsWith("y")) - return resource + "s"; - return resource.TrimEnd('y') + "ies"; + if (resource.EndsWith('x')) + { + return resource + "es"; + } + + return resource + "s"; } } \ No newline at end of file diff --git a/src/SeqCli/Templates/Export/TemplateSetExporter.cs b/src/SeqCli/Templates/Export/TemplateSetExporter.cs index 0bfa38c5..57d8d059 100644 --- a/src/SeqCli/Templates/Export/TemplateSetExporter.cs +++ b/src/SeqCli/Templates/Export/TemplateSetExporter.cs @@ -83,11 +83,12 @@ await ExportTemplates( await ExportTemplates( id => _connection.ExpressionIndexes.FindAsync(id), () => _connection.ExpressionIndexes.ListAsync(), - expressionIndex => expressionIndex.Id.Replace("expressionindex-", ""), + expressionIndex => expressionIndex.Expression.All(char.IsLetterOrDigit) + ? expressionIndex.Expression + : expressionIndex.Id.Replace("expressionindex-", ""), templateValueMap); - } - + async Task ExportTemplates( Func> findEntity, Func>> listEntities, From fed611a4db9238ba93d7f9255dad733a33952524 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 24 Apr 2024 09:21:02 +1000 Subject: [PATCH 06/16] Mark template export test case with min API version --- test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs index f97b7fdb..1286d37c 100644 --- a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs +++ b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs @@ -8,7 +8,7 @@ namespace SeqCli.EndToEnd.Templates; -[CliTestCase(MinimumApiVersion = "2021.3.6336")] +[CliTestCase(MinimumApiVersion = "2024.3.0")] public class TemplateExportImportTestCase : ICliTestCase { readonly TestDataFolder _testDataFolder; From e95cc7b62522f061a3dca66772ad79ac52feb2a3 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 24 Apr 2024 09:36:49 +1000 Subject: [PATCH 07/16] Version-gated end-to-end tests for new index-related commands --- .../Cli/Commands/Index/SuppressCommand.cs | 2 +- .../Indexes/ExpressionIndexBasicsTestCase.cs | 36 +++++++++++++++++++ .../Indexes/IndexesTestCase.cs | 34 ++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/SeqCli.EndToEnd/Indexes/ExpressionIndexBasicsTestCase.cs create mode 100644 test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs diff --git a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs index 84f5d628..5aa495bd 100644 --- a/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs +++ b/src/SeqCli/Cli/Commands/Index/SuppressCommand.cs @@ -22,7 +22,7 @@ namespace SeqCli.Cli.Commands.Index; -[Command("index", "suppress", "Suppress index", Example="seqcli index suppress -i index-2191448f1d9b4f22bd32c6edef752748")] +[Command("index", "suppress", "Suppress an index", Example="seqcli index suppress -i index-2191448f1d9b4f22bd32c6edef752748")] class SuppressCommand : Command { readonly SeqConnectionFactory _connectionFactory; diff --git a/test/SeqCli.EndToEnd/Indexes/ExpressionIndexBasicsTestCase.cs b/test/SeqCli.EndToEnd/Indexes/ExpressionIndexBasicsTestCase.cs new file mode 100644 index 00000000..4e7497d4 --- /dev/null +++ b/test/SeqCli.EndToEnd/Indexes/ExpressionIndexBasicsTestCase.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System.Threading.Tasks; +using Seq.Api; +using SeqCli.EndToEnd.Support; +using Serilog; +using Xunit; + +namespace SeqCli.EndToEnd.Indexes; + +[CliTestCase(MinimumApiVersion = "2024.3.0")] +public class ExpressionIndexBasicsTestCase: ICliTestCase +{ + public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRunner runner) + { + const string expr = "@Resource.service.name"; + var exit = runner.Exec("expressionindex create", $"-e {expr}"); + Assert.Equal(0, exit); + + var entity = (await connection.ExpressionIndexes.ListAsync()).Single(e => e.Expression == expr); + Assert.Equal(expr, entity.Expression); + + exit = runner.Exec("expressionindex list"); + Assert.Equal(0, exit); + + Assert.Contains(expr, runner.LastRunProcess!.Output); + Assert.Contains(entity.Id, runner.LastRunProcess.Output); + + exit = runner.Exec("expressionindex remove", $"-i {entity.Id}"); + Assert.Equal(0, exit); + + exit = runner.Exec("expressionindex list"); + Assert.Equal(0, exit); + + Assert.DoesNotContain(entity.Id, runner.LastRunProcess.Output); + } +} \ No newline at end of file diff --git a/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs new file mode 100644 index 00000000..84b82f03 --- /dev/null +++ b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Threading.Tasks; +using Seq.Api; +using SeqCli.EndToEnd.Support; +using Serilog; +using Xunit; + +namespace SeqCli.EndToEnd.Indexes; + +[CliTestCase(MinimumApiVersion = "2024.3.0")] +public class IndexesTestCase: ICliTestCase +{ + public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRunner runner) + { + const string expr = "Magic123"; + var exit = runner.Exec("expressionindex create", $"-e {expr}"); + Assert.Equal(0, exit); + + var expressionIndex = (await connection.ExpressionIndexes.ListAsync()).Single(e => e.Expression == expr); + var signal = (await connection.Signals.ListAsync()).First(s => !s.IsIndexSuppressed); + var indexForSignal = (await connection.Indexes.ListAsync()).First(i => i.IndexedEntityId == signal.Id); + + exit = runner.Exec("index list"); + Assert.Equal(0, exit); + Assert.Contains(expressionIndex.Id, runner.LastRunProcess!.Output); + Assert.Contains(signal.Id, runner.LastRunProcess!.Output); + + exit = runner.Exec($"index suppress -i {indexForSignal.Id}"); + Assert.Equal(0, exit); + + signal = await connection.Signals.FindAsync(signal.Id); + Assert.True(signal.IsIndexSuppressed); + } +} From ed4691c31f596bebfb104ec306a4229ad789a733 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 24 Apr 2024 10:10:56 +1000 Subject: [PATCH 08/16] Missed the 2024.3 version update in #337 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 98e38046..74bd2fdb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2024.2.{build} +version: 2024.3.{build} skip_tags: true image: - Visual Studio 2022 From 1c5f9051293709fd35f50f68cdfca6590e07e768 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Mon, 29 Apr 2024 08:28:33 +1000 Subject: [PATCH 09/16] Add example signal expression --- src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs index bed979df..9bc2edee 100644 --- a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs +++ b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs @@ -52,7 +52,7 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config Options.Add( "stream:", - "Stream incoming events to this app instance as they're ingested; optionally accepts a signal expression limiting which events should be streamed", + "Stream incoming events to this app instance as they're ingested; optionally accepts a signal expression limiting which events should be streamed, for example `signal-1,signal-2`", s => { _streamIncomingEvents = true; From 1bf380d860437e221a6d030afc0e7b45f19a1a96 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 29 Apr 2024 10:42:53 +1000 Subject: [PATCH 10/16] Use required keyword; ordinal string comparison --- src/Roastery/Data/Database.cs | 1 - src/Roastery/Web/RequestLoggingMiddleware.cs | 1 - src/SeqCli/Cli/CommandMetadata.cs | 4 ++-- src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs | 2 +- src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs | 4 ++-- src/SeqCli/Cli/Commands/LogCommand.cs | 2 +- src/SeqCli/Sample/Loader/Simulation.cs | 1 - src/SeqCli/Syntax/DurationMoniker.cs | 2 +- src/SeqCli/Templates/Import/TemplateSetImporter.cs | 3 ++- 9 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Roastery/Data/Database.cs b/src/Roastery/Data/Database.cs index 067dea7b..a166d5b7 100644 --- a/src/Roastery/Data/Database.cs +++ b/src/Roastery/Data/Database.cs @@ -9,7 +9,6 @@ using Serilog; using Serilog.Events; using SerilogTracing; -using SerilogTracing.Instrumentation; namespace Roastery.Data; diff --git a/src/Roastery/Web/RequestLoggingMiddleware.cs b/src/Roastery/Web/RequestLoggingMiddleware.cs index ea55f25f..f0cdb784 100644 --- a/src/Roastery/Web/RequestLoggingMiddleware.cs +++ b/src/Roastery/Web/RequestLoggingMiddleware.cs @@ -5,7 +5,6 @@ using Serilog.Context; using Serilog.Events; using SerilogTracing; -using SerilogTracing.Instrumentation; namespace Roastery.Web; diff --git a/src/SeqCli/Cli/CommandMetadata.cs b/src/SeqCli/Cli/CommandMetadata.cs index e691cce7..0feed60e 100644 --- a/src/SeqCli/Cli/CommandMetadata.cs +++ b/src/SeqCli/Cli/CommandMetadata.cs @@ -16,8 +16,8 @@ namespace SeqCli.Cli; public class CommandMetadata : ICommandMetadata { - public string Name { get; set; } = null!; + public required string Name { get; set; } public string? SubCommand { get; set; } - public string HelpText { get; set; } = null!; + public required string HelpText { get; set; } public string? Example { get; set; } } \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs index 9bc2edee..5b71c2cc 100644 --- a/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs +++ b/src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs @@ -116,4 +116,4 @@ bool ValidateSettingName(string settingName) return 0; } -} \ No newline at end of file +} diff --git a/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs b/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs index 42fcaa99..ce31c26e 100644 --- a/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs +++ b/src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs @@ -18,8 +18,8 @@ namespace SeqCli.Cli.Commands.Bench; class QueryBenchCase { - public string Id { get; set; } = null!; - public string Query { get; set; } = null!; + public required string Id { get; set; } + public required string Query { get; set; } public string? SignalExpression { get; set; } // Not used programmatically at this time. diff --git a/src/SeqCli/Cli/Commands/LogCommand.cs b/src/SeqCli/Cli/Commands/LogCommand.cs index ee79356c..536245a9 100644 --- a/src/SeqCli/Cli/Commands/LogCommand.cs +++ b/src/SeqCli/Cli/Commands/LogCommand.cs @@ -86,7 +86,7 @@ protected override async Task Run() continue; var name = key.Trim(); - if (name.StartsWith("@")) + if (name.StartsWith('@')) name = $"@{name}"; payload[name] = new JValue(value); diff --git a/src/SeqCli/Sample/Loader/Simulation.cs b/src/SeqCli/Sample/Loader/Simulation.cs index 56f5a37d..fba0a939 100644 --- a/src/SeqCli/Sample/Loader/Simulation.cs +++ b/src/SeqCli/Sample/Loader/Simulation.cs @@ -17,7 +17,6 @@ using Seq.Api; using SeqCli.Ingestion; using Serilog; -using SerilogTracing; namespace SeqCli.Sample.Loader; diff --git a/src/SeqCli/Syntax/DurationMoniker.cs b/src/SeqCli/Syntax/DurationMoniker.cs index 59919600..c02a2398 100644 --- a/src/SeqCli/Syntax/DurationMoniker.cs +++ b/src/SeqCli/Syntax/DurationMoniker.cs @@ -50,7 +50,7 @@ public static TimeSpan ToTimeSpan(string duration) // This is not at all robust; we could use a decent duration parser for use here in `seqcli`. - if (duration.EndsWith("ms")) + if (duration.EndsWith("ms", StringComparison.Ordinal)) return TimeSpan.FromMilliseconds(int.Parse(duration[..^2])); var value = int.Parse(duration[..^1], CultureInfo.InvariantCulture); diff --git a/src/SeqCli/Templates/Import/TemplateSetImporter.cs b/src/SeqCli/Templates/Import/TemplateSetImporter.cs index dd04ce6d..91b76add 100644 --- a/src/SeqCli/Templates/Import/TemplateSetImporter.cs +++ b/src/SeqCli/Templates/Import/TemplateSetImporter.cs @@ -39,7 +39,8 @@ static class TemplateSetImporter bool merge) { var ordering = new[] {"users", "signals", "apps", "appinstances", - "dashboards", "sqlqueries", "workspaces", "retentionpolicies", "alerts", "expressionindexes"}.ToList(); + "dashboards", "sqlqueries", "workspaces", "retentionpolicies", + "alerts", "expressionindexes"}.ToList(); var sorted = templates.OrderBy(t => ordering.IndexOf(t.ResourceGroup)); From fd94db46c845d5173d13dab887bc1dea1f27dbb0 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 29 Apr 2024 11:03:11 +1000 Subject: [PATCH 11/16] Fixes in test project --- test/SeqCli.Tests/Cli/CommandLineHostTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/SeqCli.Tests/Cli/CommandLineHostTests.cs b/test/SeqCli.Tests/Cli/CommandLineHostTests.cs index 9d9ba88a..fdd0b28f 100644 --- a/test/SeqCli.Tests/Cli/CommandLineHostTests.cs +++ b/test/SeqCli.Tests/Cli/CommandLineHostTests.cs @@ -20,10 +20,10 @@ public async Task CheckCommandLineHostPicksCorrectCommand() { new( new Lazy(() => new ActionCommand(() => executed.Add("test"))), - new CommandMetadata {Name = "test"}), + new CommandMetadata {Name = "test", HelpText = "help"}), new( new Lazy(() => new ActionCommand(() => executed.Add("test2"))), - new CommandMetadata {Name = "test2"}) + new CommandMetadata {Name = "test2", HelpText = "help"}) }; var commandLineHost = new CommandLineHost(availableCommands); await commandLineHost.Run(new []{ "test"},new LoggingLevelSwitch()); @@ -40,10 +40,10 @@ public async Task WhenMoreThanOneSubcommandAndTheUserRunsWithSubcommandEnsurePic { new( new Lazy(() => new ActionCommand(() => commandsRan.Add("test-subcommand1"))), - new CommandMetadata {Name = "test", SubCommand = "subcommand1"}), + new CommandMetadata {Name = "test", SubCommand = "subcommand1", HelpText = "help"}), new( new Lazy(() => new ActionCommand(() => commandsRan.Add("test-subcommand2"))), - new CommandMetadata {Name = "test", SubCommand = "subcommand2"}) + new CommandMetadata {Name = "test", SubCommand = "subcommand2", HelpText = "help"}) }; var commandLineHost = new CommandLineHost(availableCommands); await commandLineHost.Run(new[] { "test", "subcommand2" }, new LoggingLevelSwitch()); @@ -61,12 +61,12 @@ public async Task VerboseOptionSetsLoggingLevelToInformation() { new( new Lazy(() => new ActionCommand(() => { })), - new CommandMetadata {Name = "test"}) + new CommandMetadata {Name = "test", HelpText = "help"}) }; var commandLineHost = new CommandLineHost(availableCommands); - await commandLineHost.Run(new[] { "test", "--verbose" }, levelSwitch); + await commandLineHost.Run(["test", "--verbose"], levelSwitch); Assert.Equal(LogEventLevel.Information, levelSwitch.MinimumLevel); } From 10797cc962b1bf741455c5b4330b9da6dd91ef62 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 29 Apr 2024 13:44:50 +1000 Subject: [PATCH 12/16] Launch configurations for local executable vs Docker/latest vs Docker/preview --- test/SeqCli.EndToEnd/Args.cs | 34 +++++++++++-------- .../Support/CliCommandRunner.cs | 10 ++---- ...nseCliSetupTestCase.cs => LicenseSetup.cs} | 9 ++--- .../Support/TestConfiguration.cs | 19 ++++------- 4 files changed, 30 insertions(+), 42 deletions(-) rename test/SeqCli.EndToEnd/Support/{LicenseCliSetupTestCase.cs => LicenseSetup.cs} (82%) diff --git a/test/SeqCli.EndToEnd/Args.cs b/test/SeqCli.EndToEnd/Args.cs index 258e17f8..4e1701ea 100644 --- a/test/SeqCli.EndToEnd/Args.cs +++ b/test/SeqCli.EndToEnd/Args.cs @@ -1,26 +1,32 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text.RegularExpressions; +#nullable enable + namespace SeqCli.EndToEnd; -public class Args +public class Args(params string[] args) { - readonly string[] _args; - - public Args(params string[] args) - { - _args = args; - } - - public Regex[] TestCases() => _args + public Regex[] TestCases() => args .Where(arg => !arg.StartsWith("--")) .Select(ToArgRegex) .ToArray(); // Simple replacement so `Events.*` becomes `Events\..*` - static Regex ToArgRegex(string arg) => new Regex(arg.Replace(".", "\\.").Replace("*", ".*")); + static Regex ToArgRegex(string arg) => new(arg.Replace(".", "\\.").Replace("*", ".*")); - public bool Multiuser() => _args.Any(a => a == "--license-certificate-stdin"); + public bool Multiuser() => args.Any(a => a == "--license-certificate-stdin"); - public bool UseDockerSeq() => _args.Any(a => a == "--docker-server"); -} \ No newline at end of file + public bool UseDockerSeq([NotNullWhen(true)] out string? imageTag) + { + if (args.Any(a => a == "--docker-server")) + { + imageTag = args.Any(a => a == "--pre") ? "preview" : "latest"; + return true; + } + + imageTag = null; + return false; + } +} diff --git a/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs b/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs index f17e6335..1955d913 100644 --- a/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs +++ b/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs @@ -4,21 +4,15 @@ namespace SeqCli.EndToEnd.Support; -public class CliCommandRunner +public class CliCommandRunner(TestConfiguration configuration) { - readonly TestConfiguration _configuration; static readonly TimeSpan DefaultExecTimeout = TimeSpan.FromSeconds(10); public ITestProcess? LastRunProcess { get; private set; } - public CliCommandRunner(TestConfiguration configuration) - { - _configuration = configuration; - } - public int Exec(string command, string? args = null, bool disconnected = false) { - using var process = _configuration.SpawnCliProcess(command, args, skipServerArg: disconnected); + using var process = configuration.SpawnCliProcess(command, args, skipServerArg: disconnected); LastRunProcess = process; return process.WaitForExit(DefaultExecTimeout); } diff --git a/test/SeqCli.EndToEnd/Support/LicenseCliSetupTestCase.cs b/test/SeqCli.EndToEnd/Support/LicenseSetup.cs similarity index 82% rename from test/SeqCli.EndToEnd/Support/LicenseCliSetupTestCase.cs rename to test/SeqCli.EndToEnd/Support/LicenseSetup.cs index fe6c44a3..67dcfabc 100644 --- a/test/SeqCli.EndToEnd/Support/LicenseCliSetupTestCase.cs +++ b/test/SeqCli.EndToEnd/Support/LicenseSetup.cs @@ -5,18 +5,13 @@ namespace SeqCli.EndToEnd.Support; -public class LicenseSetup +public class LicenseSetup(Args args) { - readonly bool _enabled; + readonly bool _enabled = args.Multiuser(); bool _attempted; string _certificate; - public LicenseSetup(Args args) - { - _enabled = args.Multiuser(); - } - public async Task SetupAsync( SeqConnection connection, ILogger logger) diff --git a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs index fd872954..ee80491a 100644 --- a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs +++ b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs @@ -4,15 +4,8 @@ namespace SeqCli.EndToEnd.Support; -public class TestConfiguration +public class TestConfiguration(Args args) { - readonly Args _args; - - public TestConfiguration(Args args) - { - _args = args; - } - static int ServerListenPort => 9989; #pragma warning disable CA1822 @@ -24,7 +17,7 @@ public TestConfiguration(Args args) public string TestedBinary => Path.Combine(EquivalentBaseDirectory, "seqcli.dll"); - public bool IsMultiuser => _args.Multiuser(); + public bool IsMultiuser => args.Multiuser(); public CaptiveProcess SpawnCliProcess(string command, string additionalArgs = null, Dictionary environment = null, bool skipServerArg = false) { @@ -34,8 +27,7 @@ public CaptiveProcess SpawnCliProcess(string command, string additionalArgs = nu if (!skipServerArg) commandWithArgs += $" --server=\"{ServerListenUrl}\""; - var args = $"{TestedBinary} {commandWithArgs}"; - return new CaptiveProcess("dotnet", args, environment); + return new CaptiveProcess("dotnet", $"{TestedBinary} {commandWithArgs}", environment); } public CaptiveProcess SpawnServerProcess(string storagePath) @@ -43,11 +35,12 @@ public CaptiveProcess SpawnServerProcess(string storagePath) if (storagePath == null) throw new ArgumentNullException(nameof(storagePath)); var commandWithArgs = $"run --listen=\"{ServerListenUrl}\" --storage=\"{storagePath}\""; - if (_args.UseDockerSeq()) + if (args.UseDockerSeq(out var imageTag)) { var containerName = Guid.NewGuid().ToString("n"); - return new CaptiveProcess("docker", $"run --name {containerName} -it --rm -e ACCEPT_EULA=Y -p {ServerListenPort}:80 datalust/seq:latest", stopCommandFullExePath: "docker", stopCommandArgs: $"stop {containerName}"); + return new CaptiveProcess("docker", $"run --name {containerName} -it --rm -e ACCEPT_EULA=Y -p {ServerListenPort}:80 datalust/seq:{imageTag}", stopCommandFullExePath: "docker", stopCommandArgs: $"stop {containerName}"); } + return new CaptiveProcess("seq", commandWithArgs); } } From 72ed0a8c7114991fb5bc3ee35d977cd115e765a9 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 29 Apr 2024 16:38:22 +1000 Subject: [PATCH 13/16] launchSettings.json --- .../Properties/launchSettings.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/SeqCli.EndToEnd/Properties/launchSettings.json diff --git a/test/SeqCli.EndToEnd/Properties/launchSettings.json b/test/SeqCli.EndToEnd/Properties/launchSettings.json new file mode 100644 index 00000000..231d5959 --- /dev/null +++ b/test/SeqCli.EndToEnd/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "SeqCli.EndToEnd (Seq Executable)": { + "commandName": "Project" + }, + "SeqCli.EndToEnd (datalust/seq:latest)": { + "commandName": "Project", + "commandLineArgs": "--docker-server" + }, + "SeqCli.EndToEnd (datalust/seq:preview)": { + "commandName": "Project", + "commandLineArgs": "--docker-server --pre" + } + } +} From d1663bd8e8b66d0a747cffa588c1efabdda3b4c3 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 29 Apr 2024 16:39:35 +1000 Subject: [PATCH 14/16] Fix end-to-end indexing tests; some templating updates were needed --- .gitignore | 1 - .../Cli/Features/OutputFormatFeature.cs | 2 +- src/SeqCli/Templates/Import/GenericEntity.cs | 1 + .../Templates/Import/TemplateSetImporter.cs | 45 ++++++++++++++----- .../Indexes/IndexesTestCase.cs | 4 +- .../Templates/TemplateExportImportTestCase.cs | 2 +- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 940794e6..a52a7183 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ dlldata.c project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c diff --git a/src/SeqCli/Cli/Features/OutputFormatFeature.cs b/src/SeqCli/Cli/Features/OutputFormatFeature.cs index 8ae28432..f7ee857d 100644 --- a/src/SeqCli/Cli/Features/OutputFormatFeature.cs +++ b/src/SeqCli/Cli/Features/OutputFormatFeature.cs @@ -134,7 +134,7 @@ public void WriteEntity(Entity entity) else { var dyn = (dynamic) jo; - Console.WriteLine($"{entity.Id} {dyn.Title ?? dyn.Name ?? dyn.Username}"); + Console.WriteLine($"{entity.Id} {dyn.Title ?? dyn.Name ?? dyn.Username ?? dyn.Expression}"); } } diff --git a/src/SeqCli/Templates/Import/GenericEntity.cs b/src/SeqCli/Templates/Import/GenericEntity.cs index d86a28f0..48838362 100644 --- a/src/SeqCli/Templates/Import/GenericEntity.cs +++ b/src/SeqCli/Templates/Import/GenericEntity.cs @@ -21,4 +21,5 @@ class GenericEntity : Entity { public string? Title { get; set; } public string? Name { get; set; } + public string? Expression { get; set; } } \ No newline at end of file diff --git a/src/SeqCli/Templates/Import/TemplateSetImporter.cs b/src/SeqCli/Templates/Import/TemplateSetImporter.cs index 91b76add..fe2c3ed4 100644 --- a/src/SeqCli/Templates/Import/TemplateSetImporter.cs +++ b/src/SeqCli/Templates/Import/TemplateSetImporter.cs @@ -75,21 +75,40 @@ static class TemplateSetImporter var resourceGroupLink = template.ResourceGroup + "Resources"; var link = apiRoot.Links.Single(l => resourceGroupLink.Equals(l.Key, StringComparison.OrdinalIgnoreCase)); var resourceGroup = await connection.Client.GetAsync(apiRoot, link.Key); + + // ExpressionIndexes with mapped ids or identical expressions are assumed to be equivalent. + var immutableTarget = template.ResourceGroup != "ExpressionIndexes"; if (state.TryGetCreatedEntityId(template.Name, out var existingId) && await CheckEntityExistenceAsync(connection, resourceGroup, existingId)) { asObject["Id"] = existingId; - await UpdateEntityAsync(connection, resourceGroup, asObject, existingId); - Log.Information("Updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + if (immutableTarget) + { + Log.Information("No work required for existing immutable entity {EntityId} from {TemplateName}", existingId, template.Name); + } + else + { + await UpdateEntityAsync(connection, resourceGroup, asObject, existingId); + Log.Information("Updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + } } else if (merge && !state.TryGetCreatedEntityId(template.Name, out _) && await TryFindMergeTargetAsync(connection, resourceGroup, asObject) is { } mergedId) { asObject["Id"] = mergedId; - await UpdateEntityAsync(connection, resourceGroup, asObject, mergedId); - state.AddOrUpdateCreatedEntityId(template.Name, mergedId); - Log.Information("Merged and updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + + if (immutableTarget) + { + Log.Information("Adding merge entry for existing immutable entity {EntityId} from {TemplateName}", existingId, template.Name); + state.AddOrUpdateCreatedEntityId(template.Name, mergedId); + } + else + { + await UpdateEntityAsync(connection, resourceGroup, asObject, mergedId); + state.AddOrUpdateCreatedEntityId(template.Name, mergedId); + Log.Information("Merged and updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + } } else { @@ -104,20 +123,24 @@ await TryFindMergeTargetAsync(connection, resourceGroup, asObject) is { } merged static async Task TryFindMergeTargetAsync(SeqConnection connection, ResourceGroup resourceGroup, IDictionary entity) { if (!entity.TryGetValue("Title", out var nameOrTitleValue) && - !entity.TryGetValue("Name", out nameOrTitleValue) || + !entity.TryGetValue("Name", out nameOrTitleValue) && + !entity.TryGetValue("Expression", out nameOrTitleValue)|| nameOrTitleValue is not string nameOrTitle) { return null; } - // O(Ntemplates*Nentities) - easy target for optimization with some caching. - var candidates = await connection.Client.GetAsync>(resourceGroup, "Items", - new Dictionary + var parameters = resourceGroup.Links["Items"].Template.Contains("shared") + ? new Dictionary { ["shared"] = true - }); + } + : null; + + // O(Ntemplates*Nentities) - easy target for optimization with some caching. + var candidates = await connection.Client.GetAsync>(resourceGroup, "Items", parameters); - return candidates.FirstOrDefault(e => e.Title == nameOrTitle || e.Name == nameOrTitle)?.Id; + return candidates.FirstOrDefault(e => e.Title == nameOrTitle || e.Name == nameOrTitle || e.Expression == nameOrTitle)?.Id; } static async Task CreateEntityAsync(SeqConnection connection, ResourceGroup resourceGroup, object entity) diff --git a/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs index 84b82f03..f87608b8 100644 --- a/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs +++ b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs @@ -17,10 +17,10 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm Assert.Equal(0, exit); var expressionIndex = (await connection.ExpressionIndexes.ListAsync()).Single(e => e.Expression == expr); - var signal = (await connection.Signals.ListAsync()).First(s => !s.IsIndexSuppressed); + var signal = (await connection.Signals.ListAsync(shared: true)).First(s => !s.IsIndexSuppressed); var indexForSignal = (await connection.Indexes.ListAsync()).First(i => i.IndexedEntityId == signal.Id); - exit = runner.Exec("index list"); + exit = runner.Exec("index list --json"); Assert.Equal(0, exit); Assert.Contains(expressionIndex.Id, runner.LastRunProcess!.Output); Assert.Contains(signal.Id, runner.LastRunProcess!.Output); diff --git a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs index 1286d37c..1b5dc311 100644 --- a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs +++ b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs @@ -43,7 +43,7 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm await File.WriteAllTextAsync(exportedFilename, content); - exit = runner.Exec("template import", $"-i \"{_testDataFolder.Path}\""); + exit = runner.Exec("template import", $"-i \"{_testDataFolder.Path}\" --merge"); Assert.Equal(0, exit); var created = Assert.Single(await connection.Signals.ListAsync(shared: true), s => s.Title == newTitle); From 334583b1ed5c191742e90f50c108b39dfe015fa6 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 30 Apr 2024 11:42:27 +1000 Subject: [PATCH 15/16] seqcli app uninstall --- .../Cli/Commands/App/UninstallCommand.cs | 64 +++++++++++++++++++ .../Cli/Features/EntityIdentityFeature.cs | 11 ++-- test/SeqCli.EndToEnd/App/AppBasicsTestCase.cs | 3 + 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/SeqCli/Cli/Commands/App/UninstallCommand.cs diff --git a/src/SeqCli/Cli/Commands/App/UninstallCommand.cs b/src/SeqCli/Cli/Commands/App/UninstallCommand.cs new file mode 100644 index 00000000..c9d275a9 --- /dev/null +++ b/src/SeqCli/Cli/Commands/App/UninstallCommand.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using SeqCli.Cli.Features; +using SeqCli.Connection; +using SeqCli.Util; +using Serilog; + +namespace SeqCli.Cli.Commands.App; + +[Command("app", "uninstall", "Uninstall an app package", + Example = "seqcli app uninstall --package-id 'Seq.App.JsonArchive'")] +// ReSharper disable once UnusedType.Global +class UninstallCommand : Command +{ + readonly SeqConnectionFactory _connectionFactory; + + string? _packageId, _id; + readonly ConnectionFeature _connection; + + public UninstallCommand(SeqConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + + Options.Add( + "package-id=", + "The package id of the app package to uninstall", + packageId => _packageId = ArgumentString.Normalize(packageId)); + + Options.Add( + "i=|id=", + "The id of a single app package to uninstall", + t => _id = ArgumentString.Normalize(t)); + + _connection = Enable(); + } + + protected override async Task Run() + { + if (_packageId == null && _id == null) + { + Log.Error("A `package-id` or `id` must be specified"); + return 1; + } + + var connection = _connectionFactory.Connect(_connection); + + var toRemove = _id != null ? [await connection.Apps.FindAsync(_id)] + : (await connection.Apps.ListAsync()) + .Where(app => _packageId == app.Package.PackageId) + .ToArray(); + + if (!toRemove.Any()) + { + Log.Error("No matching API key was found"); + return 1; + } + + foreach (var app in toRemove) + await connection.Apps.RemoveAsync(app); + + return 0; + } +} diff --git a/src/SeqCli/Cli/Features/EntityIdentityFeature.cs b/src/SeqCli/Cli/Features/EntityIdentityFeature.cs index e15dbcc6..a6f9dab2 100644 --- a/src/SeqCli/Cli/Features/EntityIdentityFeature.cs +++ b/src/SeqCli/Cli/Features/EntityIdentityFeature.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using SeqCli.Util; namespace SeqCli.Cli.Features; @@ -22,8 +23,6 @@ class EntityIdentityFeature : CommandFeature readonly string _entityName; readonly string _verb; - string? _title, _id; - public EntityIdentityFeature(string entityName, string verb) { _entityName = entityName ?? throw new ArgumentNullException(nameof(entityName)); @@ -35,12 +34,12 @@ public override void Enable(OptionSet options) options.Add( "t=|title=", $"The title of the {_entityName}(s) to {_verb}", - t => _title = t); + t => Title = ArgumentString.Normalize(t)); options.Add( "i=|id=", $"The id of a single {_entityName} to {_verb}", - t => _id = t); + t => Id = ArgumentString.Normalize(t)); } public override IEnumerable GetUsageErrors() @@ -49,7 +48,7 @@ public override IEnumerable GetUsageErrors() yield return "Only one of either `title` or `id` can be specified"; } - public string? Title => string.IsNullOrWhiteSpace(_title) ? null : _title.Trim(); + public string? Title { get; private set; } - public string? Id => string.IsNullOrWhiteSpace(_id) ? null : _id.Trim(); + public string? Id { get; private set; } } \ No newline at end of file diff --git a/test/SeqCli.EndToEnd/App/AppBasicsTestCase.cs b/test/SeqCli.EndToEnd/App/AppBasicsTestCase.cs index 638ca8b0..d8c8d63f 100644 --- a/test/SeqCli.EndToEnd/App/AppBasicsTestCase.cs +++ b/test/SeqCli.EndToEnd/App/AppBasicsTestCase.cs @@ -23,6 +23,9 @@ public Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRun exit = runner.Exec("app update", "--all"); Assert.Equal(0, exit); + exit = runner.Exec("app uninstall", "--package-id Seq.App.EmailPlus"); + Assert.Equal(0, exit); + return Task.CompletedTask; } } From 1ed88015e12094abf535d654f4481ce846716488 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Wed, 1 May 2024 14:19:40 +1000 Subject: [PATCH 16/16] update to stable 2024.3 release of Seq.Api --- src/SeqCli/SeqCli.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeqCli/SeqCli.csproj b/src/SeqCli/SeqCli.csproj index 983bf911..afa34ece 100644 --- a/src/SeqCli/SeqCli.csproj +++ b/src/SeqCli/SeqCli.csproj @@ -28,7 +28,7 @@ - +