2 # *** NOTE *** LEAVE THIS MODULE LIST AS A PARAGRAPH
6 our $VERSION = '0.0.3';
7 use English
qw( -no_match_vars );
8 use Fatal
qw( close chdir opendir closedir );
9 use File
::Temp
qw( tempdir );
10 use File
::Path
qw( mkpath );
11 use File
::Spec
::Functions
qw( file_name_is_absolute catfile );
12 use File
::Basename
qw( dirname );
13 use POSIX
qw( strftime );
14 use Getopt
::Long
qw( :config gnu_getopt );
16 # *** NOTE *** LEAVE EMPTY LINE ABOVE
18 my %default_config = ( # default values
19 workdir
=> '/tmp/our-deploy',
40 workdir|work-directory|deploy-directory|w=s
44 usage
() if $config{usage
};
45 version
() if $config{version
};
50 while (read DATA
, my $buffer, 4096) {
51 print {*STDOUT
} $buffer;
54 } ## end if ($config{tar})
56 my $tar_package = eval {
59 } || 'Pseudo::Archive::Tar';
60 my $tar = $tar_package->new();
63 if ($config{filelist
}) {
65 print for $tar->list_files();
69 my %script_config = (%default_config, get_config
($tar));
72 print {*STDOUT
} Data
::Dumper
::Dumper
(\
%script_config);
76 # Merge configurations and go on
77 %config = (%script_config, %config);
79 if ($config{inspect
}) {
81 $config{'deploy'} = 0;
82 $config{'tempdir'} = 0;
83 $config{workdir
} = $config{inspect
};
84 } ## end if ($config{inspect})
86 if ($config{dryrun
}) {
88 print {*STDOUT
} Data
::Dumper
::Dumper
(\
%config);
92 # go into the working directory, creating any intermediate if needed
93 mkpath
($config{workdir
});
94 chdir($config{workdir
});
95 print {*STDERR
} "### Got into working directory '$config{workdir}'\n\n";
98 if ($config{'tempdir'}) { # Only if allowed
99 my $now = strftime
('%Y-%m-%d_%H-%M-%S', localtime);
101 tempdir
($now . 'X' x
10, DIR
=> '.', CLEANUP
=> $config{cleanup
});
105 "### Created and got into temporary directory '$tempdir'\n";
106 print {*STDERR
} "### (will clean it up later)\n" if $config{cleanup
};
107 print {*STDERR
} "\n";
108 } ## end if ($config{'tempdir'})
110 eval { # Not really needed, but you know...
111 $ENV{PATH
} = '/bin:/usr/bin:/sbin:/usr/sbin';
113 execute_deploy_programs
() if $config{'deploy'};
115 carp
$EVAL_ERROR if $EVAL_ERROR;
117 # Get back so that cleanup can successfully happen, if requested
118 chdir '..' if defined $tempdir;
120 sub execute_deploy_programs
{
121 my @deploy_programs = @
{$config{deploy
} || []};
123 if ($config{bundle
}) { # add all executable scripts in current directory
124 print {*STDERR
} "### Auto-deploying all executables in main dir\n\n";
125 my %flag_for = map { $_ => 1 } @deploy_programs;
127 for my $item (sort readdir $dh) {
128 next if $flag_for{$item};
129 next unless ((-f
$item) || (-l
$item)) && (-x
$item);
130 $flag_for{$item} = 1;
131 push @deploy_programs, $item;
132 } ## end for my $item (sort readdir...
134 } ## end if ($config{bundle})
137 for my $deploy (@deploy_programs) {
138 $deploy = catfile
('.', $deploy)
139 unless file_name_is_absolute
($deploy);
141 print {*STDERR
} "### Skipping '$deploy', not executable\n\n";
144 print {*STDERR
} "### Executing '$deploy'...\n";
145 system {$deploy} $deploy;
146 print {*STDERR
} "\n";
147 } ## end for my $deploy (@deploy_programs)
150 } ## end sub execute_deploy_programs
155 my ($file) = $tar->get_files('deployable/config.pl');
156 return unless $file && $file->has_content();
158 my $config = eval 'my ' . $file->get_content() or return;
159 return $config unless wantarray;
161 } ## end sub get_config
166 for my $file ($tar->get_files()) {
167 my ($area, $full_path) = split /\//mxs
, $file->full_path(), 2;
168 next unless $area eq 'root' || $area eq 'here';
171 $area eq 'here' ?
'.'
172 : $config{inspect
} ?
$area
174 my $real_path = join('/', $dirprefix, $full_path);
177 "### Extracting $full_path in '$area' => $real_path\n";
178 if ($file->is_dir()) {
182 mkpath
(dirname
$real_path);
183 write_file
($real_path, $file->get_content());
186 chmod $file->mode(), $real_path;
187 } ## end for my $file ($tar->get_files...
190 } ## end sub save_files
193 my $filename = shift;
194 open my $fh, '>', $filename or croak
"open('$filename'): $OS_ERROR";
199 } ## end sub write_file
202 print {*STDOUT
} <<"END_OF_USAGE" ;
205 More or less, this script is intended to be launched without parameters.
206 Anyway, you can also set the following options, which will override any
207 present configuration (except in "--show-options"):
209 * --usage | --man | --help
210 print these help lines and exit
213 print script version and exit
215 * --bundle | --all-exec | -X
216 treat all executables in the main deployment directory as scripts
219 * --cleanup | -c | --no-cleanup
220 perform / don't perform temporary directory cleanup after work done
222 * --deploy | --no-deploy
223 deploy scripts are executed by default (same as specifying '--deploy')
224 but you can prevent it.
226 * --dryrun | --dry-run
227 print final options and exit
229 * --filelist | --list | -l
230 print a list of files that are shipped in the deploy script
232 * --inspect | -i <dirname>
233 just extract all the stuff into <dirname> for inspection. Implies
234 --no-deploy, --no-tempdir, ignores --bundle (as a consequence of
235 --no-deploy), disables --cleanup and sets the working directory
238 * --show | --show-options | -s
239 print configured options and exit
242 print out the tar file that contains all the shipped files, useful
243 to redirect to file or pipe to the tar program
245 * --tempdir | --no-tempdir
246 by default a temporary directory is created (same as specifying
247 '--tempdir'), but you can execute directly in the workdir (see below)
250 * --workdir | --work-directory | --deploy-directory | -w
251 working base directory (a temporary subdirectory will be created
259 print "$0 version $VERSION\n";
263 package Pseudo::Archive::Tar;
266 use IPC::Open3 qw( open3 );
267 use Symbol
qw( gensym );
268 use English
qw( -no_match_vars );
273 my $self = bless {@_}, $package;
277 sub _interact_with_program
{
278 my ($self, @command) = @_;
280 my ($in, $out, $err);
282 my $pid = open3
($in, $out, $err, @command) or die "open3(): $OS_ERROR";
286 my $offset = 0; # for taking stuff from $self->{_programfile}
287 my $to_select = IO
::Select
->new($in);
288 my $from_select = IO
::Select
->new($out, $err);
290 while ($from_select->exists($out)) {
291 if ($to_select->can_write(0)) {
292 my $nwritten = syswrite $in, $self->{_programfile
}, 4096, $offset
293 or die "print(): $OS_ERROR";
295 $offset += $nwritten;
296 if ($offset == length($self->{_programfile
})) {
297 $to_select->remove($in);
300 } ## end if ($to_select->can_write...
301 for my $reader ($from_select->can_read(0)) {
302 my $nread = sysread $reader, my $buffer, 4096;
303 die "read(): $OS_ERROR" unless defined $nread;
305 $from_select->remove($reader);
308 die "got something from tar's STDERR: $buffer..."
310 push @result, $buffer;
311 } ## end for my $reader ($from_select...
312 } ## end while ($from_select->exists...
313 die 'could not pipe all data to tar' if $in;
315 return join '', @result;
316 } ## end sub _interact_with_program
318 sub _transform_mode
{
320 my ($sticky, $user, $group, $other) = unpack 'A A3 A3 A3', $mode;
323 for my $rwx ($user, $group, $other) {
324 my ($r, $w, $x) = split //, $rwx;
326 $v |= 1 if $x eq 'x';
327 $v |= 1 && $sticky |= $sticky_mask if lc($x) eq 's';
328 $v |= 2 if $w eq 'w';
329 $v |= 4 if $r eq 'r';
332 } ## end for my $rwx ($user, $group...
333 return oct(join '', $sticky, $user, $group, $other);
334 } ## end sub _transform_mode
339 my $filelist = $self->_interact_with_program(qw( tar tvf - ));
342 for my $line (split /\n/, $filelist) {
343 my ($mode, $ug, $size, $date1, $date2, $filename) =
344 split /\s+/, $line, 6;
346 Pseudo
::Archive
::Tar
::File
->new(
347 mode
=> _transform_mode
($mode),
348 is_dir
=> (substr($mode, 0, 1) eq 'd'),
353 } ## end for my $line (split /\n/...
355 $self->{_files
} = \
@files;
357 } ## end sub _refresh_list
360 my ($self, $handle) = @_;
363 $self->{_tarfile
} = <$handle>;
364 $self->_refresh_list();
370 my @files = map { $_->full_path() } @
{$self->{_files
}};
371 return @files if wantarray;
373 } ## end sub list_files
377 return @
{$self->{_files
}} unless @_;
379 return grep { $target eq $_->full_path() } @
{$self->{_files
}};
380 } ## end sub get_files
382 package Pseudo
::Archive
::Tar
::File
;
383 use Scalar
::Util
qw( weaken );
387 my $self = bless {@_}, $package;
388 weaken
$self->{_parent
};
392 sub full_path
{ return $_[0]->{name
}; }
393 sub mode
{ return $_[0]->{mode
}; }
394 sub has_content
{ return $_[0]->{size
}; }
395 sub is_dir
{ return $_[0]->{is_dir
}; }
399 return $self->{_parent
}
400 ->_interact_with_program(qw( tar xOf - ), $self->{name
});