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 1
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   330235 use strict;
  1         2  
  1         31  
99 1     1   4 use warnings;
  1         1  
  1         24  
100              
101 1     1   514 use utf8;
  1         10  
  1         4  
102 1     1   414 use FindBin;
  1         709  
  1         35  
103 1     1   438 use POSIX 'strftime';
  1         4277  
  1         8  
104              
105             our $VERSION = '0.0.5'; # VERSION
106              
107 1     1   748 use Moose;
  1         2  
  1         9  
108 1     1   5347 use MooseX::AttributeShortcuts;
  1         221091  
  1         5  
109              
110 1     1   21113 use File::Temp 'mkdtemp';
  1         11878  
  1         45  
111 1     1   325 use File::Path::Tiny;
  1         392  
  1         19  
112 1     1   329 use File::Spec::Functions;
  1         467  
  1         54  
113              
114 1     1   312 use YAML::XS qw(LoadFile Dump);
  1         1623  
  1         40  
115 1     1   165 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 7
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 log_dir
193              
194             set/get log dir (String)
195              
196             default is C<"./rexlogs/">
197             =cut
198             has log_dir => (is => 'rw', default => './rexlogs/');
199              
200             =item parallelism
201              
202             set/get parallelism nums (Int)
203              
204             see B<rex -t> option
205              
206             default is 5
207             =cut
208             has parallelism => (is => 'rw', default => 5);
209              
210             =item log_paths
211              
212             get log paths (ArrayRef)
213              
214             format is
215              
216             [{task_id => log_path}, ...]
217              
218             I<readonly>
219             =cut
220             has log_paths => (
221             is => 'ro',
222             default => sub{[]},
223             traits => ['Array'],
224             handles => {add_log_paths => 'push'},
225             );
226             =item reports
227              
228             get rex process reports (ArrayRef)
229              
230             format is:
231              
232             [{report => $report_ref, task_id => $task_id, date => $date, hostname => $hostname}, ...]
233              
234             I<readonly>
235             =cut
236             has reports => (
237             is => 'ro',
238             default => sub{[]},
239             traits => ['Array'],
240             handles => {
241             add_reports => 'push',
242             map_reports => 'map'
243             }
244             );
245              
246             =back
247             =cut
248              
249             has date => (is => 'ro', lazy => 1, builder => 1); # date: format is YYmmdd
250             has prefix => (is => 'ro', lazy => 1, builder => 1); # log prefix dir
251             has tasklist => (is => 'ro', lazy => 1, builder => 1); # rex tasklist base object, use private
252             has pm => (is => 'ro', lazy => 1, builder => 1); # parallel forkmanager object, use private
253              
254             =head1 METHODS
255              
256             =over 7
257              
258             =item add_task
259              
260             add B<Rex::Inline::Base> Object to TaskList
261              
262             or Add Data reference to TaskList
263              
264             my $rex_inline = Rex::Inline->new;
265              
266             $rex_inline->add_task({
267             name => 'something_uniq_string', # required when add data_reference task
268             func => sub { # required when add data_reference task
269             ...
270             },
271             user => $user2,
272             server => [@server2],
273             # if need password
274             password => $password2,
275             # optional
276             public_key => $public_key2,
277             private_key => $private_key2,
278             });
279              
280             ...
281              
282             =cut
283              
284             has task => (
285             is => 'ro',
286             isa => 'TaskType',
287             coerce => 1,
288             default => sub{[]},
289             traits => ['Array'],
290             handles => {add_task => 'push'},
291             );
292              
293             =item add_auth
294              
295             Add an authentication fallback
296              
297             This is the default authentication
298              
299             If all you provide authentications is failed, B<Rex::Inline> will try to use this one
300              
301             $rex_inline->add_auth({
302             user => $user,
303             password => $password,
304             sudo => TRUE,
305             });
306             $rex_inline->add_auth({
307             user => $user,
308             public_key => $public_key,
309             private_key => $private_key,
310             });
311              
312             =cut
313              
314             has auth => (
315             is => 'ro',
316             isa => 'ArrayRef[HashRef]',
317             default => sub{[]},
318             traits => ['Array'],
319             handles => {add_auth => 'push'},
320             predicate => 1
321             );
322              
323             =item execute
324              
325             Execute all loaded Task in parallel
326              
327             $rex_inline->execute;
328              
329             =cut
330             sub execute {
331             my $self = shift;
332              
333             ### setup parallel forkmanager
334             $self->pm->run_on_finish(sub {
335             my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $results) = @_;
336             # retrieve data structure from child
337             if ($results) { # children are not forced to send anything
338             my @reports = @{$results->{reports}};
339             $self->add_reports(@reports) if @reports;
340              
341             my @log_paths = @{$results->{log_paths}};
342             $self->add_log_paths(@log_paths) if @log_paths;
343             }
344             });
345              
346             ### run task list
347             for my $task_in_list ($self->tasklist->get_tasks) {
348             $self->pm->start and next;
349              
350             my @reports;
351             my @log_paths;
352             if ( $self->tasklist->is_task($task_in_list) ) {
353             my $task_id = $self->tasklist->get_task($task_in_list)->desc;
354             ### set logging path
355             my $log_path = catfile( $self->prefix, "${task_id}.log" );
356             logging to_file => $log_path;
357             push @log_paths, $log_path;
358             ### set report path
359             my $report_path = mkdtemp( sprintf("%s/reports_XXXXXX", $self->prefix) );
360             set report_path => $report_path;
361             ### run
362             $self->tasklist->run($task_in_list);
363             ### fetch reports
364             push @reports, $self->_fetch_reports($task_in_list, $report_path, $task_id) if $self->use_report;
365             }
366              
367             $self->pm->finish(0, {reports => [@reports], log_paths => [@log_paths]});
368             }
369              
370             ### wait parallel task
371             $self->pm->wait_all_children;
372             }
373              
374             =item report_as_yaml
375              
376             my $yaml_report = $rex_inline->report_as_yaml;
377              
378             =item report_as_json
379              
380             my $json_report = $rex_inline->report_as_json;
381              
382             =cut
383              
384             sub report_as_yaml { Dump( [ shift->map_reports(sub { Dump($_) }) ] ) }
385             sub report_as_json { encode_json( [shift->map_reports(sub { encode_json($_) })] ) }
386              
387             =item print_as_yaml
388              
389             $rex_inline->print_as_yaml;
390              
391             =item print_as_json
392              
393             $rex_inline->print_as_json;
394              
395             =cut
396              
397             sub print_as_yaml { print join("\n", shift->map_reports(sub { Dump($_) })), "\n" }
398             sub print_as_json { print join("\n", shift->map_reports(sub { encode_json($_) })), "\n" }
399              
400             =back
401             =cut
402              
403             sub _fetch_reports {
404             my $self = shift;
405             my ($task_name, $report_path, $task_id) = @_;
406              
407             my @reports;
408              
409             ### read report path
410             for my $server ( @{ $self->tasklist->get_task($task_name)->server } ) {
411             my $report;
412              
413             for my $report_file ( glob catfile( $report_path, $server, '*.yml' ) ) {
414             my $report_content = eval { LoadFile($report_file) };
415             $report = {
416             report => $report_content,
417             group => $task_id,
418             date => $self->date,
419             host => $server->name
420             };
421             }
422             rmdir catdir( $report_path, $server );
423              
424             unless ($report) {
425             ### log failed
426             $report = {
427             report => {
428             task => {
429             failed => '1',
430             message => sprintf(
431             'Wrong username/password or wrong key on %s. Or root is not permitted to login over SSH.',
432             $server->name
433             )
434             }
435             },
436             group => $task_id,
437             date => $self->date,
438             host => $server->name
439             };
440             }
441              
442             ### push report
443             push @reports, $report;
444             }
445             rmdir $report_path;
446              
447             return @reports;
448             }
449              
450             sub _build_tasklist {
451             my $self = shift;
452            
453             ### set log debug level
454             if ($self->use_debug) {
455             $Rex::Logger::debug = $self->debug_bool;
456             $Rex::Logger::silent = 0;
457             }
458              
459             ### set parallelism
460             parallelism($self->parallelism);
461             ### set use cache
462             Rex::Config->set_use_cache($self->use_cache);
463             ### set report
464             Rex::Config->set_do_reporting($self->use_report);
465             Rex::Config->set_report_type('YAML');
466              
467             ### initial task list
468             for my $task (@{$self->task}) {
469             ### setup new connection group
470             group $task->id => @{$task->server};
471             ### setup auth for group
472             auth for => $task->id => %{$task->task_auth};
473             ### initial task
474             desc $task->id;
475             ### last param overwrite the caller module name Rex Commands line 284-286
476             task $task->name, group => $task->id, $task->func, { class => "Rex::CLI" };
477             }
478              
479             ### default auth
480             my @default_auth;
481             if ($self->{user}) {
482             @default_auth = ( user => $self->{user} );
483             for (qw(password public_key private_key)) {
484             push @default_auth, $_ => $self->{$_} if $self->{$_};
485             }
486             }
487             $self->add_auth({@default_auth}) if @default_auth;
488              
489             ### add auth fallback
490             auth fallback => @{ $self->auth } if $self->has_auth;
491              
492             return Rex::TaskList->create;
493             }
494              
495             sub _build_date { strftime "%Y%m%d", localtime(time) }
496             sub _build_prefix {
497             my $self = shift;
498             my $prefix = catdir($self->log_dir, $self->date);
499              
500             File::Path::Tiny::mk($prefix) unless -d $prefix;
501              
502             return $prefix;
503             }
504             sub _build_pm { Parallel::ForkManager->new(10) }
505              
506             __PACKAGE__->meta->make_immutable;