Skip to content

Commit be85dc1

Browse files
committed
Update the node health command to be roughly comparable with cluster health
1 parent 380bdfb commit be85dc1

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

src/SeqCli/Cli/Commands/Cluster/HealthCommand.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
// limitations under the License.
1414

1515
using System;
16-
using System.Globalization;
17-
using System.Linq;
1816
using System.Threading;
1917
using System.Threading.Tasks;
2018
using Seq.Api;
@@ -38,18 +36,13 @@ class HealthCommand : Command
3836
readonly ConnectionFeature _connection;
3937
readonly OutputFormatFeature _output;
4038
readonly TimeoutFeature _timeout;
41-
42-
bool _waitUntilHealthy;
39+
readonly WaitUntilHealthyFeature _waitUntilHealthy;
4340

4441
public HealthCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig outputConfig)
4542
{
4643
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
4744

48-
Options.Add("wait-until-healthy", "Wait until the cluster returns a status of healthy", _ =>
49-
{
50-
_waitUntilHealthy = true;
51-
});
52-
45+
_waitUntilHealthy = Enable(new WaitUntilHealthyFeature("cluster"));
5346
_timeout = Enable(new TimeoutFeature());
5447
_output = Enable(new OutputFormatFeature(outputConfig));
5548
_connection = Enable<ConnectionFeature>();
@@ -61,7 +54,7 @@ protected override async Task<int> Run()
6154

6255
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
6356

64-
if (_waitUntilHealthy)
57+
if (_waitUntilHealthy.ShouldWait)
6558
{
6659
return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30));
6760
}

src/SeqCli/Cli/Commands/Node/HealthCommand.cs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,44 +13,94 @@
1313
// limitations under the License.
1414

1515
using System;
16-
using System.Globalization;
16+
using System.IO;
1717
using System.Linq;
18+
using System.Threading;
1819
using System.Threading.Tasks;
20+
using Newtonsoft.Json;
21+
using Seq.Api;
1922
using SeqCli.Cli.Features;
23+
using SeqCli.Config;
2024
using SeqCli.Connection;
2125
using SeqCli.Util;
2226
using Serilog;
2327

2428
namespace SeqCli.Cli.Commands.Node;
2529

2630
[Command("node", "health",
27-
"Probe a Seq node's `/health` endpoint, and print the returned HTTP status code, or 'Unreachable' if the endpoint could not be queried",
31+
"Probe a Seq node's `/health` endpoint, and print the returned HTTP status code, or 'Unreachable' if the endpoint could not be queried; note that no API key is required",
2832
Example = "seqcli node health -s https://seq-2.example.com")]
2933
class HealthCommand : Command
3034
{
3135
readonly SeqConnectionFactory _connectionFactory;
3236

33-
string? _profileName, _serverUrl;
37+
readonly ConnectionFeature _connection;
38+
readonly WaitUntilHealthyFeature _waitUntilHealthy;
39+
readonly TimeoutFeature _timeout;
40+
readonly OutputFormatFeature _output;
3441

35-
public HealthCommand(SeqConnectionFactory connectionFactory)
42+
public HealthCommand(SeqConnectionFactory connectionFactory, SeqCliOutputConfig outputConfig)
3643
{
3744
_connectionFactory = connectionFactory;
38-
39-
Options.Add("s=|server=",
40-
"The URL of the Seq server; by default the `connection.serverUrl` config value will be used",
41-
v => _serverUrl = ArgumentString.Normalize(v));
4245

43-
Options.Add("profile=",
44-
"A connection profile to use; by default the `connection.serverUrl` and `connection.apiKey` config values will be used",
45-
v => _profileName = ArgumentString.Normalize(v));
46+
_waitUntilHealthy = Enable(new WaitUntilHealthyFeature("node"));
47+
_timeout = Enable(new TimeoutFeature());
48+
_connection = Enable<ConnectionFeature>();
49+
_output = Enable(new OutputFormatFeature(outputConfig));
4650
}
4751

4852
protected override async Task<int> Run()
4953
{
50-
// An API key is not accepted; we don't want to imply that /health requires authentication.
51-
var surrogateConnectionFeature = new ConnectionFeature { ProfileName = _profileName, Url = _serverUrl };
52-
var connection = _connectionFactory.Connect(surrogateConnectionFeature);
54+
var connection = _connectionFactory.Connect(_connection);
55+
56+
var timeout = _timeout.ApplyTimeout(connection.Client.HttpClient);
57+
58+
if (_waitUntilHealthy.ShouldWait)
59+
{
60+
return await RunUntilHealthy(connection, timeout ?? TimeSpan.FromSeconds(30));
61+
}
62+
63+
return await RunOnce(connection);
64+
}
65+
66+
async Task<int> RunUntilHealthy(SeqConnection connection, TimeSpan timeout)
67+
{
68+
using var ct = new CancellationTokenSource(timeout);
69+
70+
var tick = TimeSpan.FromSeconds(1);
71+
72+
connection.Client.HttpClient.Timeout = tick;
73+
74+
try
75+
{
76+
return await Task.Run(async () =>
77+
{
78+
while (true)
79+
{
80+
try
81+
{
82+
if (await RunOnce(connection) == 0)
83+
{
84+
return 0;
85+
}
86+
}
87+
catch (Exception ex)
88+
{
89+
Log.Error("{UnhandledExceptionMessage}", Presentation.FormattedMessage(ex));
90+
}
5391

92+
await Task.Delay(tick, ct.Token);
93+
}
94+
}, ct.Token);
95+
}
96+
catch (TaskCanceledException)
97+
{
98+
return 1;
99+
}
100+
}
101+
102+
async Task<int> RunOnce(SeqConnection connection)
103+
{
54104
try
55105
{
56106
var response = await connection.Client.HttpClient.GetAsync("health");
@@ -61,17 +111,32 @@ protected override async Task<int> Run()
61111
{
62112
Log.Information("{HeaderName}: {HeaderValue}", key, value);
63113
}
64-
65-
Console.WriteLine(await response.Content.ReadAsStringAsync());
114+
115+
if (_output.Json)
116+
{
117+
var shouldBeJson = await response.Content.ReadAsStringAsync();
118+
try
119+
{
120+
var obj = JsonConvert.DeserializeObject(shouldBeJson) ?? throw new InvalidDataException();
121+
_output.WriteObject(obj);
122+
}
123+
catch
124+
{
125+
_output.WriteObject(new { Response = shouldBeJson });
126+
}
127+
}
128+
else
129+
{
130+
Console.WriteLine((int)response.StatusCode);
131+
}
66132

67133
return response.IsSuccessStatusCode ? 0 : 1;
68134
}
69135
catch (Exception ex)
70136
{
71137
Log.Information(ex, "Exception thrown when calling health endpoint");
72-
138+
73139
Console.WriteLine("Unreachable");
74-
Console.WriteLine(Presentation.FormattedMessage(ex));
75140
return 1;
76141
}
77142
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright © Datalust Pty Ltd
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace SeqCli.Cli.Features;
16+
17+
class WaitUntilHealthyFeature(string targetName): CommandFeature
18+
{
19+
public bool ShouldWait { get; private set; }
20+
21+
public override void Enable(OptionSet options)
22+
{
23+
options.Add("wait-until-healthy", $"Wait until the {targetName} returns a status of healthy", _ =>
24+
{
25+
ShouldWait = true;
26+
});
27+
}
28+
}

0 commit comments

Comments
 (0)