File Coverage

blib/lib/Rex/Inline.pm
Criterion Covered Total %
statement 34 36 94.4
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 48 95.8


line stmt bran cond sub pod time code
1             #
2             # (c) Johnny Wang <johnnywang1991@msn.com>
3             #
4             # vim: set ts=2
5             # vim: set sw=2
6             # vim: set tw=0
7             # vim: set expandtab
8              
9             =encoding UTF-8
10              
11             =head1 NAME
12              
13             Rex::Inline - write Rex in perl
14              
15             =head1 DESCRIPTION
16              
17             Rex::Inline is an API of I<Rex> module write with Moose.
18              
19             when you want use rex in your perl program, and do not want to use the B<rex> command line, you can try to use this module.
20              
21             =head1 GETTING HELP
22            
23             =over 4
24            
25             =item * Bug Tracker: L<https://github.com/johnnywang1991/RexInline/issues>
26            
27             =back
28              
29             =head1 SYNOPSIS
30              
31             use Rex::Inline;
32             use Rex::Inline::Test;
33              
34             my $rex_inline = Rex::Inline->new(
35             use_debug => 0
36             # now you can set default authentication
37             user => $user, # optional
38             password => $password, # optional
39             public_key => $public_key, # optional
40             private_key => $private_key,# optional
41             );
42              
43             # add default authentication
44             # if you didn't provide authentication in your task, Rex::Inline will use this as default one
45             # or if your authentication is failed, Rex::Inline will use this retry the ssh connection
46             $rex_inline->add_auth({
47             user => $user,
48             password => $password,
49             sudo => TRUE,
50             });
51             $rex_inline->add_auth({
52             user => $user,
53             public_key => $public_key,
54             private_key => $private_key,
55             });
56              
57             # data reference like this
58             $rex_inline->add_task(
59             {
60             name => 'something_uniq_string', # name is required when add data_reference task
61             func => sub { # func is required when add data_reference task
62             ...
63             },
64             user => $user,
65             server => [@server],
66             # if need password
67             password => $password,
68             # optional
69             public_key => $public_key,
70             private_key => $private_key,
71             }
72             );
73              
74             # or Rex::Inline::Test is based on Rex::Inline::Base module
75             # See Rex::Inline::Base Documents
76             $rex_inline->add_task(
77             Rex::Inline::Test->new(
78             user => $user,
79             server => [@server],
80             # if need password
81             password => $password,
82             # optional
83             public_key => $public_key,
84             private_key => $private_key,
85             # input param, in any format you want
86             input => $input,
87             )
88             );
89              
90             $rex_inline->execute;
91              
92             # get rex task reports
93             $rex_inline->reports;
94              
95             =cut
96             package Rex::Inline;
97              
98 1     1   359202 use strict;
  1         1  
  1         34  
99 1     1   3 use warnings;
  1         2  
  1         22  
100              
101 1     1   633 use utf8;
  1         11  
  1         3  
102 1     1   402 use FindBin;
  1         757  
  1         39  
103 1     1   419 use POSIX 'strftime';
  1         4368  
  1         5  
104              
105             our $VERSION = '0.0.8'; # VERSION
106              
107 1     1   743 use Moose;
  1         1  
  1         7  
108 1     1   5191 use MooseX::AttributeShortcuts;
  1         260431  
  1         8  
109              
110 1     1   26849 use File::Temp 'mkdtemp';
  1         14153  
  1         68  
111 1     1   390 use File::Path::Tiny;
  1         446  
  1         36  
112 1     1   428 use File::Spec::Functions;
  1         545  
  1         100  
113              
114 1     1   431 use YAML::XS qw(LoadFile Dump);
  1         1903  
  1         46  
115 1     1   176 use JSON;
  0            
  0            
