@@ -6,7 +6,7 @@ use ElasticSearchX::Model::Document::Types qw(:all);
66use Git::Helpers qw( checkout_root ) ;
77use Log::Contextual qw( :log :dlog ) ;
88use MetaCPAN::Model ();
9- use MetaCPAN::Types::TypeTiny qw( Bool Int Path Str ) ;
9+ use MetaCPAN::Types::TypeTiny qw( Bool HashRef Int Path Str ) ;
1010use Mojo::Server ();
1111use Term::ANSIColor qw( colored ) ;
1212use IO::Interactive qw( is_interactive ) ;
@@ -35,6 +35,22 @@ has die_on_error => (
3535 documentation => ' Die on errors instead of simply logging' ,
3636);
3737
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+
3854has ua => (
3955 is => ' ro' ,
4056 lazy => 1,
@@ -71,6 +87,27 @@ has index => (
7187 ' Index to use, defaults to "cpan" (when used: also export ES_SCRIPT_INDEX)' ,
7288);
7389
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+
74111has port => (
75112 isa => Int,
76113 is => ' ro' ,
@@ -123,13 +160,27 @@ sub BUILDARGS {
123160}
124161
125162sub 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 ;
127167
128168 # Always log.
129169 log_fatal {$error };
130170
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 " ;
133184}
134185
135186sub index {
@@ -195,6 +246,114 @@ before run => sub {
195246 # Dlog_debug {"Connected to $_"} $self->remote;
196247};
197248
249+ sub check_health {
250+ my ( $self , $irefresh ) = @_ ;
251+ my $ihealth = 0;
252+
253+ $irefresh = 0 unless ( defined $irefresh );
254+
255+ $ihealth = $self -> await;
256+
257+ if ($ihealth ) {
258+ if ( $irefresh || scalar ( keys %{ $self -> indices_info } ) == 0 ) {
259+ my $sinfo_rs
260+ = $self -> es-> cat-> indices( h => [ ' index' , ' health' ] );
261+
262+ $self -> indices_info( {} );
263+
264+ while ( $sinfo_rs =~ / ^([^[:space:]]+) +([^[:space:]]+)/gm ) {
265+ $self -> indices_info-> {$1 }
266+ = { ' index_name' => $1 , ' health' => $2 };
267+
268+ $ihealth = 0 if ( $2 eq ' red' );
269+ }
270+ }
271+ else {
272+ foreach ( keys %{ $self -> indices_info } ) {
273+ $ihealth = 0
274+ if ( $self -> indices_info-> {$_ }-> {' health' } eq ' red' );
275+ }
276+ }
277+ }
278+
279+ if ($ihealth ) {
280+ if ( $irefresh || scalar ( keys %{ $self -> aliases_info } ) == 0 ) {
281+ my $sinfo_rs
282+ = $self -> es-> cat-> aliases( h => [ ' alias' , ' index' ] );
283+
284+ $self -> aliases_info( {} );
285+
286+ while ( $sinfo_rs =~ / ^([^[:space:]]+) +([^[:space:]]+)/gm ) {
287+ $self -> aliases_info-> {$1 }
288+ = { ' alias_name' => $1 , ' index' => $2 };
289+ }
290+ }
291+
292+ $ihealth = 0 if ( scalar ( keys %{ $self -> aliases_info } ) == 0 );
293+ }
294+
295+ return $ihealth ;
296+ }
297+
298+ sub await {
299+ my $self = $_ [0];
300+ my $iready = 0;
301+
302+ if ( scalar ( keys %{ $self -> cluster_info } ) == 0 ) {
303+ my $es = $self -> es;
304+ my $iseconds = 0;
305+
306+ log_info {" Awaiting Elasticsearch ..." };
307+
308+ do {
309+ eval {
310+ $iready = $es -> ping;
311+
312+ if ($iready ) {
313+ log_info {
314+ " Awaiting $iseconds / "
315+ . $self -> arg_await_timeout
316+ . " : ready"
317+ };
318+
319+ $self -> cluster_info( \%{ $es -> info } );
320+ }
321+ };
322+
323+ if ($@ ) {
324+ if ( $iseconds < $self -> arg_await_timeout ) {
325+ log_info {
326+ " Awaiting $iseconds / "
327+ . $self -> arg_await_timeout
328+ . " : unavailable - sleeping ..."
329+ };
330+
331+ sleep (1);
332+
333+ $iseconds ++;
334+ }
335+ else {
336+ log_error {
337+ " Awaiting $iseconds / "
338+ . $self -> arg_await_timeout
339+ . " : unavailable - timeout!"
340+ };
341+
342+ # Set System Error: 112 - EHOSTDOWN - Host is down
343+ $self -> exit_code(112);
344+ $self -> handle_error( $@ , 1 );
345+ }
346+ }
347+ } while ( !$iready && $iseconds <= $self -> arg_await_timeout );
348+ }
349+ else {
350+ # ElasticSearch Service is available
351+ $iready = 1;
352+ }
353+
354+ return $iready ;
355+ }
356+
198357sub are_you_sure {
199358 my ( $self , $msg ) = @_ ;
200359
@@ -216,8 +375,82 @@ __END__
216375
217376=pod
218377
378+ =head1 NAME
379+
380+ MetaCPAN::Role::Script - Base Role which is used by many command line applications
381+
219382=head1 SYNOPSIS
220383
221384Roles which should be available to all modules.
222385
386+ =head1 OPTIONS
387+
388+ This Role makes the command line application accept the following options
389+
390+ =over 4
391+
392+ =item Option C<--await 15 >
393+
394+ This option will set the I<ElasticSearch Availability Check Timeout > .
395+ After C<await > seconds the Application will fail with an Exception and the Exit Code [112]
396+ (C<112 - EHOSTDOWN - Host is down > ) will be returned
397+
398+ bin/metacpan <script_name> --await 15
399+
400+ See L<Method C<await()> >
401+
402+ =back
403+
404+ =head1 METHODS
405+
406+ This Role provides the following methods
407+
408+ =over 4
409+
410+ =item C<await() >
411+
412+ This method uses the
413+ L<C<Search::Elasticsearch::Client::2_0::Direct::ping()> |https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#ping()>
414+ method to verify the service availabilty and wait for C<arg_await_timeout > seconds.
415+ When the service does not become available within C<arg_await_timeout > seconds it re-throws the
416+ Exception from the C<Search::Elasticsearch::Client > and sets C< $! > to C< 112 > .
417+ The C<Search::Elasticsearch::Client > generates a C<"Search::Elasticsearch::Error::NoNodes" > Exception.
418+ When the service is available it will populate the C<cluster_info > C<HASH > structure with the basic information
419+ about the cluster.
420+ See L<Option C<--await 15> >
421+ See L<Method C<check_health()> >
422+
423+ =item C<check_health( [ refresh ] ) >
424+
425+ This method uses the
426+ L<C<Search::Elasticsearch::Client::2_0::Direct::cat()> |https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#cat()>
427+ method to collect basic data about the cluster structure as the general information,
428+ the health state of the indices and the created aliases.
429+ This information is stored in C<cluster_info > , C<indices_info > and C<aliases_info > as C<HASH > structures.
430+ If the parameter C<refresh > is set to C< 1 > the structures C<indices_info > and C<aliases_info > will always
431+ be updated.
432+ If the C<cluster_info > structure is empty it calls first the C<await() > method.
433+ If the service is unavailable the C<await() > method will produce an exception and the structures will be empty
434+ The method returns C< 1 > when the C<cluster_info > is populated, none of the indices in C<indices_info > has
435+ the Health State I<red > and at least one alias is created in C<aliases_info >
436+ otherwise the method returns C< 0 >
437+
438+ =item C<are_you_sure() >
439+
440+ Requests the user to confirm the operation with "I< YES > "
441+
442+ =item C<handle_error( error_message[, die_always ] ) >
443+
444+ Logs the string C<error_message > with the log function as fatal error.
445+ If C<exit_code > is not equel C< 0 > sets its value in C< $! > .
446+ If the option C<--die_on_error > is enabled it throws an Exception with C<error_message > .
447+ If the parameter C<die_always > is set it overrides the option C<--die_on_error > .
448+
449+ =item C<print_error( error_message ) >
450+
451+ Logs the string C<error_message > with the log function and displays it in red.
452+ But it does not end the application.
453+
454+ =back
455+
223456=cut
0 commit comments