@@ -6,7 +6,7 @@ use ElasticSearchX::Model::Document::Types qw(:all);
6
6
use Git::Helpers qw( checkout_root ) ;
7
7
use Log::Contextual qw( :log :dlog ) ;
8
8
use MetaCPAN::Model ();
9
- use MetaCPAN::Types::TypeTiny qw( Bool Int Path Str ) ;
9
+ use MetaCPAN::Types::TypeTiny qw( Bool HashRef Int Path Str ) ;
10
10
use Mojo::Server ();
11
11
use Term::ANSIColor qw( colored ) ;
12
12
use IO::Interactive qw( is_interactive ) ;
@@ -35,6 +35,22 @@ has die_on_error => (
35
35
documentation => ' Die on errors instead of simply logging' ,
36
36
);
37
37
38
+ has exit_code => (
39
+ isa => Int,
40
+ is => ' rw' ,
41
+ default => 0,
42
+ documentation => ' Exit Code to be returned on termination' ,
43
+ );
44
+
45
+ has arg_await_timeout => (
46
+ init_arg => ' await' ,
47
+ is => ' ro' ,
48
+ isa => Int,
49
+ default => 15,
50
+ documentation =>
51
+ ' seconds before connection is considered failed with timeout' ,
52
+ );
53
+
38
54
has ua => (
39
55
is => ' ro' ,
40
56
lazy => 1,
@@ -71,6 +87,27 @@ has index => (
71
87
' Index to use, defaults to "cpan" (when used: also export ES_SCRIPT_INDEX)' ,
72
88
);
73
89
90
+ has cluster_info => (
91
+ isa => HashRef,
92
+ traits => [' Hash' ],
93
+ is => ' rw' ,
94
+ default => sub { {} },
95
+ );
96
+
97
+ has indices_info => (
98
+ isa => HashRef,
99
+ traits => [' Hash' ],
100
+ is => ' rw' ,
101
+ default => sub { {} },
102
+ );
103
+
104
+ has aliases_info => (
105
+ isa => HashRef,
106
+ traits => [' Hash' ],
107
+ is => ' rw' ,
108
+ default => sub { {} },
109
+ );
110
+
74
111
has port => (
75
112
isa => Int,
76
113
is => ' ro' ,
@@ -123,13 +160,27 @@ sub BUILDARGS {
123
160
}
124
161
125
162
sub handle_error {
126
- my ( $self , $error ) = @_ ;
163
+ my ( $self , $error , $die_always ) = @_ ;
164
+
165
+ # Die if configured (for the test suite).
166
+ $die_always = $self -> die_on_error unless defined $die_always ;
127
167
128
168
# Always log.
129
169
log_fatal {$error };
130
170
131
- # Die if configured (for the test suite).
132
- Carp::croak $error if $self -> die_on_error;
171
+ $! = $self -> exit_code if ( $self -> exit_code != 0 );
172
+
173
+ Carp::croak $error if $die_always ;
174
+ }
175
+
176
+ sub print_error {
177
+ my ( $self , $error ) = @_ ;
178
+
179
+ # Always log.
180
+ log_error {$error };
181
+
182
+ # Display Error in red
183
+ print colored( [' bold red' ], " *** ERROR ***: $error " ), " \n " ;
133
184
}
134
185
135
186
sub index {
@@ -195,6 +246,122 @@ before run => sub {
195
246
# Dlog_debug {"Connected to $_"} $self->remote;
196
247
};
197
248
249
+ sub _get_indices_info {
250
+ my ( $self , $irefresh ) = @_ ;
251
+
252
+ if ( $irefresh || scalar ( keys %{ $self -> indices_info } ) == 0 ) {
253
+ my $sinfo_rs = $self -> es-> cat-> indices( h => [ ' index' , ' health' ] );
254
+ my $sindices_parsing = qr / ^([^[:space:]]+) +([^[:space:]]+)/ m ;
255
+
256
+ $self -> indices_info( {} );
257
+
258
+ while ( $sinfo_rs =~ / $sindices_parsing /g ) {
259
+ $self -> indices_info-> {$1 }
260
+ = { ' index_name' => $1 , ' health' => $2 };
261
+ }
262
+ }
263
+ }
264
+
265
+ sub _get_aliases_info {
266
+ my ( $self , $irefresh ) = @_ ;
267
+
268
+ if ( $irefresh || scalar ( keys %{ $self -> aliases_info } ) == 0 ) {
269
+ my $sinfo_rs = $self -> es-> cat-> aliases( h => [ ' alias' , ' index' ] );
270
+ my $saliases_parsing = qr / ^([^[:space:]]+) +([^[:space:]]+)/ m ;
271
+
272
+ $self -> aliases_info( {} );
273
+
274
+ while ( $sinfo_rs =~ / $saliases_parsing /g ) {
275
+ $self -> aliases_info-> {$1 } = { ' alias_name' => $1 , ' index' => $2 };
276
+ }
277
+ }
278
+ }
279
+
280
+ sub check_health {
281
+ my ( $self , $irefresh ) = @_ ;
282
+ my $ihealth = 0;
283
+
284
+ $irefresh = 0 unless ( defined $irefresh );
285
+
286
+ $ihealth = $self -> await;
287
+
288
+ if ($ihealth ) {
289
+ $self -> _get_indices_info($irefresh );
290
+
291
+ foreach ( keys %{ $self -> indices_info } ) {
292
+ $ihealth = 0
293
+ if ( $self -> indices_info-> {$_ }-> {' health' } eq ' red' );
294
+ }
295
+ }
296
+
297
+ if ($ihealth ) {
298
+ $self -> _get_aliases_info($irefresh );
299
+
300
+ $ihealth = 0 if ( scalar ( keys %{ $self -> aliases_info } ) == 0 );
301
+ }
302
+
303
+ return $ihealth ;
304
+ }
305
+
306
+ sub await {
307
+ my $self = $_ [0];
308
+ my $iready = 0;
309
+
310
+ if ( scalar ( keys %{ $self -> cluster_info } ) == 0 ) {
311
+ my $es = $self -> es;
312
+ my $iseconds = 0;
313
+
314
+ log_info {" Awaiting Elasticsearch ..." };
315
+
316
+ do {
317
+ eval {
318
+ $iready = $es -> ping;
319
+
320
+ if ($iready ) {
321
+ log_info {
322
+ " Awaiting $iseconds / "
323
+ . $self -> arg_await_timeout
324
+ . " : ready"
325
+ };
326
+
327
+ $self -> cluster_info( \%{ $es -> info } );
328
+ }
329
+ };
330
+
331
+ if ($@ ) {
332
+ if ( $iseconds < $self -> arg_await_timeout ) {
333
+ log_info {
334
+ " Awaiting $iseconds / "
335
+ . $self -> arg_await_timeout
336
+ . " : unavailable - sleeping ..."
337
+ };
338
+
339
+ sleep (1);
340
+
341
+ $iseconds ++;
342
+ }
343
+ else {
344
+ log_error {
345
+ " Awaiting $iseconds / "
346
+ . $self -> arg_await_timeout
347
+ . " : unavailable - timeout!"
348
+ };
349
+
350
+ # Set System Error: 112 - EHOSTDOWN - Host is down
351
+ $self -> exit_code(112);
352
+ $self -> handle_error( $@ , 1 );
353
+ }
354
+ }
355
+ } while ( !$iready && $iseconds <= $self -> arg_await_timeout );
356
+ }
357
+ else {
358
+ # ElasticSearch Service is available
359
+ $iready = 1;
360
+ }
361
+
362
+ return $iready ;
363
+ }
364
+
198
365
sub are_you_sure {
199
366
my ( $self , $msg ) = @_ ;
200
367
@@ -216,8 +383,82 @@ __END__
216
383
217
384
=pod
218
385
386
+ =head1 NAME
387
+
388
+ MetaCPAN::Role::Script - Base Role which is used by many command line applications
389
+
219
390
=head1 SYNOPSIS
220
391
221
392
Roles which should be available to all modules.
222
393
394
+ =head1 OPTIONS
395
+
396
+ This Role makes the command line application accept the following options
397
+
398
+ =over 4
399
+
400
+ =item Option C<--await 15 >
401
+
402
+ This option will set the I<ElasticSearch Availability Check Timeout > .
403
+ After C<await > seconds the Application will fail with an Exception and the Exit Code [112]
404
+ (C<112 - EHOSTDOWN - Host is down > ) will be returned
405
+
406
+ bin/metacpan <script_name> --await 15
407
+
408
+ See L<Method C<await()> >
409
+
410
+ =back
411
+
412
+ =head1 METHODS
413
+
414
+ This Role provides the following methods
415
+
416
+ =over 4
417
+
418
+ =item C<await() >
419
+
420
+ This method uses the
421
+ L<C<Search::Elasticsearch::Client::2_0::Direct::ping()> |https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#ping()>
422
+ method to verify the service availabilty and wait for C<arg_await_timeout > seconds.
423
+ When the service does not become available within C<arg_await_timeout > seconds it re-throws the
424
+ Exception from the C<Search::Elasticsearch::Client > and sets C< $! > to C< 112 > .
425
+ The C<Search::Elasticsearch::Client > generates a C<"Search::Elasticsearch::Error::NoNodes" > Exception.
426
+ When the service is available it will populate the C<cluster_info > C<HASH > structure with the basic information
427
+ about the cluster.
428
+ See L<Option C<--await 15> >
429
+ See L<Method C<check_health()> >
430
+
431
+ =item C<check_health( [ refresh ] ) >
432
+
433
+ This method uses the
434
+ L<C<Search::Elasticsearch::Client::2_0::Direct::cat()> |https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#cat()>
435
+ method to collect basic data about the cluster structure as the general information,
436
+ the health state of the indices and the created aliases.
437
+ This information is stored in C<cluster_info > , C<indices_info > and C<aliases_info > as C<HASH > structures.
438
+ If the parameter C<refresh > is set to C< 1 > the structures C<indices_info > and C<aliases_info > will always
439
+ be updated.
440
+ If the C<cluster_info > structure is empty it calls first the C<await() > method.
441
+ If the service is unavailable the C<await() > method will produce an exception and the structures will be empty
442
+ The method returns C< 1 > when the C<cluster_info > is populated, none of the indices in C<indices_info > has
443
+ the Health State I<red > and at least one alias is created in C<aliases_info >
444
+ otherwise the method returns C< 0 >
445
+
446
+ =item C<are_you_sure() >
447
+
448
+ Requests the user to confirm the operation with "I< YES > "
449
+
450
+ =item C<handle_error( error_message[, die_always ] ) >
451
+
452
+ Logs the string C<error_message > with the log function as fatal error.
453
+ If C<exit_code > is not equel C< 0 > sets its value in C< $! > .
454
+ If the option C<--die_on_error > is enabled it throws an Exception with C<error_message > .
455
+ If the parameter C<die_always > is set it overrides the option C<--die_on_error > .
456
+
457
+ =item C<print_error( error_message ) >
458
+
459
+ Logs the string C<error_message > with the log function and displays it in red.
460
+ But it does not end the application.
461
+
462
+ =back
463
+
223
464
=cut
0 commit comments