116             use Parallel::ForkManager;
117              
118             use Rex -feature => 0.31;
119             use Rex::Config;
120             use Rex::Group;
121             use Rex::TaskList;
122              
123             # custom module
124             use Rex::Inline::Test;
125              
126             use namespace::autoclean;
127              
128             use Moose::Util::TypeConstraints;
129             subtype 'TaskType'
130             => as 'ArrayRef[Object]';
131             coerce 'TaskType'
132             => from 'ArrayRef',
133             => via { [ map { (ref $_ eq 'HASH') ? Rex::Inline::Test->new($_) : $_ } @$_ ] };
134             no Moose::Util::TypeConstraints;
135              
136             =head1 ATTRIBUTES
137              
138             =over 4
139              
140             =item user
141              
142             set default ssh connection user
143              
144             =item password
145              
146             set default ssh connection password
147              
148             =item private_key
149              
150             set default private_key filename
151              
152             =item public_key
153              
154             set default public_key filename
155              
156             =cut
157              
158             has [qw(user password private_key public_key)] => (is => 'ro', predicate => 1);
159              
160             =item use_debug
161              
162             set/get debug option (Bool)
163              
164             Print or not debug level log
165              
166             see B<rex -d> option
167              
168             default is 0 (disabled)
169             =cut
170             has use_debug => (is => 'rw', default => 0);
171              
172             =item use_cache
173              
174             set/get use_cache option (Bool)
175              
176             Use or not B<rex -c> option
177              
178             default is 1 (enable)
179             =cut
180             has use_cache => (is => 'rw', default => 1);
181              
182             =item use_report
183              
184             set/get use_report option (Bool)
185              
186             show rex report result
187              
188             default is 1 (enable)
189             =cut
190             has use_report => (is => 'rw', default => 1);
191              
192             =item use_report_log
193              
194             set/get use_report_log option (Bool)
195              
196             report to log
197              
198             default is 0 (false)
199             =cut
200             has use_report_log => (is => 'rw', default => 0);
201              
202              
203             =item log_dir
204              
205             set/get log dir (String)
206              
207             default is C<"./rexlogs/">
208             =cut
209             has log_dir => (is => 'rw', default => './rexlogs/');
210              
211             =item parallelism
212              
213             set/get parallelism nums (Int)
214              
215             see B<rex -t> option
216              
217             default is 5
218             =cut
219             has parallelism => (is => 'rw', default => 5);
220              
221             =item log_paths
222              
223             get log paths (ArrayRef)
224              
225             format is
226              
227             [{task_id => log_path}, ...]
228              
229             I<readonly>
230             =cut
231             has log_paths => (
232             is => 'ro',
233             default => sub{[]},
234             traits => ['Array'],
235             handles => {add_log_paths => 'push'},
236             );
237             =item reports
238              
239             get rex process reports (ArrayRef)
240              
241             format is:
242              
243             [{report => $report_ref, task_id => $task_id, date => $date, hostname => $hostname}, ...]
244              
245             I<readonly>
246             =cut
247             has reports => (
248             is => 'ro',
249             default => sub{[]},
250             traits => ['Array'],
251             handles => {
252             add_reports => 'push',
253             map_reports => 'map'
254             }
255             );
256              
257             =back
258             =cut
259              
260             has date => (is => 'ro', lazy => 1, builder => 1); # date: format is YYmmdd
261             has prefix => (is => 'ro', lazy => 1, builder => 1); # log prefix dir
262             has tasklist => (is => 'ro', lazy => 1, builder => 1); # rex tasklist base object, use private
263             has pm => (is => 'ro', lazy => 1, builder => 1); # parallel forkmanager object, use private
264              
265             =head1 METHODS
266              
267             =over 4
268              
269             =item add_task
270              
271             add B<Rex::Inline::Base> Object to TaskList
272              
273             or Add Data reference to TaskList
274              
275             my $rex_inline = Rex::Inline->new;
276              
277             $rex_inline->add_task({
278             name => 'something_uniq_string', # required when add data_reference task
279             func => sub { # required when add data_reference task
280             ...
281             },
282             user => $user2,
283             server => [@server2],
284             # if need password
285             password => $password2,
286             # optional
287             public_key => $public_key2,
288             private_key => $private_key2,
289             });
290              
291             ...
292              
293             =cut
294              
295             has task => (
296             is => 'ro',
297             isa => 'TaskType',
298             coerce => 1,
299             default => sub{[]},
300             traits => ['Array'],
301             handles => {add_task => 'push'},
302             );
303              
304             =item add_auth
305              
306             Add an authentication fallback
307              
308             This is the default authentication
309              
310             If all you provide authentications is failed, B<Rex::Inline> will try to use this one
311              
312             $rex_inline->add_auth({
313             user => $user,
314             password => $password,
315             sudo => TRUE,
316             });
317             $rex_inline->add_auth({
318             user => $user,
319             public_key => $public_key,
320             private_key => $private_key,
321             });
322              
323             =cut
324              
325             has auth => (
326             is => 'ro',
327             isa => 'ArrayRef[HashRef]',
328             default => sub{[]},
329             traits => ['Array'],
330             handles => {add_auth => 'push'},
331             predicate => 1
332             );
333              
334             =item execute
335              
336             Execute all loaded Task in parallel
337              
338             $rex_inline->execute;
339              
340             =cut
341             sub execute {
342             my $self = shift;
343              
344             ### setup parallel forkmanager
345             $self->pm->run_on_finish(sub {
346             my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $results) = @_;
347             # retrieve data structure from child
348             if ($results) { # children are not forced to send anything
349             my @reports = @{$results->{reports}};
350             $self->add_reports(@reports) if @reports;
351              
352             my @log_paths = @{$results->{log_paths}};
353             $self->add_log_paths(@log_paths) if @log_paths;
354             }
355             });
356              
357             ### run task list
358             for my $task_in_list ($self->tasklist->get_tasks) {
359             $self->pm->start and next;
360              
361             my @reports;
362             my @log_paths;
363             if ( $self->tasklist->is_task($task_in_list) ) {
364             my $task_id = $self->tasklist->get_task($task_in_list)->desc;
365             ### set logging path
366             my $log_path = catfile( $self->prefix, "${task_id}.log" );
367             logging to_file => $log_path;
368             push @log_paths, $log_path;
369             ### set report path
370             my $report_path = mkdtemp( sprintf("%s/reports_XXXXXX", $self->prefix) );
371             set report_path => $report_path;
372             ### run
373             $self->tasklist->run($task_in_list);
374             ### fetch reports
375             push @reports, $self->_fetch_reports($task_in_list, $report_path, $task_id) if $self->use_report;
376             }
377              
378             $self->pm->finish(0, {reports => [@reports], log_paths => [@log_paths]});
379             }
380              
381             ### wait parallel task
382             $self->pm->wait_all_children;
383             }
384              
385             =item report_as_yaml
386              
387             my $yaml_report = $rex_inline->report_as_yaml;
388              
389             =item report_as_json
390              
391             my $json_report = $rex_inline->report_as_json;
392              
393             =cut
394              
395             sub report_as_yaml { Dump( [ shift->map_reports(sub { Dump($_) }) ] ) }
396             sub report_as_json { encode_json( [shift->map_reports(sub { encode_json($_) })] ) }
397              
398             =item print_as_yaml
399              
400             $rex_inline->print_as_yaml;
401              
402             =item print_as_json
403              
404             $rex_inline->print_as_json;
405              
406             =cut
407              
408             sub print_as_yaml { print join("\n", shift->map_reports(sub { Dump($_) })), "\n" }
409             sub print_as_json { print join("\n", shift->map_reports(sub { encode_json($_) })), "\n" }
410              
411             =back
412             =cut
413              
414             sub _fetch_reports {
415             my $self = shift;
416             my ($task_name, $report_path, $task_id) = @_;
417              
418             my @reports;
419              
420             ### read report path
421             for my $server ( @{ $self->tasklist->get_task($task_name)->server } ) {
422             my $report;
423              
424             for my $report_file ( glob catfile( $report_path, $server, '*.yml' ) ) {
425             my $report_content = eval { LoadFile($report_file) };
426             $report = {
427             report => $report_content,
428             group => $task_id,
429             date => $self->date,
430             host => $server->name
431             };
432             }
433             rmdir catdir( $report_path, $server );
434              
435             unless ($report) {
436             ### log failed
437             $report = {
438             report => {
439             task => {
440             failed => '1',
441             message => sprintf(
442             'Wrong username/password or wrong key on %s. Or root is not permitted to login over SSH.',
443             $server->name
444             )
445             }
446             },
447             group => $task_id,
448             date => $self->date,
449             host => $server->name
450             };
451             }
452              
453             ### push report
454             push @reports, $report;
455             }
456             rmdir $report_path;
457              
458             Rex::Logger::info( join("\n", map { Dump($_) } @reports) ) if $self->use_report_log;
459             return @reports;
460             }
461              
462             sub _build_tasklist {
463             my $self = shift;
464            
465             ### set log debug level
466             if ($self->use_debug) {
467             $Rex::Logger::debug = $self->debug_bool;
468             $Rex::Logger::silent = 0;
469             }
470              
471             ### force use ssh instead openssh
472             set connection => "SSH";
473             ### set parallelism
474             parallelism($self->parallelism);
475             ### set use cache
476             Rex::Config->set_use_cache($self->use_cache);
477             ### set report
478             Rex::Config->set_do_reporting($self->use_report);
479             Rex::Config->set_report_type('YAML');
480              
481             ### initial task list
482             for my $task (@{$self->task}) {
483             ### setup new connection group
484             group $task->id => @{$task->server};
485             ### setup auth for group
486             auth for => $task->id => %{$task->task_auth};
487             ### initial task
488             desc $task->id;
489             ### last param overwrite the caller module name Rex Commands line 284-286
490             task $task->name, group => $task->id, $task->func, { class => "Rex::CLI" };
491             }
492              
493             ### default auth
494             my @default_auth;
495             if ($self->{user}) {
496             @default_auth = ( user => $self->{user} );
497             for (qw(password public_key private_key)) {
498             push @default_auth, $_ => $self->{$_} if $self->{$_};
499             }
500             }
501             $self->add_auth({@default_auth}) if @default_auth;
502              
503             ### add auth fallback
504             auth fallback => @{ $self->auth } if $self->has_auth;
505              
506             return Rex::TaskList->create;
507             }
508              
509             sub _build_date { strftime "%Y%m%d", localtime(time) }
510             sub _build_prefix {
511             my $self = shift;
512             my $prefix = catdir($self->log_dir, $self->date);
513              
514             File::Path::Tiny::mk($prefix) unless -d $prefix;
515              
516             return $prefix;
517             }
518             sub _build_pm { Parallel::ForkManager->new(10) }
519              
520             __PACKAGE__->meta->make_immutable;