File Coverage

blib/lib/App/Dest.pm
Criterion Covered Total %
statement 405 422 95.9
branch 132 180 73.3
condition 48 64 75.0
subroutine 50 50 100.0
pod 22 22 100.0
total 657 738 89.0


line stmt bran cond sub pod time code
1             package App::Dest;
2             # ABSTRACT: Deployment State Manager
3              
4 7     7   1488506 use 5.016;
  7         30  
5 7     7   2897 use exact;
  7         198231  
  7         63  
6              
7 7     7   19229 use File::Basename qw( dirname basename );
  7         22  
  7         579  
8 7     7   1979 use File::Copy 'copy';
  7         22526  
  7         736  
9 7     7   2490 use File::Copy::Recursive qw( dircopy rcopy );
  7         29008  
  7         812  
10 7     7   3657 use File::DirCompare ();
  7         39238  
  7         457  
11 7     7   65 use File::Find 'find';
  7         15  
  7         686  
12 7     7   46 use File::Path qw( mkpath rmtree );
  7         14  
  7         537  
13 7     7   7419 use IPC::Run 'run';
  7         419692  
  7         936  
14 7     7   6256 use Path::Tiny 'path';
  7         158773  
  7         825  
15 7     7   75 use POSIX 'strftime';
  7         18  
  7         67  
16 7     7   17207 use Text::Diff ();
  7         104027  
  7         114899  
