File Coverage

lib/Rex/TaskList/Base.pm
Criterion Covered Total %
statement 181 221 81.9
branch 52 72 72.2
condition 27 45 60.0
subroutine 33 37 89.1
pod 0 20 0.0
total 293 395 74.1


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             package Rex::TaskList::Base;
6              
7 56     56   942 use v5.12.5;
  56         265  
8 56     56   288 use warnings;
  56         156  
  56         2782  
9              
10             our $VERSION = '1.14.3'; # VERSION
11              
12             BEGIN {
13 56     56   528 use Rex::Shared::Var;
  56         176  
  56         3361  
14 56     56   310 share qw(@SUMMARY);
15             }
16              
17 56     56   357 use Data::Dumper;
  56         115  
  56         3005  
18 56     56   358 use Rex::Logger;
  56         126  
  56         527  
19 56     56   1492 use Rex::Task;
  56         147  
  56         608  
20 56     56   1664 use Rex::Config;
  56         122  
  56         330  
21 56     56   383 use Rex::Interface::Executor;
  56         806  
  56         627  
22 56     56   1545 use Rex::Fork::Manager;
  56         187  
  56         605  
23 56     56   1916 use Rex::Report;
  56         131  
  56         318  
24 56     56   1366 use Rex::Group;
  56         147  
  56         541  
25 56     56   1648 use Time::HiRes qw(time);
  56         248  
  56         413  
26 56     56   5139 use POSIX qw(floor);
  56         514  
  56         1994  
