Skip to content

Commit b865382

Browse files
authored
Merge pull request #275 from nblumhardt/app-instance-commands
`app list`, `appinstance (create|list|remove)`
2 parents 6f77c58 + d542806 commit b865382

File tree

21 files changed

+346
-62
lines changed

21 files changed

+346
-62
lines changed

src/SeqCli/Cli/Command.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ protected virtual async Task<int> Run(string[] unrecognized)
9191

9292
protected virtual Task<int> Run() { return Task.FromResult(0); }
9393

94-
protected virtual void ShowUsageErrors(IEnumerable<string> errors)
94+
protected static void ShowUsageErrors(IEnumerable<string> errors)
9595
{
9696
foreach (var error in errors)
9797
{

src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,9 @@ protected override async Task<int> Run()
4747
var list = _entityIdentity.Id != null ?
4848
new[] { await connection.ApiKeys.FindAsync(_entityIdentity.Id) } :
4949
(await connection.ApiKeys.ListAsync())
50-
.Where(ak => _entityIdentity.Title == null || _entityIdentity.Title == ak.Title)
51-
.ToArray();
50+
.Where(ak => _entityIdentity.Title == null || _entityIdentity.Title == ak.Title);
5251

53-
foreach (var apiKey in list)
54-
{
55-
_output.WriteEntity(apiKey);
56-
}
52+
_output.ListEntities(list);
5753

5854
return 0;
5955
}

src/SeqCli/Cli/Commands/App/DefineCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using System.Threading.Tasks;
1717
using SeqCli.Apps;
1818
using SeqCli.Apps.Definitions;
19-
using SeqCli.Config;
2019
using SeqCli.Util;
2120

2221
namespace SeqCli.Cli.Commands.App;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using SeqCli.Cli.Features;
5+
using SeqCli.Config;
6+
using SeqCli.Connection;
7+
8+
namespace SeqCli.Cli.Commands.App;
9+
10+
[Command("app", "list", "List installed app packages", Example="seqcli app list")]
11+
class ListCommand : Command
12+
{
13+
readonly SeqConnectionFactory _connectionFactory;
14+
15+
string? _title, _id;
16+
readonly ConnectionFeature _connection;
17+
readonly OutputFormatFeature _output;
18+
19+
string? PackageId => string.IsNullOrWhiteSpace(_title) ? null : _title.Trim();
20+
string? Id => string.IsNullOrWhiteSpace(_id) ? null : _id.Trim();
21+
22+
public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
23+
{
24+
if (config == null) throw new ArgumentNullException(nameof(config));
25+
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
26+
27+
Options.Add(
28+
"package-id=",
29+
"The package id of the app(s) to list",
30+
t => _title = t);
31+
32+
Options.Add(
33+
"i=|id=",
34+
"The id of a single app to list",
35+
t => _id = t);
36+
37+
_output = Enable(new OutputFormatFeature(config.Output));
38+
_connection = Enable<ConnectionFeature>();
39+
}
40+
41+
protected override async Task<int> Run()
42+
{
43+
if (PackageId != null && Id != null)
44+
{
45+
ShowUsageErrors(new[] {"Only one of either `package-id` or `id` can be specified"});
46+
return 1;
47+
}
48+
49+
var connection = _connectionFactory.Connect(_connection);
50+
51+
var list = Id != null ?
52+
new[] { await connection.Apps.FindAsync(Id) } :
53+
(await connection.Apps.ListAsync())
54+
.Where(ak => PackageId == null || PackageId == ak.Package.PackageId);
55+
56+
_output.ListEntities(list);
57+
58+
return 0;
59+
}
60+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Seq.Api.Model.AppInstances;
6+
using SeqCli.Cli.Features;
7+
using SeqCli.Config;
8+
using SeqCli.Connection;
9+
using SeqCli.Util;
10+
using Serilog;
11+
12+
namespace SeqCli.Cli.Commands.AppInstance;
13+
14+
[Command("appinstance", "create", "Create an instance of an installed app",
15+
Example = "seqcli appinstance create -t 'Email Ops' --app hostedapp-314159 -p [email protected]")]
16+
class CreateCommand : Command
17+
{
18+
readonly SeqConnectionFactory _connectionFactory;
19+
20+
readonly ConnectionFeature _connection;
21+
readonly OutputFormatFeature _output;
22+
23+
string? _title, _appId;
24+
readonly Dictionary<string, string> _settings = new();
25+
readonly List<string> _overridable = new();
26+
27+
public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
28+
{
29+
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
30+
31+
Options.Add(
32+
"t=|title=",
33+
"A title for the app instance",
34+
t => _title = ArgumentString.Normalize(t));
35+
36+
Options.Add(
37+
"app=",
38+
"The id of the installed app package to instantiate",
39+
app => _appId = ArgumentString.Normalize(app));
40+
41+
Options.Add(
42+
"p={=}|property={=}",
43+
"Specify name/value settings for the app, e.g. `-p [email protected] -p Subject=\"Alert!\"`",
44+
(n, v) =>
45+
{
46+
var name = n.Trim();
47+
var valueText = v?.Trim();
48+
_settings.Add(name, valueText ?? "");
49+
});
50+
51+
Options.Add(
52+
"overridable=",
53+
"Specify setting names that may be overridden by users when invoking the app",
54+
s => _overridable.Add(s));
55+
56+
// The command doesn't yet implement "Stream incoming events".
57+
58+
_connection = Enable<ConnectionFeature>();
59+
_output = Enable(new OutputFormatFeature(config.Output));
60+
}
61+
62+
protected override async Task<int> Run()
63+
{
64+
var connection = _connectionFactory.Connect(_connection);
65+
66+
AppInstanceEntity instance = await connection.AppInstances.TemplateAsync(_appId)!;
67+
68+
bool ValidateSettingName(string settingName)
69+
{
70+
if (!instance.Settings!.ContainsKey(settingName))
71+
{
72+
Log.Error("The app does not accept a setting with name {SettingName}; available settings are: {AvailableSettings}", settingName, instance.Settings.Keys.ToArray());
73+
return false;
74+
}
75+
76+
return true;
77+
}
78+
79+
instance.Title = _title;
80+
81+
foreach (var setting in _settings)
82+
{
83+
if (!ValidateSettingName(setting.Key))
84+
return 1;
85+
86+
instance.Settings![setting.Key] = setting.Value;
87+
}
88+
89+
foreach (var overridable in _overridable)
90+
{
91+
if (!ValidateSettingName(overridable))
92+
return 1;
93+
94+
instance.InvocationOverridableSettings!.Add(overridable);
95+
}
96+
97+
instance = await connection.AppInstances.AddAsync(instance);
98+
99+
_output.WriteEntity(instance);
100+
101+
return 0;
102+
}
103+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using SeqCli.Cli.Features;
5+
using SeqCli.Config;
6+
using SeqCli.Connection;
7+
8+
namespace SeqCli.Cli.Commands.AppInstance;
9+
10+
[Command("appinstance", "list", "List instances of installed apps", Example="seqcli appinstance list")]
11+
class ListCommand : Command
12+
{
13+
readonly SeqConnectionFactory _connectionFactory;
14+
15+
readonly EntityIdentityFeature _entityIdentity;
16+
readonly ConnectionFeature _connection;
17+
readonly OutputFormatFeature _output;
18+
19+
public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
20+
{
21+
if (config == null) throw new ArgumentNullException(nameof(config));
22+
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
23+
24+
_entityIdentity = Enable(new EntityIdentityFeature("app instance", "list"));
25+
_output = Enable(new OutputFormatFeature(config.Output));
26+
_connection = Enable<ConnectionFeature>();
27+
}
28+
29+
protected override async Task<int> Run()
30+
{
31+
var connection = _connectionFactory.Connect(_connection);
32+
33+
var list = _entityIdentity.Id != null ?
34+
new[] { await connection.AppInstances.FindAsync(_entityIdentity.Id) } :
35+
(await connection.AppInstances.ListAsync())
36+
.Where(d => _entityIdentity.Title == null || _entityIdentity.Title == d.Title);
37+
38+
_output.ListEntities(list);
39+
40+
return 0;
41+
}
42+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using SeqCli.Cli.Features;
5+
using SeqCli.Connection;
6+
using Serilog;
7+
8+
namespace SeqCli.Cli.Commands.AppInstance;
9+
[Command("appinstance", "remove", "Remove an app instance from the server",
10+
Example="seqcli appinstance remove -t 'Email Ops'")]
11+
12+
class RemoveCommand : Command
13+
{
14+
readonly SeqConnectionFactory _connectionFactory;
15+
16+
readonly EntityIdentityFeature _entityIdentity;
17+
readonly ConnectionFeature _connection;
18+
19+
public RemoveCommand(SeqConnectionFactory connectionFactory)
20+
{
21+
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
22+
23+
_entityIdentity = Enable(new EntityIdentityFeature("app instance", "remove"));
24+
_connection = Enable<ConnectionFeature>();
25+
}
26+
27+
protected override async Task<int> Run()
28+
{
29+
if (_entityIdentity.Title == null && _entityIdentity.Id == null)
30+
{
31+
Log.Error("A `title` or `id` must be specified");
32+
return 1;
33+
}
34+
35+
var connection = _connectionFactory.Connect(_connection);
36+
37+
var toRemove = _entityIdentity.Id != null ?
38+
new[] {await connection.AppInstances.FindAsync(_entityIdentity.Id)} :
39+
(await connection.AppInstances.ListAsync())
40+
.Where(ak => _entityIdentity.Title == ak.Title)
41+
.ToArray();
42+
43+
if (!toRemove.Any())
44+
{
45+
Log.Error("No matching app instance was found");
46+
return 1;
47+
}
48+
49+
foreach (var appInstanceEntity in toRemove)
50+
await connection.AppInstances.RemoveAsync(appInstanceEntity);
51+
52+
return 0;
53+
}
54+
}

src/SeqCli/Cli/Commands/Dashboard/ListCommand.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,9 @@ protected override async Task<int> Run()
4949
var list = _entityIdentity.Id != null ?
5050
new[] { await connection.Dashboards.FindAsync(_entityIdentity.Id) } :
5151
(await connection.Dashboards.ListAsync(ownerId: _entityOwner.OwnerId, shared: _entityOwner.IncludeShared))
52-
.Where(d => _entityIdentity.Title == null || _entityIdentity.Title == d.Title)
53-
.ToArray();
52+
.Where(d => _entityIdentity.Title == null || _entityIdentity.Title == d.Title);
5453

55-
foreach (var dashboardEntity in list)
56-
{
57-
_output.WriteEntity(dashboardEntity);
58-
}
54+
_output.ListEntities(list);
5955

6056
return 0;
6157
}

src/SeqCli/Cli/Commands/Feed/ListCommand.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,9 @@ protected override async Task<int> Run()
5757
var list = _id != null ?
5858
new[] { await connection.Feeds.FindAsync(_id) } :
5959
(await connection.Feeds.ListAsync())
60-
.Where(f => _name == null || _name == f.Name)
61-
.ToArray();
60+
.Where(f => _name == null || _name == f.Name);
6261

63-
foreach (var feed in list)
64-
{
65-
_output.WriteEntity(feed);
66-
}
62+
_output.ListEntities(list);
6763

6864
return 0;
6965
}

src/SeqCli/Cli/Commands/HelpCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,13 @@ void PrintHelp(string executableName)
179179
{
180180
if (!printedGroups.Contains(avail.Metadata.Name))
181181
{
182-
Printing.Define($" {avail.Metadata.Name}", "<sub-command>", 13, Console.Out);
182+
Printing.Define($" {avail.Metadata.Name}", "<sub-command>", Console.Out);
183183
printedGroups.Add(avail.Metadata.Name);
184184
}
185185
}
186186
else
187187
{
188-
Printing.Define($" {avail.Metadata.Name}", avail.Metadata.HelpText, 13, Console.Out);
188+
Printing.Define($" {avail.Metadata.Name}", avail.Metadata.HelpText, Console.Out);
189189
}
190190
}
191191

@@ -201,7 +201,7 @@ void PrintHelp(string executableName, string topLevelCommand)
201201

202202
foreach (var avail in _orderedCommands.Where(c => c.Metadata.Name == topLevelCommand))
203203
{
204-
Printing.Define($" {avail.Metadata.SubCommand}", avail.Metadata.HelpText, 13, Console.Out);
204+
Printing.Define($" {avail.Metadata.SubCommand}", avail.Metadata.HelpText, Console.Out);
205205
}
206206

207207
Console.WriteLine();

0 commit comments

Comments
 (0)