1515using System ;
1616using System . Globalization ;
1717using System . Linq ;
18+ using System . Threading ;
1819using System . Threading . Tasks ;
20+ using Seq . Api ;
1921using SeqCli . Cli . Features ;
2022using SeqCli . Config ;
2123using SeqCli . Connection ;
2628namespace SeqCli . Cli . Commands . Cluster ;
2729
2830[ Command ( "cluster" , "health" ,
29- "Probe a Seq node's `/health/cluster` endpoint, and print the returned status" ,
30- Example = "seqcli cluster health -s https://seq.example.com" ) ]
31+ "Probe a Seq node's `/health/cluster` endpoint, and print the returned status. This command can also be used " +
32+ "to wait on a timeout until the cluster is healthy." ,
33+ Example = "seqcli cluster health -s https://seq.example.com --wait-until-healthy" ) ]
3134class HealthCommand : Command
3235{
3336 readonly SeqConnectionFactory _connectionFactory ;
3437
3538 readonly ConnectionFeature _connection ;
3639 readonly OutputFormatFeature _output ;
40+ readonly TimeoutFeature _timeout ;
3741
42+ bool _waitUntilHealthy ;
43+
3844 public HealthCommand ( SeqConnectionFactory connectionFactory , SeqCliOutputConfig outputConfig )
3945 {
4046 _connectionFactory = connectionFactory ?? throw new ArgumentNullException ( nameof ( connectionFactory ) ) ;
47+
48+ Options . Add ( "wait-until-healthy" , "Wait until the cluster returns a status of healthy" , _ =>
49+ {
50+ _waitUntilHealthy = true ;
51+ } ) ;
4152
53+ _timeout = Enable ( new TimeoutFeature ( ) ) ;
4254 _output = Enable ( new OutputFormatFeature ( outputConfig ) ) ;
4355 _connection = Enable < ConnectionFeature > ( ) ;
4456 }
@@ -47,6 +59,54 @@ protected override async Task<int> Run()
4759 {
4860 var connection = _connectionFactory . Connect ( _connection ) ;
4961
62+ var timeout = _timeout . ApplyTimeout ( connection . Client . HttpClient ) ;
63+
64+ if ( _waitUntilHealthy )
65+ {
66+ return await RunUntilHealthy ( connection , timeout ?? TimeSpan . FromSeconds ( 30 ) ) ;
67+ }
68+
69+ return await RunOnce ( connection ) ;
70+ }
71+
72+ async Task < int > RunUntilHealthy ( SeqConnection connection , TimeSpan timeout )
73+ {
74+ using var ct = new CancellationTokenSource ( timeout ) ;
75+
76+ var tick = TimeSpan . FromSeconds ( 1 ) ;
77+
78+ connection . Client . HttpClient . Timeout = tick ;
79+
80+ try
81+ {
82+ return await Task . Run ( async ( ) =>
83+ {
84+ while ( true )
85+ {
86+ try
87+ {
88+ if ( await RunOnce ( connection ) == 0 )
89+ {
90+ return 0 ;
91+ }
92+ }
93+ catch ( Exception ex )
94+ {
95+ Log . Error ( "{UnhandledExceptionMessage}" , Presentation . FormattedMessage ( ex ) ) ;
96+ }
97+
98+ await Task . Delay ( tick , ct . Token ) ;
99+ }
100+ } , ct . Token ) ;
101+ }
102+ catch ( TaskCanceledException )
103+ {
104+ return 1 ;
105+ }
106+ }
107+
108+ async Task < int > RunOnce ( SeqConnection connection )
109+ {
50110 var health = await connection . Cluster . CheckHealthAsync ( ) ;
51111
52112 if ( _output . Json )
@@ -58,14 +118,12 @@ protected override async Task<int> Run()
58118 Console . WriteLine ( $ "{ health . Status } ") ;
59119 }
60120
61- return ( health . Status ) switch
121+ return health . Status switch
62122 {
63123 HealthStatus . Healthy => 0 ,
64124 HealthStatus . Degraded => 101 ,
65125 HealthStatus . Unhealthy => 102 ,
66- // Catch-all for any future statuses
67- // We give the main ones well-defined exit codes
68- _ => ( int ) health . Status
126+ _ => 103
69127 } ;
70128 }
71129}
0 commit comments