File Coverage

blib/lib/Search/ESsearcher.pm
Criterion Covered Total %
statement 29 376 7.7
branch 0 146 0.0
condition 0 42 0.0
subroutine 10 34 29.4
pod 10 19 52.6
total 49 617 7.9


line stmt bran cond sub pod time code
1             package Search::ESsearcher;
2              
3 1     1   74507 use 5.006;
  1         4  
4 1     1   6 use base Error::Helper;
  1         2  
  1         562  
5 1     1   896 use strict;
  1         4  
  1         20  
6 1     1   5 use warnings;
  1         2  
  1         24  
7 1     1   857 use Getopt::Long;
  1         13913  
  1         7  
8 1     1   1017 use JSON;
  1         11682  
  1         7  
9 1     1   819 use Template;
  1         21988  
  1         47  
10 1     1   739 use Search::Elasticsearch;
  1         60850  
  1         40  
11 1     1   1560 use Term::ANSIColor;
  1         10019  
  1         164  
12 1     1   1063 use Time::ParseDate;
  1         12601  
  1         4414  
13              
14             =head1 NAME
15              
16             Search::ESsearcher - Provides a handy system for doing templated elasticsearch searches.
17              
18             =head1 VERSION
19              
20             Version 0.4.3
21              
22             =cut
23              
24             our $VERSION = '0.4.3';
25              
26              
27             =head1 SYNOPSIS
28              
29             use Search::ESsearcher;
30              
31             my $ess = Search::ESsearcher->new();
32              
33             =head1 METHODS
34              
35             =head2 new
36              
37             This initiates the object.
38              
39             my $ss=Search::ESsearcher->new;
40              
41             =cut
42              
43             sub new{
44              
45 0     0 1   my $self = {
46             perror=>undef,
47             error=>undef,
48             errorString=>"",
49             base=>undef,
50             search=>'syslog',
51             search_template=>undef,
52             search_filled_in=>undef,
53             search_usable=>undef,
54             output=>'syslog',
55             output_template=>undef,
56             options=>'syslog',
57             options_array=>undef,
58             elastic=>'default',
59             elastic_hash=>{
60             nodes => [
61             '127.0.0.1:9200'
62             ]
63             },
64             errorExtra=>{
65             flags=>{
66             '1'=>'IOerror',
67             '2'=>'NOfile',
68             '3'=>'nameInvalid',
69             '4'=>'searchNotUsable',
70             '5'=>'elasticNotLoadable',
71             '6'=>'notResults',
72             }
73             },
74             };
75 0           bless $self;
76              
77             # finds the etc base to use
78 0 0         if ( -d '/usr/local/etc/essearch/' ) {
    0          
    0          
79 0           $self->{base}='/usr/local/etc/essearch/';
80             } elsif ( -d '/etc/essearch/' ) {
81 0           $self->{base}='/etc/essearch/';
82             } elsif ( $0 =~ /bin\/essearcher$/ ) {
83 0           $self->{base}=$0;
84 0           $self->{base}=~s/\/bin\/essearcher$/\/etc\/essearch\//;
85             }
86              
87             # inits Template
88 0           $self->{t}=Template->new({
89             EVAL_PERL=>1,
90             INTERPOLATE=>1,
91             POST_CHOMP=>1,
92             });
93              
94             # inits JSON
95 0           $self->{j}=JSON->new;
96 0           $self->{j}->pretty(1); # make the output sanely human readable
97 0           $self->{j}->relaxed(1); # make writing search templates a bit easier
98              
99 0           return $self;
100             }
101              
102             =head elastic_get
103              
104             This returns what Elasticsearch config will be used.
105              
106             my $elastic=$ess->elastic_get;
107              
108             =cut
109              
110             sub elastic_get{
111 0     0 0   my $self=$_[0];
112 0           my $name=$_[1];
113              
114 0 0         if ( ! $self->errorblank ) {
115 0           return undef;
116             }
117              
118 0           return $self->{elastic};
119             }
120              
121             =head elastic_set
122              
123             This sets the name of the config file to use.
124              
125             One option is taken and name of the config file to load.
126              
127             Undef sets it back to the default, "default".
128              
129             $ess->elastic_set('foo');
130              
131             $ess->elastic_set(undef);
132              
133             =cut
134              
135             sub elastic_set{
136 0     0 0   my $self=$_[0];
137 0           my $name=$_[1];
138              
139 0 0         if ( ! $self->errorblank ) {
140 0           return undef;
141             }
142              
143 0 0         if (! $self->name_validate( $name ) ){
144 0           $self->{error}=3;
145 0           $self->{errorString}='"'.$name.'" is not a valid name';
146 0           $self->warn;
147 0           return undef;
148             }
149              
150 0 0         if( !defined( $name ) ){
151 0           $name='default';
152             }
153              
154 0           $self->{elastic}=$name;
155              
156 0           return 1;
157             }
158              
159             =head2 fetch_help
160              
161             This fetches the help for the current search and returns it.
162             Failsure to find one, results in a empty message being returned.
163              
164             my $help=$ess->fetch_help;
165              
166             =cut
167              
168             sub fetch_help{
169 0     0 1   my $self=$_[0];
170              
171 0 0         if ( ! $self->errorblank ) {
172 0           return undef;
173             }
174              
175 0           my $file=undef;
176 0           my $data=undef;
177              
178             # ~/ -> etc -> module -> error
179 0 0 0       if (
    0 0        
180             ( defined( $ENV{'HOME'} ) ) &&
181             ( -f $ENV{'HOME'}.'/.config/essearcher/help/'.$self->{search} )
182             ) {
183 0           $file=$ENV{'HOME'}.'/.config/essearcher/help/'.$self->{search};
184             } elsif (
185             ( defined( $self->{base} ) ) &&
186             ( -f $self->{base}.'/etc/essearcher/help/'.$self->{search} )
187             ) {
188 0           $file=$self->{base}.'/etc/essearcher/help/'.$self->{search};
189             } else {
190             # do a quick check of making sure we have a valid name before trying a module...
191             # not all valid names are perl module name valid, but it will prevent arbitrary code execution
192 0 0         if ( $self->name_validate( $self->{search} ) ) {
193             my $to_eval='use Search::ESsearcher::Templates::'.$self->{search}.
194 0           '; $data=Search::ESsearcher::Templates::'.$self->{search}.'->help;';
195 0           eval( $to_eval );
196             }
197             # if undefined, it means the eval failed
198 0 0         if ( ! defined( $data ) ) {
199 0           $self->{error}=2;
200 0           $self->{errorString}='No help file with the name "'.$self->{search}.'" was found';
201 0           $self->warn;
202 0           return '';
203             }
204             }
205              
206 0 0         if ( ! defined( $data ) ) {
207 0           my $fh;
208 0 0         if (! open($fh, '<', $file ) ) {
209 0           $self->{error}=1;
210 0           $self->{errorString}='Failed to open "'.$file.'"',
211             $self->warn;
212 0           return '';
213             }
214             # if it is larger than 2M bytes, something is wrong as the template
215             # it takes is literally longer than all HHGTTG books combined
216 0 0         if (! read($fh, $data, 200000000 )) {
217 0           $self->{error}=1;
218 0           $self->{errorString}='Failed to read "'.$file.'"',
219             $self->warn;
220 0           return '';
221             }
222 0           close($fh);
223             }
224              
225 0           return $data;
226             }
227              
228             =head2 get_options
229              
230             This fetches the options for use later
231             when filling in the search template.
232              
233             $ess->get_options;
234              
235             =cut
236              
237             sub get_options{
238 0     0 1   my $self=$_[0];
239              
240 0 0         if ( ! $self->errorblank ) {
241 0           return undef;
242             }
243              
244 0           my %parsed_options;
245              
246 0           GetOptions( \%parsed_options, @{ $self->{options_array} } );
  0            
247              
248              
249 0           $self->{parsed_options}=\%parsed_options;
250              
251 0           return 1;
252             }
253              
254             =head2 load_options
255              
256             This loads the currently set options.
257              
258             $ess->load_options;
259              
260             =cut
261              
262             sub load_options{
263 0     0 1   my $self=$_[0];
264              
265 0 0         if ( ! $self->errorblank ) {
266 0           return undef;
267             }
268              
269 0           my $file;
270             my $data;
271              
272             # ~/ -> etc -> module -> error
273 0 0 0       if (
    0 0        
274             ( defined( $ENV{'HOME'} ) ) &&
275             ( -f $ENV{'HOME'}.'/.config/essearcher/options/'.$self->{options} )
276             ) {
277 0           $file=$ENV{'HOME'}.'/.config/essearcher/options/'.$self->{options};
278             } elsif (
279             ( defined( $self->{base} ) ) &&
280             ( -f $self->{base}.'/etc/essearcher/options/'.$self->{options} )
281             ) {
282 0           $file=$self->{base}.'/etc/essearcher/options/'.$self->{options};
283             } else {
284             # do a quick check of making sure we have a valid name before trying a module...
285             # not all valid names are perl module name valid, but it will prevent arbitrary code execution
286 0 0         if ( $self->name_validate( $self->{options} ) ){
287             my $to_eval='use Search::ESsearcher::Templates::'.$self->{options}.
288 0           '; $data=Search::ESsearcher::Templates::'.$self->{options}.'->options;';
289 0           eval( $to_eval );
290             }
291             # if undefined, it means the eval failed
292 0 0         if ( ! defined( $data ) ){
293 0           $self->{error}=2;
294 0           $self->{errorString}='No options file or module with the name "'.$self->{options}.'" was found';
295 0           $self->warn;
296 0           return undef;
297             }
298             }
299              
300 0 0         if ( defined( $file ) ) {
301 0           my $fh;
302 0 0         if (! open($fh, '<', $file ) ) {
303 0           $self->{error}=1;
304 0           $self->{errorString}='Failed to open "'.$file.'"',
305             $self->warn;
306 0           return undef;
307             }
308             # if it is larger than 2M bytes, something is wrong as the options
309             # it takes is literally longer than all HHGTTG books combined
310 0 0         if (! read($fh, $data, 200000000 )) {
311 0           $self->{error}=1;
312 0           $self->{errorString}='Failed to read "'.$file.'"',
313             $self->warn;
314 0           return undef;
315             }
316 0           close($fh);
317             }
318              
319             # split it appart and remove comments and blank lines
320 0           my @options=split(/\n/,$data);
321 0           @options=grep(!/^#/, @options);
322 0           @options=grep(!/^$/, @options);
323              
324             # we have now completed with out error, so save it
325 0           $self->{options_array}=\@options;
326              
327 0           return 1;
328             }
329              
330             =head2 load_elastic
331              
332             This loads the currently specified config file
333             containing the Elasticsearch config JSON.
334              
335             $ess->load_elastic;
336              
337             =cut
338              
339             sub load_elastic{
340 0     0 1   my $self=$_[0];
341              
342 0 0         if ( ! $self->errorblank ) {
343 0           return undef;
344             }
345              
346 0           my $file=undef;
347              
348             # ~/ -> etc -> error
349 0 0 0       if (
    0 0        
350             ( defined( $ENV{'HOME'} ) ) &&
351             ( -f $ENV{'HOME'}.'/.config/essearcher/elastic/'.$self->{elastic} )
352             ) {
353 0           $file=$ENV{'HOME'}.'/.config/essearcher/elastic/'.$self->{elastic};
354             } elsif (
355             ( defined( $self->{base} ) ) &&
356             ( -f $self->{base}.'/etc/essearcher/elastic/'.$self->{elastic} )
357             ) {
358 0           $file=$self->{base}.'/etc/essearcher/elastic/'.$self->{elastic};
359             } else {
360             $self->{elastic_hash}={
361 0           nodes => [
362             '127.0.0.1:9200'
363             ]
364             };
365             }
366              
367 0 0         if (defined( $file )) {
368 0           my $fh;
369 0 0         if (! open($fh, '<', $file ) ) {
370 0           $self->{error}=1;
371 0           $self->{errorString}='Failed to open "'.$file.'"',
372             $self->warn;
373 0           return undef;
374             }
375 0           my $data;
376             # if it is larger than 2M bytes, something is wrong as the template
377             # it takes is literally longer than all HHGTTG books combined
378 0 0         if (! read($fh, $data, 200000000 )) {
379 0           $self->{error}=1;
380 0           $self->{errorString}='Failed to read "'.$file.'"',
381             $self->warn;
382 0           return undef;
383             }
384 0           close($fh);
385              
386 0           eval {
387 0           my $decoded=$self->{j}->decode( $data );
388 0           $self->{elastic_hash}=$decoded;
389             };
390 0 0         if ( $@ ){
391 0           $self->{error}=5;
392 0           $self->{errorString}=$@;
393 0           $self->warn;
394 0           return undef;
395             }
396              
397             }
398              
399 0           eval{
400 0           $self->{es}=Search::Elasticsearch->new( $self->{elastic_hash} );
401             };
402 0 0         if ( $@ ){
403 0           $self->{error}=5;
404 0           $self->{errorString}=$@;
405 0           $self->warn;
406 0           return undef;
407             }
408              
409 0           return 1;
410             }
411              
412             =head2 load_output
413              
414             This loads the currently specified output template.
415              
416             While this is save internally, the template is also
417             returned as a string.
418              
419             my $outpot_template=$ess->load_output;
420              
421             =cut
422              
423             sub load_output{
424 0     0 1   my $self=$_[0];
425              
426 0 0         if ( ! $self->errorblank ) {
427 0           return undef;
428             }
429              
430 0           my $file=undef;
431 0           my $data=undef;
432              
433             # ~/ -> etc -> module -> error
434 0 0 0       if (
    0 0        
435             ( defined( $ENV{'HOME'} ) ) &&
436             ( -f $ENV{'HOME'}.'/.config/essearcher/output/'.$self->{output} )
437             ) {
438 0           $file=$ENV{'HOME'}.'/.config/essearcher/output/'.$self->{output};
439             } elsif (
440             ( defined( $self->{base} ) ) &&
441             ( -f $self->{base}.'/etc/essearcher/output/'.$self->{output} )
442             ) {
443 0           $file=$self->{base}.'/etc/essearcher/outpot/'.$self->{output};
444             } else {
445             # do a quick check of making sure we have a valid name before trying a module...
446             # not all valid names are perl module name valid, but it will prevent arbitrary code execution
447 0 0         if ( $self->name_validate( $self->{options} ) ) {
448             my $to_eval='use Search::ESsearcher::Templates::'.$self->{output}.
449 0           '; $data=Search::ESsearcher::Templates::'.$self->{output}.'->output;';
450 0           eval( $to_eval );
451             }
452             # if undefined, it means the eval failed
453 0 0         if ( ! defined( $data ) ) {
454 0           $self->{error}=2;
455 0           $self->{errorString}='No options file with the name "'.$self->{output}.'" was found';
456 0           $self->warn;
457 0           return '';
458             }
459             }
460              
461 0 0         if ( ! defined( $data ) ) {
462 0           my $fh;
463 0 0         if (! open($fh, '<', $file ) ) {
464 0           $self->{error}=1;
465 0           $self->{errorString}='Failed to open "'.$file.'"',
466             $self->warn;
467 0           return '';
468             }
469             # if it is larger than 2M bytes, something is wrong as the template
470             # it takes is literally longer than all HHGTTG books combined
471 0 0         if (! read($fh, $data, 200000000 )) {
472 0           $self->{error}=1;
473 0           $self->{errorString}='Failed to read "'.$file.'"',
474             $self->warn;
475 0           return '';
476             }
477 0           close($fh);
478             }
479              
480             # we have now completed with out error, so save it
481 0           $self->{output_template}=$data;
482              
483 0           return $data;
484             }
485              
486             =head2 load_search
487              
488             This loads the currently specified search template.
489              
490             While this is save internally, the template is also
491             returned as a string.
492              
493             my $search_template=$ess->load_search;
494              
495             =cut
496              
497             sub load_search{
498 0     0 1   my $self=$_[0];
499              
500 0 0         if ( ! $self->errorblank ) {
501 0           return undef;
502             }
503              
504 0           my $file=undef;
505 0           my $data;
506              
507             # ~/ -> etc -> module -> error
508 0 0 0       if (
    0 0        
509             ( defined( $ENV{'HOME'} ) ) &&
510             ( -f $ENV{'HOME'}.'/.config/essearcher/search/'.$self->{search} )
511             ) {
512 0           $file=$ENV{'HOME'}.'/.config/essearcher/search/'.$self->{search};
513             } elsif (
514             ( defined( $self->{base} ) ) &&
515             ( -f $self->{base}.'/etc/essearcher/search/'.$self->{search} )
516             ) {
517 0           $file=$self->{base}.'/etc/essearcher/search/'.$self->{search};
518             } else {
519             # do a quick check of making sure we have a valid name before trying a module...
520             # not all valid names are perl module name valid, but it will prevent arbitrary code execution
521 0 0         if ( $self->name_validate( $self->{options} ) ){
522             my $to_eval='use Search::ESsearcher::Templates::'.$self->{options}.
523 0           '; $data=Search::ESsearcher::Templates::'.$self->{options}.'->search;';
524 0           eval( $to_eval );
525             }
526             # if undefined, it means the eval failed
527 0 0         if ( ! defined( $data ) ){
528 0           $self->{error}=2;
529 0           $self->{errorString}='No template file with the name "'.$self->{search}.'" was found';
530 0           $self->warn;
531 0           return undef;
532             }
533             }
534              
535 0 0         if ( ! defined( $data ) ) {
536 0           my $fh;
537 0 0         if (! open($fh, '<', $file ) ) {
538 0           $self->{error}=1;
539 0           $self->{errorString}='Failed to open "'.$file.'"',
540             $self->warn;
541 0           return undef;
542             }
543             # if it is larger than 2M bytes, something is wrong as the template
544             # it takes is literally longer than all HHGTTG books combined
545 0 0         if (! read($fh, $data, 200000000 )) {
546 0           $self->{error}=1;
547 0           $self->{errorString}='Failed to read "'.$file.'"',
548             $self->warn;
549 0           return undef;
550             }
551 0           close($fh);
552             }
553              
554             # we have now completed with out error, so save it
555 0           $self->{search_template}=$data;
556              
557 0           return 1;
558             }
559              
560             =head2 name_valide
561              
562             This validates a config name.
563              
564             One option is taken and that is the name to valid.
565              
566             The returned value is a perl boolean based on if it
567             it is valid or not.
568              
569             if ( ! $ess->name_validate( $name ) ){
570             print "Name is not valid.\n";
571             }
572              
573             =cut
574              
575             sub name_validate{
576 0     0 0   my $self=$_[0];
577 0           my $name=$_[1];
578              
579 0 0         if ( ! $self->errorblank ) {
580 0           return undef;
581             }
582              
583 0 0         if (! defined( $name ) ){
584 0           return 1;
585             }
586              
587 0           $name=~s/[A-Z0-9a-z\:\-\=\_+\ ]+//;
588              
589 0 0         if ( $name !~ /^$/ ){
590 0           return undef;
591             }
592              
593 0           return 1;
594             }
595              
596             =head options_get
597              
598             This returns the currently set options
599             config name.
600              
601             my $options=$ess->options_get;
602              
603             =cut
604              
605             sub options_get{
606 0     0 0   my $self=$_[0];
607              
608 0 0         if ( ! $self->errorblank ) {
609 0           return undef;
610             }
611              
612 0           return $self->{options};
613             }
614              
615             =head options_set
616              
617             This sets the options config name to use.
618              
619             One option is taken and this is the config name.
620             If it is undefiend, then the default is used.
621              
622             $ess->options_set( $name );
623              
624             =cut
625              
626             sub options_set{
627 0     0 0   my $self=$_[0];
628 0           my $name=$_[1];
629              
630 0 0         if ( ! $self->errorblank ) {
631 0           return undef;
632             }
633              
634 0 0         if (! $self->name_validate( $name ) ){
635 0           $self->{error}=3;
636 0           $self->{errorString}='"'.$name.'" is not a valid name';
637 0           $self->warn;
638 0           return undef;
639             }
640              
641 0 0         if( !defined( $name ) ){
642 0           $name='syslog';
643             }
644              
645 0           $self->{options}=$name;
646              
647 0           return 1;
648             }
649              
650             =head output_get
651              
652             This returns the currently set output
653             template name.
654              
655             my $output=$ess->output_get;
656              
657             =cut
658              
659             sub output_get{
660 0     0 0   my $self=$_[0];
661 0           my $name=$_[1];
662              
663 0 0         if ( ! $self->errorblank ) {
664 0           return undef;
665             }
666              
667 0           return $self->{output};
668             }
669              
670             =head output_set
671              
672              
673             This sets the output template name to use.
674              
675             One option is taken and this is the template name.
676             If it is undefiend, then the default is used.
677              
678             $ess->output_set( $name );
679              
680             =cut
681              
682             sub output_set{
683 0     0 0   my $self=$_[0];
684 0           my $name=$_[1];
685              
686 0 0         if ( ! $self->errorblank ) {
687 0           return undef;
688             }
689              
690 0 0         if (! $self->name_validate( $name ) ){
691 0           $self->{error}=3;
692 0           $self->{errorString}='"'.$name.'" is not a valid name';
693 0           $self->warn;
694 0           return undef;
695             }
696              
697 0 0         if( !defined( $name ) ){
698 0           $name='syslog';
699             }
700              
701 0           $self->{output}=$name;
702              
703 0           return 1;
704             }
705              
706             =head2 results_process
707              
708             This processes the results from search_run.
709              
710             One option is taken and that is the return from search_run.
711              
712             The returned value from this is array of each document found
713             after it has been formated using the set output template.
714              
715             my $results=$ess->search_run;
716             my @formated=$ess->results_process( $results );
717             @formated=reverse(@formated);
718             print join("\n", @formated)."\n";
719              
720             =cut
721              
722             sub results_process{
723 0     0 1   my $self=$_[0];
724 0           my $results=$_[1];
725              
726 0 0         if ( ! $self->errorblank ) {
727 0           return undef;
728             }
729              
730             #make sure we have a sane object passed to us
731 0 0 0       if (
      0        
732             ( ref( $results ) ne 'HASH' ) ||
733             ( !defined( $results->{hits} ) )||
734             ( !defined( $results->{hits}{hits} ) )
735             ){
736 0           $self->{error}=6;
737 0           $self->{errorString}='The passed results variable does not a appear to be a search results return';
738 0           $self->warn;
739 0           return undef;
740             }
741              
742             #use Data::Dumper;
743             #print Dumper( $results->{hits}{hits} );
744              
745             my $vars={
746             o=>$self->{parsed_options},
747             r=>$results,
748 0     0     c=>sub{ return color( $_[0] ); },
749             pd=>sub{
750 0 0   0     if( $_[0] =~ /^raw\:/ ){
751 0           $_[0] =~ s/^raw\://;
752 0           return $_[0];
753             }
754 0           $_[0]=~s/m$/minutes/;
755 0           $_[0]=~s/M$/months/;
756 0           $_[0]=~s/d$/days/;
757 0           $_[0]=~s/h$/hours/;
758 0           $_[0]=~s/h$/weeks/;
759 0           $_[0]=~s/y$/years/;
760 0           $_[0]=~s/([0123456789])$/$1seconds/;
761 0           $_[0]=~s/([0123456789])s$/$1seconds/;
762 0           my $secs="";
763 0           eval{ $secs=parsedate( $_[0] ); };
  0            
764 0           return $secs;
765             },
766 0           };
767              
768 0           my @formatted;
769 0           foreach my $doc ( @{ $results->{hits}{hits} } ){
  0            
770 0           $vars->{doc}=$doc;
771 0           $vars->{f}=$doc->{_source};
772              
773 0           my $processed;
774 0           $self->{t}->process( \$self->{output_template}, $vars , \$processed );
775 0           chomp($processed);
776              
777 0           push(@formatted,$processed);
778             }
779              
780 0           @formatted=reverse(@formatted);
781              
782 0           return @formatted;
783             }
784              
785             =head search_get
786              
787             This returns the currently set search
788             template name.
789              
790             my $search=$ess->search_get;
791              
792              
793             =cut
794              
795             sub search_get{
796 0     0 0   my $self=$_[0];
797 0           my $name=$_[1];
798              
799 0 0         if ( ! $self->errorblank ) {
800 0           return undef;
801             }
802              
803 0           return $self->{search};
804             }
805              
806             =head2 search_fill_in
807              
808             This fills in the loaded search template.
809              
810             The results are saved internally as well as returned.
811              
812             my $filled_in=$ess->search_fill_in;
813              
814             =cut
815              
816             sub search_fill_in{
817 0     0 1   my $self=$_[0];
818 0           my $name=$_[1];
819              
820 0 0         if ( ! $self->errorblank ) {
821 0           return undef;
822             }
823              
824             my $vars={
825             o=>$self->{parsed_options},
826             aon=>sub{
827 0     0     $_[0]=~s/\+/\ AND\ /;
828 0           $_[0]=~s/\,/\ OR\ /;
829 0           $_[0]=~s/\!/\ NOT\ /;
830 0           return $_[0];
831             },
832             aonHost=>sub{
833 0     0     $_[0]=~s/^([A-Za-z0-9\.]+)/\/$1*\//;
834 0           $_[0]=~s/\+([A-Za-z0-9\.]+)/\ AND\ \/$1*\//;
835 0           $_[0]=~s/\,([A-Za-z0-9\.]+)/\ OR\ \/$1*\//;
836 0           $_[0]=~s/\!([A-Za-z0-9\.]+)/\ NOT\ \/$1*\//;
837 0           return $_[0];
838             },
839             pd=>sub{
840 0 0   0     if( $_[0] =~ /^u\:/ ){
    0          
841 0           $_[0] =~ s/^u\://;
842 0           $_[0]=~s/m$/minutes/;
843 0           $_[0]=~s/M$/months/;
844 0           $_[0]=~s/d$/days/;
845 0           $_[0]=~s/h$/hours/;
846 0           $_[0]=~s/h$/weeks/;
847 0           $_[0]=~s/y$/years/;
848 0           $_[0]=~s/([0123456789])$/$1seconds/;
849 0           $_[0]=~s/([0123456789])s$/$1seconds/;
850 0           my $secs="";
851 0           eval{ $secs=parsedate( $_[0] ); };
  0            
852 0           return $secs;
853             }elsif( $_[0] =~ /^\-/ ){
854 0           return 'now'.$_[0];
855             }
856 0           return $_[0];
857             },
858 0           };
859              
860 0           my $processed;
861 0           $self->{t}->process( \$self->{search_template}, $vars , \$processed );
862              
863 0           $self->{search_filled_in}=$processed;
864              
865 0           $self->{search_usable}=undef;
866              
867 0           eval {
868 0           my $decoded=$self->{j}->decode( $processed );
869 0           $self->{search_hash}=$decoded;
870             };
871 0 0         if ( $@ ){
872 0           $self->{error}=4;
873 0           $self->{errorString}='The returned filled in search template does not parse as JSON... '.$@;
874 0           $self->warn;
875 0           return $processed;
876             }
877              
878 0           return $processed;
879             }
880              
881             =head2 search_run
882              
883             This is used to run the search after search_fill_in
884             has been called.
885              
886             The returned value is of the type that would be returned
887             by L<Search::Elasticsearch>->search.
888              
889             my $results=$ess->search_run;
890              
891             =cut
892              
893             sub search_run{
894 0     0 1   my $self=$_[0];
895 0           my $name=$_[1];
896              
897 0 0         if ( ! $self->errorblank ) {
898 0           return undef;
899             }
900              
901 0           my $results;
902 0           eval{
903 0           $results=$self->{es}->search( $self->{search_hash} );
904             };
905              
906             # @timestamp can't be handled via
907 0 0 0       if (
      0        
908             ( ref( $results ) eq 'HASH' ) ||
909             ( defined( $results->{hits} ) )||
910             ( defined( $results->{hits}{hits} ) )
911             ){
912 0           foreach my $item ( @{ $results->{hits}{hits} } ){
  0            
913 0 0         if (!defined( $item->{'_source'}{'timestamp'}) ) {
914 0           $item->{'_source'}{'timestamp'}=$item->{'_source'}{'@timestamp'}
915             }
916             }
917             }
918              
919 0           return $results;
920             }
921              
922             =head search_set
923              
924             This sets the search template name to use.
925              
926             One option is taken and this is the template name.
927             If it is undefiend, then the default is used.
928              
929             $ess->search_sets( $name );
930              
931             =cut
932              
933             sub search_set{
934 0     0 0   my $self=$_[0];
935 0           my $name=$_[1];
936              
937 0 0         if ( ! $self->errorblank ) {
938 0           return undef;
939             }
940              
941 0 0         if (! $self->name_validate( $name ) ){
942 0           $self->{error}=3;
943 0           $self->{errorString}='"'.$name.'" is not a valid name';
944 0           $self->warn;
945 0           return undef;
946             }
947              
948 0 0         if( !defined( $name ) ){
949 0           $name='syslog';
950             }
951              
952 0           $self->{search}=$name;
953              
954 0           return 1;
955             }
956              
957             =head1 Configuration And Usage
958              
959             Configs, help, and templates are looked for in the following manner and order,
960             with the following of the elasticsearch config.
961              
962             $ENV{HOME}."/.config/essearcher/".$item."/".$name
963             $base.'/etc/essearcher/".$item."/".$name
964             Search::ESsearcher::Templates::$name->$item
965             ERROR
966              
967             Item can be any of the following.
968              
969             elastic
970             help
971             output
972             options
973             search
974              
975             The basic idea is you have matching output, options
976             and search that you can use to perform queries and
977             print the results.
978              
979             Each template/config is its own file under the directory
980             named after its purpose. So the options template fail2ban
981             would be 'options/fail2ban'.
982              
983             =head2 elastic
984              
985             This directory contains JSON formatted config files
986             for use with connecting to the Elasticsearch server.
987              
988             This is then read in and converted to a hash and feed
989             to L<Search::Elasticsearch>->new.
990              
991             By default it will attempt to connect to it on
992             "127.0.0.1:9200". The JSON equivalent would be...
993              
994             { "nodes": [ "127.0.0.1:9200" ] }
995              
996             =head2 options
997              
998             This is a file that will be used as a string for with
999             L<Getopt::Long>. They will be passed to the templates
1000             as a hash.
1001              
1002             =head2 help
1003              
1004             This contains information on the options the search uses.
1005              
1006             This is just a text file containing information and is not
1007             required.
1008              
1009             If you are writing a module, it should definitely be present.
1010              
1011             =head2 search
1012              
1013             This is a L<Template> template that will be filled in using
1014             the data from the passed command line options and used
1015             to run the search.
1016              
1017             The end result should be valid JSON that can be turned
1018             into a hash for feeding L<Search::Elasticsearch>->search.
1019              
1020             When writing search templates, it is highly suggested
1021             to use L<Template::Plugin::JSON> for when inserting variables
1022             as it will automatically escape them.
1023              
1024             =head2 output
1025              
1026             This is a L<Template> template that will be filled in using
1027             the data from the passed command line options and the returned
1028             results.
1029              
1030             It will be used for each returned document. bin/essearcher will
1031             then join the array with "\n".
1032              
1033             =head1 TEMPLATES
1034              
1035             =head2 o
1036              
1037             This is a hash that contains the parsed options.
1038              
1039             Below is a example with the option --program and
1040             transforming it a JSON save value.
1041              
1042             [% USE JSON ( pretty => 1 ) %]
1043             [% DEFAULT o.program = "*" %]
1044            
1045             {"query_string": {
1046             "default_field": "program",
1047             "query": [% o.program.json %]
1048             }
1049             },
1050              
1051             =head2 aon
1052              
1053             This is AND, OR, or NOT sub that handles
1054             the following in a string, transforming them
1055             from the punctuation to the logic.
1056              
1057             , OR
1058             + AND
1059             ! NOT
1060              
1061             So the string "postfix,spamd" would become
1062             "postfix OR spamd".
1063              
1064             Can be used like below.
1065              
1066             [% USE JSON ( pretty => 1 ) %]
1067            
1068             [% IF o.program %]
1069             {"query_string": {
1070             "default_field": "program",
1071             "query": [% aon( o.program ).json %]
1072             }
1073             },
1074             [% END %]
1075              
1076             This function is only available for the search template.
1077              
1078             =head2 aonHost
1079              
1080             This is AND, OR, or NOT sub that handles
1081             the following in a string, transforming them
1082             from the punctuation to the logic.
1083              
1084             , OR
1085             + AND
1086             ! NOT
1087              
1088             So the string "foo.,mail.bar." would become
1089             "/foo./ OR /mail.bar./".
1090              
1091             This is best used with $field.keyword.
1092              
1093             Can be used like below.
1094              
1095             [% USE JSON ( pretty => 1 ) %]
1096              
1097             [% IF o.host %]
1098             {"query_string": {
1099             "default_field": "host.keyword",
1100             "query": [% aonHost( o.host ).json %]
1101             }
1102             },
1103             [% END %]
1104              
1105             This function is only available for the search template.
1106              
1107             =head2 c
1108              
1109             This wraps L<Term::ANSIColor>->color.
1110              
1111             [% c("cyan") %][% f.timestamp %] [% c("bright_blue") %][% f.logsource %]
1112              
1113             This function is only available for the output template.
1114              
1115             =head2 pd
1116              
1117             This is a time helper.
1118              
1119             /^-/ appends "now" to it. So "-5m" becomes "now-5m".
1120              
1121             /^u\:/ takes what is after ":" and uses Time::ParseDate to convert
1122             it to a unix time value.
1123              
1124             Any thing not matching maching any of the above will just be passed on.
1125              
1126             [% IF o.dgt %]
1127             {"range": {
1128             "@timestamp": {
1129             "gt": [% pd( o.dgt ).json %]
1130             }
1131             }
1132             },
1133             [% END %]
1134              
1135              
1136             =head1 Modules
1137              
1138             Additonal modules bundling help, options, search, and output
1139             can be made. The requirement for these are as below.
1140              
1141             They need to exist below Search::ESsearcher::Templates.
1142              
1143             Provide the following functions that return strings.
1144              
1145             help
1146             options
1147             search
1148             output
1149              
1150             Basic information as to what is required to make it work in logstash
1151             or the like is also good as well.
1152              
1153             =head1 ERROR CODES/FLAGS
1154              
1155             All error handling is done via L<Error::Helper>.
1156              
1157             =head2 1 / IOerror
1158              
1159             Failed to perform some sort of file operation.
1160              
1161             =head2 2 / NOfile
1162              
1163             The specified template/config does not exist.
1164              
1165             =head2 3 / nameIsInvalid
1166              
1167             Invalid name specified.
1168              
1169             =head2 4 / searchNotUsable
1170              
1171             Errored while processing the template.
1172              
1173             For more information on writing templates, see L<Template>.
1174              
1175             =head2 5 / elasticnotloadable
1176              
1177             The Elasticsearch config does not parse as JSON, preventing
1178             it from being loaded.
1179              
1180             =head2 6 / notResults
1181              
1182             The return value passed to results_process deos not appear to
1183             be a results return. Most likely the search errored and returned
1184             undef or a blank value.
1185              
1186             =head1 AUTHOR
1187              
1188             Zane C. Bowers-Hadley, C<< <vvelox at vvelox.net> >>
1189              
1190             =head1 BUGS
1191              
1192             Please report any bugs or feature requests to C<bug-search-essearcher at rt.cpan.org>, or through
1193             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Search-ESsearcher>. I will be notified, and then you'll
1194             automatically be notified of progress on your bug as I make changes.
1195              
1196              
1197              
1198              
1199             =head1 SUPPORT
1200              
1201             You can find documentation for this module with the perldoc command.
1202              
1203             perldoc Search::ESsearcher
1204              
1205              
1206             You can also look for information at:
1207              
1208             =over 4
1209              
1210             =item * RT: CPAN's request tracker (report bugs here)
1211              
1212             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Search-ESsearcher>
1213              
1214             =item * AnnoCPAN: Annotated CPAN documentation
1215              
1216             L<http://annocpan.org/dist/Search-ESsearcher>
1217              
1218             =item * CPAN Ratings
1219              
1220             L<https://cpanratings.perl.org/d/Search-ESsearcher>
1221              
1222             =item * Search CPAN
1223              
1224             L<https://metacpan.org/release/Search-ESsearcher>
1225              
1226             =item * Repository
1227              
1228             L<https://github.com/VVelox/Search-ESsearcher>
1229              
1230             =back
1231              
1232              
1233             =head1 ACKNOWLEDGEMENTS
1234              
1235              
1236             =head1 LICENSE AND COPYRIGHT
1237              
1238             This software is Copyright (c) 2019 by Zane C. Bowers-Hadley.
1239              
1240             This is free software, licensed under:
1241              
1242             The Artistic License 2.0 (GPL Compatible)
1243              
1244              
1245             =cut
1246              
1247             1; # End of Search::ESsearcher