27              
28             sub new {
29 61     61 0 162 my $that = shift;
30 61   33     366 my $proto = ref($that) || $that;
31 61         240 my $self = {@_};
32              
33 61         138 bless( $self, $proto );
34              
35 61         279 $self->{IN_TRANSACTION} = 0;
36 61         786 $self->{DEFAULT_AUTH} = Rex::Config->get_default_auth();
37 61         268 $self->{tasks} = {};
38              
39 61         402 return $self;
40             }
41              
42             sub create_task {
43 212     212 0 424 my $self = shift;
44 212         315 my $task_name = shift;
45 212         319 my $options = pop;
46 212         316 my $desc = pop;
47              
48 212 50       721 if ( exists $self->{tasks}->{$task_name} ) {
49 0         0 Rex::Logger::info( "Task $task_name already exists. Overwriting...",
50             "warn" );
51             }
52              
53 212         810 Rex::Logger::debug("Creating task: $task_name");
54              
55 212         350 my $func;
56 212 50       671 if ( ref($desc) eq "CODE" ) {
57 0         0 $func = $desc;
58 0         0 $desc = "";
59             }
60             else {
61 212         373 $func = pop;
62             }
63              
64             # matching against a task count of 2 because of the two internal tasks (filtered below)
65 212 100       311 if ( ( scalar( keys %{ $self->{tasks} } ) ) == 2 ) {
  212         841  
66 39         492 my $requested_env = Rex::Config->get_environment;
67 39         722 my @environments = Rex::Commands->get_environments;
68              
69 39 50 33     582 if ( $task_name ne 'Commands:Box:get_sys_info'
      33        
      33        
70             && $task_name ne 'Test:run'
71             && $requested_env ne ''
72 0         0 && !grep { $_ eq $requested_env } @environments )
73             {
74 0         0 Rex::Logger::info(
75             "Environment '$requested_env' has been requested, but it could not be found in the Rexfile. This is most likely only by mistake.",
76             'warn'
77             );
78 0         0 Rex::Logger::info(
79             "If it is intentional, you can suppress this warning by specifying an empty environment: environment '$requested_env' => sub {};",
80             'warn'
81             );
82             }
83             }
84              
85 212         421 my @server = ();
86              
87 212 100       444 if ($::FORCE_SERVER) {
88              
89 1 50       3 if ( ref $::FORCE_SERVER eq "ARRAY" ) {
90 0         0 my $group_name_arr = $::FORCE_SERVER;
91              
92 0         0 for my $group_name ( @{$group_name_arr} ) {
  0         0  
93 0 0       0 if ( !Rex::Group->is_group($group_name) ) {
94 0         0 Rex::Logger::debug("Using late group-lookup");
95              
96             push @server, sub {
97 0 0   0   0 if ( !Rex::Group->is_group($group_name) ) {
98 0         0 Rex::Logger::info( "No group $group_name defined.", "error" );
99 0         0 exit 1;
100             }
101              
102             return
103 0         0 map { Rex::Group::Entry::Server->new( name => $_ )->get_servers; }
  0         0  
104             Rex::Group->get_group($group_name);
105 0         0 };
106             }
107             else {
108              
109             push( @server,
110 0         0 map { Rex::Group::Entry::Server->new( name => $_ ); }
  0         0  
111             Rex::Group->get_group($group_name) );
112              
113             }
114             }
115             }
116             else {
117 1         8 my @servers = split( /\s+/, $::FORCE_SERVER );
118             push( @server,
119 1         2 map { Rex::Group::Entry::Server->new( name => $_ ); } @servers );
  2         11  
120              
121 1         48 Rex::Logger::debug("\tserver: $_") for @server;
122             }
123              
124             }
125              
126             else {
127              
128 211 100       610 if ( scalar(@_) >= 1 ) {
129 13 100       61 if ( $_[0] eq "group" ) {
130 6         9 my $groups;
131 6 100       29 if ( ref( $_[1] ) eq "ARRAY" ) {
132 1         4 $groups = $_[1];
133             }
134             else {
135 5         16 $groups = [ $_[1] ];
136             }
137              
138 6         12 for my $group ( @{$groups} ) {
  6         15  
139 7 100       29 if ( Rex::Group->is_group($group) ) {
140 6         21 my @group_server = Rex::Group->get_group($group);
141              
142             # check if the group is empty. this is mostly due to a failure.
143             # so report it, and exit.
144 6 50 33     25 if ( scalar @group_server == 0
145             && Rex::Config->get_allow_empty_groups() == 0 )
146             {
147 0         0 Rex::Logger::info(
148             "The group $group is empty. This is mostly due to a failure.",
149             "warn" );
150 0         0 Rex::Logger::info(
151             "If this is an expected behaviour, please add the feature flag 'empty_groups'.",
152             "warn"
153             );
154 0         0 CORE::exit(1);
155             }
156 6         24 push( @server, @group_server );
157             }
158             else {
159 1         7 Rex::Logger::debug("Using late group-lookup");
160              
161             push @server, sub {
162 1 50   1   5 if ( !Rex::Group->is_group($group) ) {
163 0         0 Rex::Logger::info( "No group $group defined.", "error" );
164 0         0 exit 1;
165             }
166              
167             return map {
168 1 50 33     5 if ( ref $_ && $_->isa("Rex::Group::Entry::Server") ) {
  1         13  
169 1         4 $_->get_servers;
170             }
171             else {
172 0         0 Rex::Group::Entry::Server->new( name => $_ )->get_servers;
173             }
174             } Rex::Group->get_group($group);
175 1         9 };
176              
177             }
178             }
179             }
180             else {
181 7         17 for my $entry (@_) {
182 10 100 66     103 push(
183             @server,
184             (
185             ref $entry && $entry->isa("Rex::Group::Entry::Server")
186             ? $entry
187             : Rex::Group::Entry::Server->new( name => $entry )
188             )
189             );
190             }
191             }
192             }
193              
194             }
195              
196             my %task_hash = (
197             func => $func,
198             server => [@server],
199             desc => $desc,
200             no_ssh => ( $options->{"no_ssh"} ? 1 : 0 ),
201             hidden => ( $options->{"dont_register"} ? 1 : 0 ),
202             exit_on_connect_fail => (
203             exists $options->{exit_on_connect_fail}
204             ? $options->{exit_on_connect_fail}
205 212 100       2115 : 1
    100          
    100          
206             ),
207             before => [],
208             after => [],
209             around => [],
210             after_task_finished => [],
211             before_task_start => [],
212             name => $task_name,
213             executor => Rex::Interface::Executor->create,
214             connection_type => Rex::Config->get_connection_type,
215             );
216              
217 212 100       873 if ( $self->{DEFAULT_AUTH} ) {
218             $task_hash{auth} = {
219 207   50     1294 user => Rex::Config->get_user || undef,
      100        
      100        
      100        
      50        
220             password => Rex::Config->get_password || undef,
221             private_key => Rex::Config->get_private_key || undef,
222             public_key => Rex::Config->get_public_key || undef,
223             sudo_password => Rex::Config->get_sudo_password || undef,
224             };
225             }
226              
227 212 100       804 if ( exists $Rex::Commands::auth_late{$task_name} ) {
228 1         6 $task_hash{auth} = $Rex::Commands::auth_late{$task_name};
229             }
230              
231 212         1939 $self->{tasks}->{$task_name} = Rex::Task->new(%task_hash);
232              
233 212         1300 return $self->{tasks}->{$task_name};
234             }
235              
236             sub get_tasks {
237 17     17 0 64 my $self = shift;
238 60         175 return grep { $self->{tasks}->{$_}->hidden() == 0 }
239 17         59 sort { $a cmp $b } keys %{ $self->{tasks} };
  71         203  
  17         269  
240             }
241              
242             sub get_all_tasks {
243 96     96 0 1481 my $self = shift;
244 96         159 my $regexp = shift;
245              
246 247         1598 return grep { $_ =~ $regexp }
247 96         149 keys %{ $self->{tasks} };
  96         402  
248             }
249              
250             sub get_tasks_for {
251 2     2 0 4 my $self = shift;
252 2         9 my $host = shift;
253              
254 2         3 my @tasks;
255 2         5 for my $task_name ( keys %{ $self->{tasks} } ) {
  2         6  
256 6         10 my @servers = @{ $self->{tasks}->{$task_name}->server() };
  6         21  
257              
258 6 100 66     14 if ( ( grep { /^$host$/ } @servers ) || $#servers == -1 ) {
  28         98  
259 3         40 push @tasks, $task_name;
260             }
261             }
262              
263 2         20 my @ret = sort { $a cmp $b } @tasks;
  1         5  
264 2         11 return @ret;
265             }
266              
267             sub get_task {
268 273     273 0 4122 my ( $self, $task ) = @_;
269 273         931 return $self->{tasks}->{$task};
270             }
271              
272             sub clear_tasks {
273 7     7 0 8 my $self = shift;
274 7         41 $self->{tasks} = {};
275             }
276              
277             sub get_desc {
278 1     1 0 2 my $self = shift;
279 1         2 my $task = shift;
280              
281 1         6 return $self->{tasks}->{$task}->desc();
282             }
283              
284             sub is_task {
285 192     192 0 335 my $self = shift;
286 192         416 my $task = shift;
287              
288 192 100       469 if ( exists $self->{tasks}->{$task} ) { return 1; }
  126         403  
289 66         168 return 0;
290             }
291              
292 16     16 0 134 sub current_task { shift->{__current_task__} }
293              
294             sub run {
295 138     138 0 737 my ( $self, $task, %options ) = @_;
296              
297 138 50       818 if ( !ref $task ) {
298 0         0 $task = Rex::TaskList->create()->get_task($task);
299             }
300              
301 138         1113 my $fm = Rex::Fork::Manager->new( max => $self->get_thread_count($task) );
302 138         621 my $all_servers = $task->server;
303              
304 138         634 for my $server (@$all_servers) {
305 138         833 my $child_coderef = $self->build_child_coderef( $task, $server, %options );
306              
307 138 100       859 if ( $self->{IN_TRANSACTION} ) {
308              
309             # Inside a transaction -- no forking and no chance to get zombies.
310             # This only happens if someone calls do_task() from inside a transaction.
311 3         18 $child_coderef->();
312             }
313             else {
314             # Not inside a transaction, so lets fork
315             # Add $forked_sub to the fork queue
316 135         760 $fm->add($child_coderef);
317             }
318             }
319              
320 107         3712 Rex::Logger::debug("Waiting for children to finish");
321 107         1544 my $ret = $fm->wait_for_all;
322 107         2710 Rex::reconnect_lost_connections();
323              
324 107         14586 return $ret;
325             }
326              
327             sub build_child_coderef {
328 138     138 0 545 my ( $self, $task, $server, %options ) = @_;
329              
330             return sub {
331 32     32   2169 Rex::Logger::init();
332 32         1239 Rex::Logger::info( "Running task " . $task->name . " on $server" );
333              
334 32         229 my $return_value = eval {
335             $task->clone->run(
336             $server,
337             in_transaction => $self->{IN_TRANSACTION},
338             params => $options{params},
339             args => $options{args},
340 32         783 );
341             };
342              
343 32 100       780 if ( $self->{IN_TRANSACTION} ) {
344 3 100       204 die $@ if $@;
345             }
346             else {
347 29         169 my $e = $@;
348 29 100 100     254 my $exit_code = $@ ? ( $? || 1 ) : 0;
349              
350 29         235 push @SUMMARY,
351             {
352             task => $task->name,
353             server => $server->to_s,
354             exit_code => $exit_code,
355             error_message => $e,
356             };
357             }
358              
359 30         409 Rex::Logger::debug("Destroying all cached os information");
360 30         322 Rex::Logger::shutdown();
361              
362 30         349 return $return_value;
363 138         2084 };
364             }
365              
366             sub modify {
367 85     85 0 269 my ( $self, $type, $task, $code, $package, $file, $line ) = @_;
368              
369 85 100 100     447 if ( $package ne "main" && $package ne "Rex::CLI" ) {
370 5 100       21 if ( $task !~ m/:/ ) {
371              
372             #do we need to detect for base -Rex ?
373 4         7 $package =~ s/^Rex:://;
374             }
375             }
376              
377 85         239 $package =~ s/::/:/g;
378              
379 85         253 my @all_tasks = map { $self->get_task($_); } grep {
380 85 100 100     241 if ( $package ne "main" && $package ne "Rex:CLI" ) {
  85         425  
381 5         56 $_ =~ m/^\Q$package\E:/;
382             }
383             else {
384 80         204 $_;
385             }
386             } $self->get_all_tasks($task);
387              
388 85 50       317 if ( !@all_tasks ) {
389 0         0 Rex::Logger::info(
390             "Can't add $type $task, as it is not yet defined\nsee $file line $line");
391 0         0 return;
392             }
393              
394 85         206 for my $taskref (@all_tasks) {
395 85         267 $taskref->modify( $type => $code );
396             }
397             }
398              
399             sub set_default_auth {
400 0     0 0 0 my ( $self, $auth ) = @_;
401 0         0 $self->{DEFAULT_AUTH} = $auth;
402             }
403              
404             sub is_default_auth {
405 1     1 0 2 my ($self) = @_;
406 1         17 return $self->{DEFAULT_AUTH};
407             }
408              
409             sub set_in_transaction {
410 6     6 0 26 my ( $self, $val ) = @_;
411 6         299 $self->{IN_TRANSACTION} = $val;
412             }
413              
414             sub is_transaction {
415 0     0 0 0 my ($self) = @_;
416 0         0 return $self->{IN_TRANSACTION};
417             }
418              
419             sub get_exit_codes {
420 0     0 0 0 my ($self) = @_;
421 0         0 return map { $_->{exit_code} } @SUMMARY;
  0         0  
422             }
423              
424             sub get_thread_count {
425 138     138 0 721 my ( $self, $task ) = @_;
426 138   33     1992 my $threads = $task->parallelism || Rex::Config->get_parallelism;
427 138         579 my $server_count = scalar @{ $task->server };
  138         1979  
428              
429 138 50       1332 return $1 if $threads =~ /^(\d+)$/;
430 0 0       0 return floor( $server_count / $1 ) if $threads =~ /^max\s?\/(\d+)$/;
431 0 0       0 return floor( $server_count * $1 / 100 ) if $threads =~ /^max (\d+)%$/;
432 0 0       0 return $server_count if $threads eq 'max';
433              
434 0         0 Rex::Logger::info(
435             "Unrecognized thread count requested: '$threads'. Falling back to a single thread.",
436             'warn'
437             );
438 0         0 return 1;
439             }
440              
441 51     51 0 1688 sub get_summary { @SUMMARY }
442              
443             1;