17              
18             our $VERSION = '1.36'; # VERSION
19              
20             sub init {
21 5     5 1 1187350 my $self = _new( shift, 'expect_no_root_dir' );
22              
23 4 50       603 mkdir('.dest') or die "Unable to create .dest directory\n";
24 4 50       605 open( my $watch, '>', '.dest/watch' ) or die "Unable to create ~/.dest/watch file\n";
25              
26 4         40 $self = _new();
27              
28 4 50       84 if ( -f 'dest.watch' ) {
29 4 50       158 open( my $watches, '<', 'dest.watch' ) or die "Unable to read dest.watch file\n";
30              
31 4         161 my @watches = map { chomp; $_ } <$watches>;
  4         20  
  4         16  
32              
33 4         9 my @errors;
34 4         10 for my $watch (@watches) {
35 4         12 try {
36 4         26 $self->add($watch);
37             }
38             catch ($e) {
39 0         0 push( @errors, $watch . ': ' . $e );
40             }
41             }
42              
43             warn
44             "Created new watch list based on dest.watch file:\n" .
45 4         213 join( "\n", map { ' ' . $_ } @watches ) . "\n" .
46             (
47             (@errors)
48 4 50       18 ? "With the following errors:\n" . join( '', map { ' ' . $_ } @errors )
  0         0  
49             : ''
50             );
51             }
52              
53 4         66 return 0;
54             }
55              
56             sub add {
57 8     8 1 5972 my $self = _new(shift);
58 8 50       26 die "No directory specified; usage: dest add [directory]\n" unless @_;
59              
60 8         61 my @watches = $self->_watch_list;
61             my @adds = map {
62 8         25 my $dir = $_;
  8         18  
63 8 100       122 die "Directory specified does not exist\n" unless ( -d $dir );
64              
65 7         36 my $rel_dir = $self->_rel2root($dir);
66 7 100       1686 die "Directory $dir already added\n" if ( grep { $rel_dir eq $_ } @watches );
  2         19  
67 6         21 $rel_dir;
68             } @_;
69              
70 6 50       33 open( my $watch, '>', $self->_rel2dir('.dest/watch') ) or die "Unable to write ~/.dest/watch file\n";
71 6         88 print $watch $_, "\n" for ( sort @adds, map { $self->_rel2root($_) } @watches );
  1         4  
72 6         188 mkpath("$self->{root_dir}/.dest/$_") for (@adds);
73              
74 6         3153 return 0;
75             }
76              
77             sub rm {
78 1     1 1 2939 my $self = _new(shift);
79 1 50       6 die "No directory specified; usage: dest rm [directory]\n" unless @_;
80              
81 1         5 my @watches = $self->_watch_list;
82             my @rms = map {
83 1         4 my $dir = $_;
  1         4  
84 1   50     5 $dir //= '';
85 1         5 $dir =~ s|/$||;
86              
87 1 50       3 die "Directory $dir not currently tracked\n" unless ( grep { $dir eq $_ } @watches );
  1         6  
88              
89 1         6 $self->_rel2root($dir);
90             } @_;
91              
92 1 50       300 open( my $watch_file, '>', $self->_rel2dir('.dest/watch') ) or die "Unable to write ~/.dest/watch file\n";
93 1         6 for my $watch_dir ( map { $self->_rel2root($_) } @watches ) {
  1         5  
94 1 50       253 if ( grep { $watch_dir eq $_ } @rms ) {
  1         24  
95 1         9 rmtree( $self->_rel2dir(".dest/$watch_dir") );
96             }
97             else {
98 0         0 print $watch_file $watch_dir, "\n";
99             }
100             }
101              
102 1         26 return 0;
103             }
104              
105             sub watches {
106 3     3 1 5725 my $self = _new(shift);
107              
108 3         10 my @watches = $self->_watch_list;
109 3 100       70 print join( "\n", @watches ), "\n" if @watches;
110              
111 3         19 return 0;
112             }
113              
114             sub putwatch {
115 1     1 1 730 my $self = _new(shift);
116 1         5 my ($file) = @_;
117 1 50       16 die "File specified does not exist\n" unless ( -f $file );
118              
119 1 50       45 open( my $new_watches, '<', $file ) or die "Unable to read specified file\n";
120              
121 1         34 my @new = map { chomp; $_ } <$new_watches>;
  2         8  
  2         7  
122 1         7 my @old = $self->_watch_list;
123              
124 1         4 for my $old (@old) {
125 1 50       1 next if ( grep { $_ eq $old } @new );
  2         6  
126 0         0 $self->rm($old);
127             }
128 1         2 for my $new (@new) {
129 2 100       3 next if ( grep { $_ eq $new } @old );
  2         5  
130 1         26 $self->add($new);
131             }
132              
133 1         13 return 0;
134             }
135              
136             sub writewatch {
137 1     1 1 718 my $self = _new(shift);
138              
139 1 50       6 copy( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') ) or die "$!\n";
140              
141 1         340 return 0;
142             }
143              
144             sub make {
145 2     2 1 2470 my ( $self, $path, $ext ) = @_;
146 2 50       5 die "No name specified; usage: dest make [path]\n" unless ($path);
147              
148 2         82 $path = strftime( $path, localtime );
149              
150 2 100       8 $ext = '.' . $ext if ( defined $ext );
151 2   100     8 $ext //= '';
152              
153 2         3 try {
154 2         278 mkpath($path);
155 2         8 for ( qw( deploy verify revert ) ) {
156 6 50       582 open( my $file, '>', "$path/$_$ext" ) or die;
157 6         228 print $file "\n";
158             }
159             }
160             catch ($e) {
161 0         0 die "Failed to fully make $path; check permissions or existing files\n";
162             }
163              
164 2         16 $self->expand($path);
165              
166 2         9 return 0;
167             }
168              
169             sub expand {
170 2     2 1 5 my ( $self, $path ) = @_;
171              
172 2         5 print join( ' ', map { <"$path/$_*"> } qw( deploy verify revert ) ), "\n";
  6         280  
173              
174 2         7 return 0;
175             }
176              
177             sub list {
178 2     2 1 7459 my $self = _new(shift);
179 2         6 my ($filter) = @_;
180              
181 2         11 my $tree = $self->_actions_tree($filter);
182 2         9 for my $path ( sort keys %$tree ) {
183 2 50       3 print $path, ( ( @{ $tree->{$path} } ) ? ' actions:' : ' has no actions' ), "\n";
  2         72  
184 2         5 print ' ', $_, "\n" for ( @{ $tree->{$path} } );
  2         66  
185             }
186              
187 2         16 return 0;
188             }
189              
190             sub prereqs {
191 1     1 1 1588 my $self = _new(shift);
192 1         3 my ($filter) = @_;
193              
194 1         16 for my $action ( @{ $self->_prereq_tree($filter)->{actions} } ) {
  1         5  
195 3 100       4 print $action->{action}, ( ( @{ $action->{prereqs} } ) ? ' prereqs:' : ' has no prereqs' ), "\n";
  3         60  
196 3         4 print ' ', $_, "\n" for ( @{ $action->{prereqs} } );
  3         12  
197             }
198              
199 1         9 return 0;
200             }
201              
202             sub status {
203 5     5 1 13667 my $self = _new(shift);
204              
205 5 50       16 if ( -f $self->_rel2dir('dest.watch') ) {
206 5         13 my $diff = Text::Diff::diff( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') );
207 5 50       1547 warn "Diff between current watch list and dest.watch file:\n" . $diff . "\n" if ($diff);
208             }
209              
210 5         9 for my $path ( @{ $self->_status_data->{paths} } ) {
  5         19  
211 5         190 print "$path->{state} - $path->{path}\n";
212              
213 5         12 for my $action ( @{ $path->{actions} } ) {
  5         14  
214 12 100       24 unless ( $action->{modified} ) {
215 10 100       161 print ' ' . ( ( $action->{deployed} ) ? '-' : '+' ) . ' ' . $action->{action} . "\n";
216             }
217             else {
218 2         30 print " $action->{action}\n";
219 2         20 print " M $action->{file}\n";
220             }
221             }
222             }
223              
224 5         52 return 0;
225             }
226              
227             sub diff {
228 2     2 1 1398 my $self = _new(shift);
229 2         4 my ($path) = @_;
230              
231 2 100       5 if ( not defined $path ) {
232 1         4 $self->diff($_) for ( $self->_watch_list );
233 1         7 return 0;
234             }
235              
236             File::DirCompare->compare( $self->_rel2dir( '.dest/' . $self->_rel2root($path) ), $path, sub {
237 3     3   2055 my ( $a, $b ) = @_;
238 3   100     13 $a ||= '';
239 3   100     8 $b ||= '';
240              
241 3 100 33     55 return if ( $a =~ /\/dest.wrap$/ or $b =~ /\/dest.wrap$/ or not -f $a or not -f $b );
      66        
      66        
242 1         6 print Text::Diff::diff( $a, $b );
243 1         570 return;
244 1         4 } );
245              
246 1         10 return 0;
247             }
248              
249             sub clean {
250 2     2 1 1471 my $self = _new(shift);
251              
252 2 100       7 if (@_) {
253 1         2 for (@_) {
254 1         5 my $dest = $self->_rel2dir(".dest/$_");
255 1         457 rmtree($dest);
256 1         7 rcopy( $self->_rel2dir($_), $dest );
257             }
258             }
259             else {
260 1         4 for ( map { $self->_rel2root($_) } $self->_watch_list ) {
  1         4  
261 1         152 my $dest = $self->_rel2dir(".dest/$_");
262 1         1346 rmtree($dest);
263 1         7 dircopy( $self->_rel2dir($_), $dest );
264             }
265             }
266              
267 2         6981 return 0;
268             }
269              
270             sub preinstall {
271 1     1 1 1150 my $self = _new(shift);
272              
273 1 50       6 if (@_) {
274 0         0 for (@_) {
275 0         0 my $dest = $self->_rel2dir(".dest/$_");
276 0         0 rmtree($dest);
277             }
278             }
279             else {
280 1         5 for ( map { $self->_rel2root($_) } $self->_watch_list ) {
  1         7  
281 1         269 my $dest = $self->_rel2dir(".dest/$_");
282 1         2139 rmtree($dest);
283 1         97 mkdir($dest);
284             }
285             }
286              
287 1         13 return 0;
288             }
289              
290             sub nuke {
291 1     1 1 719 my $self = _new(shift);
292 1         6 rmtree( $self->_rel2dir('.dest') );
293 1         7 return 0;
294             }
295              
296             sub deploy {
297 5     5 1 8633 my $self = _new(shift);
298 5         23 my ( $dry_run, $action ) = _dry_check(@_);
299 5 50       17 die "File to deploy required; usage: dest deploy file\n" unless ($action);
300              
301 5         18 my $redeploy = delete $self->{redeploy};
302 5         33 my $execute_stack = $self->_build_execute_stack( $action, 'deploy', undef, $redeploy );
303             die "Action already deployed\n" if (
304             not @$execute_stack or
305 5 50 66     53 not scalar( grep { $_->{action} eq $action and $_->{type} eq 'deploy' } @$execute_stack )
  8 100       37  
306             );
307              
308 4 100       8 unless ($dry_run) {
309 3         14 $self->_execute_action($_) for (@$execute_stack);
310             }
311             else {
312 1         4 _dry_run_report($execute_stack);
313             }
314              
315 4         197 return 0;
316             }
317              
318             sub redeploy {
319 1     1 1 1621 my $self = _new(shift);
320 1         3 $self->{redeploy} = 1;
321 1         8 return $self->deploy(@_);
322             }
323              
324             sub revdeploy {
325 1     1 1 3243 my $self = _new(shift);
326 1         5 my ($action) = @_;
327              
328 1         11 $self->revert($action);
329 1         13 return $self->deploy($action);
330             }
331              
332             sub verify {
333 1     1 1 1422 my $self = _new(shift);
334 1         4 my ($action) = @_;
335              
336 1         5 return $self->_execute_action( $self->_build_execute_stack( $action, 'verify' )->[0] );
337             }
338              
339             sub revert {
340 3     3 1 6738 my $self = _new(shift);
341 3         21 my ( $dry_run, $action ) = _dry_check(@_);
342 3 50       10 die "File to revert required; usage: dest revert file\n" unless ($action);
343              
344 3         26 my $execute_stack = $self->_build_execute_stack( $action, 'revert' );
345             die "Action not deployed\n" if (
346             not @$execute_stack or
347 3 100 33     29 not scalar( grep { $_->{action} eq $action and $_->{type} eq 'revert' } @$execute_stack )
  7 50       33  
348             );
349              
350 3 100       8 unless ($dry_run) {
351 2         12 $self->_execute_action($_) for (@$execute_stack);
352             }
353             else {
354 1         6 _dry_run_report($execute_stack);
355             }
356              
357 3         93 return 0;
358             }
359              
360             sub update {
361 3     3 1 8083 my $self = _new(shift);
362 3         21 my ( $dry_run, @incs ) = _dry_check(@_);
363              
364 3         12 my $seen_action = {};
365 3         10 my $execute_stack;
366              
367 3         9 for (
368             sort {
369             ( $b->{modified} || 0 ) <=> ( $a->{modified} || 0 ) ||
370             $a->{action} cmp $b->{action}
371 20 50 100     103 }
      100        
372 15         51 grep { not $_->{matches} }
373 3         20 values %{ $self->_status_data->{actions} }
374             ) {
375 2         16 push( @$execute_stack, @{ $self->_build_execute_stack( $_->{action}, 'revert' ) } ) if (
376             $_->{modified} or $_->{deployed}
377 14 100 100     72 );
378 14         20 push( @$execute_stack, @{ $self->_build_execute_stack( $_->{action}, 'deploy', $seen_action ) } );
  14         42  
379             }
380              
381             $execute_stack = [
382             grep {
383 3 100       19 my $execution = $_;
  8         6  
384 8         16 my $match = 0;
385              
386 8         9 for (@incs) {
387 8 50       16 if ( index( $execution->{file}, $_ ) > -1 ) {
388 8         8 $match = 1;
389 8         9 last;
390             }
391             }
392              
393 8         65 $match;
394             }
395             @$execute_stack
396             ] if (@incs);
397              
398 3 100       9 unless ($dry_run) {
399 2         16 $self->_execute_action($_) for (@$execute_stack);
400             }
401             else {
402 1         5 _dry_run_report($execute_stack);
403             }
404              
405 3         203 return 0;
406             }
407              
408             sub version {
409 2   50 2 1 1847 print 'dest version ', $__PACKAGE__::VERSION || 0, "\n";
410 2         4 return 0;
411             }
412              
413             sub _new {
414 51     51   135 my ( $self, $expect_no_root_dir ) = @_;
415              
416 51 100       207 if ( not ref $self ) {
417 42         111 $self = bless( {}, __PACKAGE__ );
418              
419 42         287 $self->{root_dir} = Path::Tiny->cwd;
420              
421 42         2231 while ( not $self->{root_dir}->child('.dest')->is_dir ) {
422 28 100       1633 if ( $self->{root_dir}->is_rootdir ) {
423 4         110 $self->{root_dir} = '';
424 4         14 last;
425             }
426 24         526 $self->{root_dir} = $self->{root_dir}->parent;
427 24         1042 $self->{dir_depth}++;
428             }
429              
430 42 100       2160 if ( $expect_no_root_dir ) {
431 5 100       37 die "Project already initialized\n" if $self->{root_dir};
432             }
433             else {
434 37 50       207 die "Project not initialized\n" unless $self->{root_dir};
435             }
436             }
437              
438 50         118 return $self;
439             }
440              
441             sub _dry_check {
442 11     11   30 my @clean = grep { $_ ne '-d' } @_;
  12         58  
443 11 100       56 return ( @clean != @_ ) ? 1 : 0, @clean;
444             }
445              
446             sub _rel2root {
447 131     131   538 my ( $self, $dir ) = @_;
448 131   100     682 my $path = path( $dir || '.' );
449              
450 131         6409 try {
451 131         639 $path = $path->realpath;
452             }
453             catch ($e) {
454 0         0 $path = $path->absolute;
455             }
456              
457 131         25583 return $path->relative( $self->{root_dir} )->stringify;
458             }
459              
460             sub _rel2dir {
461 450     450   20224 my ( $self, $dir ) = @_;
462 450   50     9178 return ( '../' x ( $self->{dir_depth} || 0 ) ) . ( $dir || '.' );
      50        
463             }
464              
465             sub _watch_list {
466 110     110   203 my ($self) = @_;
467 110 50       469 open( my $watch, '<', $self->_rel2dir('.dest/watch') ) or die "Unable to read ~/.dest/watch file\n";
468 110         1730 return sort map { chomp; $self->_rel2dir($_) } <$watch>;
  105         217  
  105         229  
469             }
470              
471             sub _actions_tree {
472 61     61   150 my ( $self, $filter ) = @_;
473              
474 61         65 my $tree;
475 61         189 for my $path ( $self->_watch_list ) {
476 61         82 my @actions;
477              
478             find( {
479             follow => 1,
480             no_chdir => 1,
481             wanted => sub {
482 1160 100   1160   33594 return unless ( m|/deploy(?:\.[^\/]+)?| );
483 261         894 ( my $action = $_ ) =~ s|/deploy(?:\.[^\/]+)?||;
484              
485 261 50 66     4140 push( @actions, $action ) if (
486             not defined $filter or
487             index( $action, $filter ) > -1
488             );
489             },
490 61         7206 }, $path );
491              
492 61         679 $tree->{$path} = [ sort @actions ];
493             }
494              
495 61         154 return $tree;
496             }
497              
498             sub _prereq_tree {
499 26     26   53 my ( $self, $filter ) = @_;
500 26         80 my $tree = $self->_actions_tree($filter);
501              
502 26         38 my @actions;
503 26         130 for my $path ( sort keys %$tree ) {
504 26         55 for my $action ( @{ $tree->{$path} } ) {
  26         55  
505 114         5298 my ($file) = <"$action/deploy*">;
506 114 50       2641 open( my $content, '<', $file ) or die "Unable to read $file\n";
507              
508             my $prereqs = [
509 59         170 map { $self->_rel2dir($_) }
510 59         101 grep { defined }
511 59 50       396 map { /dest\.prereq\b[\s:=-]+(.+?)\s*$/; $1 || undef }
  59         262  
512 114         1233 grep { /dest\.prereq/ } <$content>
  187         557  
513             ];
514              
515 114         349 $action = {
516             action => $action,
517             prereqs => $prereqs,
518             };
519              
520 114         981 push( @actions, $action );
521             }
522             }
523              
524 26         218 return { tree => $tree, actions => \@actions };
525             }
526              
527             sub _status_data {
528 33     33   68 my ($self) = @_;
529              
530 33         52 my $actions;
531 33         81 my $tree = $self->_actions_tree;
532 33         155 for my $path ( keys %$tree ) {
533 33         47 for my $action ( @{ $tree->{$path} } ) {
  33         72  
534 141         450 $actions->{$action} = {
535             action => $action,
536             deployed => 1,
537             matches => 1,
538             };
539             }
540             }
541              
542 33         54 my @paths;
543 33         102 for my $path ( $self->_watch_list ) {
544 33         60 my $printed_path = 0;
545 33         53 my $data;
546              
547 33         97 try {
548             File::DirCompare->compare(
549             $self->_rel2dir( '.dest/' . $self->_rel2root($path) ),
550             $path,
551             sub {
552 157     157   53259 my ( $a, $b ) = @_;
553 157 100 66     987 return if ( $a and $a =~ /\/dest.wrap$/ or $b and $b =~ /\/dest.wrap$/ );
      100        
      66        
554 128 100       288 $data->{state} = 'diff' unless ( $printed_path++ );
555              
556 128 100 33     293 if ( not $b ) {
    100          
    50          
557             push(
558 18         25 @{ $data->{actions} },
  18         72  
559             {
560             action => $self->_rel2dir( substr( $self->_rel2root($a), 6 ) ),
561             deployed => 1,
562             },
563             );
564             }
565             elsif ( not $a ) {
566             push(
567 93         102 @{ $data->{actions} },
  93         239  
568             {
569             action => $b,
570             deployed => 0,
571             },
572             );
573             }
574             elsif ( $a and $b ) {
575 17         174 ( my $action = $b ) =~ s,/(?:deploy|verify|revert)(?:\.[^\/]+)?$,,;
576             push(
577 17         39 @{ $data->{actions} },
  17         150  
578             {
579             action => $action,
580             modified => 1,
581             file => $b,
582             },
583             );
584             }
585              
586 128         284 return;
587             },
588             )
589 33         99 }
590             catch ($e) {
591 0 0       0 $data->{state} = '?' if ( $e =~ /Not a directory/ );
592             }
593             finally {
594 33 100       4888 $data->{state} = 'ok' unless $printed_path;
595             }
596              
597 33         96 $data->{path} = $path;
598 33         61 push( @paths, $data );
599              
600 33 100       94 if ( ref $data->{actions} eq 'ARRAY' ) {
601 31         41 $actions->{ $_->{action} } = $_ for ( @{ $data->{actions} } );
  31         281  
602             }
603             }
604              
605 33         262 return { paths => \@paths, actions => $actions };
606             }
607              
608             sub _build_execute_stack {
609 25     25   78 my ( $self, $name, $type, $seen_action, $redeploy ) = @_;
610              
611 25         63 my @actions_stack = ($name);
612 25         27 my $prereqs = { map { $_->{action} => $_->{prereqs} } @{ $self->_prereq_tree->{actions} } };
  111         259  
  25         93  
613 25         135 my $state = $self->_status_data->{actions};
614              
615 25 100       90 if ( $type eq 'revert' ) {
616 5         11 my $postreqs;
617 5         35 for my $action ( keys %$prereqs ) {
618 21         30 push( @{ $postreqs->{$_} }, $action ) for ( @{ $prereqs->{$action} } );
  21         39  
  8         45  
619             }
620 5         25 $prereqs = $postreqs;
621             }
622              
623 25         34 my ( @execute_stack, $wraps );
624 25   100     92 $seen_action //= {};
625              
626 25         76 while (@actions_stack) {
627 39         60 my $action = shift @actions_stack;
628 39 100       115 next if ( $seen_action->{$action}++ );
629 30 100       36 push( @actions_stack, @{ $prereqs->{$action} || [] } );
  30         111  
630              
631 30 100       87 my $location = ( $type ne 'revert' )
632             ? $action
633             : $self->_rel2dir( '.dest/' . $self->_rel2root($action) );
634              
635 30         1793 my $file = ( <"$location/$type*"> )[0];
636              
637 30 50       119 unless ( exists $wraps->{$action} ) {
638 30         70 $wraps->{$action} = undef;
639 30         81 my @nodes = split( '/', $self->_rel2root($file) );
640 30         4281 pop @nodes;
641 30 100 100     126 shift @nodes if ( defined $nodes[0] and $nodes[0] eq '.dest' );
642 30         56 while (@nodes) {
643 56         159 my $path = $self->_rel2dir( join( '/', @nodes ) . '/dest.wrap' );
644 56 100       791 if ( -f $path ) {
645 28         78 $wraps->{$action} = $path;
646 28         58 last;
647             }
648 28         66 pop @nodes;
649             }
650             }
651              
652             my $add = sub {
653 27 50   27   53 die "Action file does not exist for: $type $action\n" unless ($file);
654              
655 27 50       67 my $executable = ( $wraps->{$action} ) ? $wraps->{$action} : $file;
656 27 50       261 die 'Execute permission denied for: ' . $executable . "\n" unless ( -x $executable );
657              
658             unshift(
659             @execute_stack,
660             {
661             type => $type,
662             action => $action,
663             file => $file,
664 27         136 wrap => $wraps->{$action},
665             },
666             );
667              
668 27 100       118 if ( $type eq 'deploy' ) {
669 17         869 my $verify_file = ( <"$action/verify*"> )[0];
670 17 50       66 die "Action file does not exist for: verify $action\n" unless ($verify_file);
671              
672             splice(
673             @execute_stack,
674             1,
675             0,
676             {
677             type => 'verify',
678             action => $action,
679             file => $verify_file,
680 17         200 wrap => $wraps->{$action},
681             },
682             );
683             }
684 30         195 };
685              
686 30 100       85 if ( $type eq 'deploy' ) {
    100          
    50          
687 19 100 100     100 $add->() unless ( $state->{$action}{deployed} and not $redeploy );
688             }
689             elsif ( $type eq 'revert' ) {
690 10 100 100     77 $add->() if ( $state->{$action}{deployed} or $state->{$action}{modified} );
691             }
692             elsif ( $type eq 'verify' ) {
693 1         2 $add->();
694             }
695             }
696              
697 25         233 return \@execute_stack;
698             }
699              
700             sub _dry_run_report {
701 3     3   7 my ($execute_stack) = @_;
702 3 50       361 print '' . ( ( $_->{wrap} ) ? $_->{wrap} . ' ' : '' ) . $_->{file}, "\n" for (@$execute_stack);
703             }
704              
705             sub _execute_action {
706 27     27   99 my ( $self, $input ) = @_;
707 27         240 my ( $type, $wrap, $action, $file, $location ) = @$input{ qw( type wrap action file location ) };
708              
709 27         45 my ( $out, $err, $died );
710             my $run = sub {
711 27     27   60 try {
712             run(
713 27 50       58 [ ( grep { defined } $wrap ), $file, $type ],
  27         206  
714             \undef, \$out, \$err,
715             ) or $died = 1;
716             }
717             catch ($e) {
718 0         0 $err = $e;
719             }
720              
721 27 50       407477 if ($err) {
722 0         0 ( my $err_str = $err ) =~ s/\s*at\s+.*$//;
723 0         0 chomp($err_str);
724 0 0       0 if ($died) {
725 0         0 die "Failed to execute $file: $err_str\n";
726             }
727             else {
728 0         0 warn "Warnings from executed $file: $err_str\n";
729             }
730             }
731 27         213 };
732              
733 27 100       135 if ( $type eq 'verify' ) {
734 12         34 $run->();
735 12         83 chomp($out);
736 12 50       89 die "$err\n" if ($err);
737              
738 12 50       75 if ($out) {
739 12         523 print "ok - verify: $action\n";
740             }
741             else {
742 0         0 die "not ok - verify: $action\n";
743             }
744             }
745             else {
746 15         610 print "begin - $type: $action\n";
747 15         51 $run->();
748 15         739 print "ok - $type: $action\n";
749             }
750              
751 27         500 my $dest_copy = $self->_rel2dir( '.dest/' . $self->_rel2root($action) );
752 27 100       6491 rmtree($dest_copy) unless ( $type eq 'verify' );
753 27 100       382 dircopy( $action, $dest_copy ) if ( $type eq 'deploy' );
754              
755 27         31191 return 0;
756             }
757              
758             1;
759              
760             __END__