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