Added for automatic generation of remote-at, which include Archive::Tar inside remote
[deployable.git] / remote
blobc947d1849dd330474e0bbe21561e5977f5420f77
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use 5.006_002;
5 our $VERSION = '0.0.2';
6 use English qw( -no_match_vars );
7 use Fatal qw( close chdir opendir closedir );
8 use File::Temp qw( tempdir );
9 use File::Path qw( mkpath );
10 use File::Spec::Functions qw( file_name_is_absolute splitpath catfile );
11 use File::Basename qw( basename );
12 use POSIX qw( strftime );
13 use Getopt::Long qw( :config gnu_getopt );
14 use Cwd qw( getcwd );
16 my %default_config = ( # default values
17 workdir => '/tmp/our-deploy',
18 cleanup => 1,
20 my %config;
21 GetOptions(
22 \%config, 'usage|help|man',
23 'version', 'cleanup|c!',
24 'dryrun|dry-run', 'no-deploy!',
25 'show|show-options|s!', 'workdir|work-directory|deploy-directory|w=s',
26 'no-tempdir!', 'bundle|all-exec|X!',
27 'inspect|i=s', 'filelist',
30 usage() if $config{usage};
31 version() if $config{version};
33 if ($config{filelist}) {
34 while (<DATA>) { last if $_ eq "[files]\n" }
35 my $printed;
36 while (<DATA>) {
37 if (/\A \s* \#/mxs) {
38 print "\n" unless $printed;
39 $printed = print;
41 else {
42 $printed = 0;
45 exit 1;
48 my %script_config = (%default_config, get_config());
49 if ($config{show}) {
50 require Data::Dumper;
51 print {*STDOUT} Data::Dumper::Dumper(\%script_config);
52 exit 1;
55 # Merge configurations and go on
56 %config = (%script_config, %config);
58 if ($config{inspect}) {
59 $config{cleanup} = 0;
60 $config{'no-deploy'} = 1;
61 $config{'no-tempdir'} = 1;
62 $config{workdir} = $config{inspect};
65 if ($config{dryrun}) {
66 require Data::Dumper;
67 print {*STDOUT} Data::Dumper::Dumper(\%config);
68 exit 1;
71 # go into the working directory, creating any intermediate if needed
72 mkpath($config{workdir});
73 chdir($config{workdir});
74 print {*STDERR} "### Got into working directory '$config{workdir}'\n\n";
76 my $tempdir;
77 if (!$config{'no-tempdir'}) { # Only if not prohibited
78 my $now = strftime('%Y-%m-%d_%H-%M-%S', localtime);
79 $tempdir =
80 tempdir($now . 'X' x 10, DIR => '.', CLEANUP => $config{cleanup});
82 chdir $tempdir;
83 print {*STDERR}
84 "### Created and got into temporary directory '$tempdir'\n";
85 print {*STDERR} "### (will clean it up later)\n" if $config{cleanup};
86 print {*STDERR} "\n";
87 } ## end if (!$config{'no-tempdir'...
89 eval { # Not really needed, but you know...
90 $ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin';
91 save_files();
92 execute_deploy_programs() unless $config{'no-deploy'};
94 carp $EVAL_ERROR if $EVAL_ERROR;
96 # Get back so that cleanup can successfully happen, if requested
97 chdir '..' if defined $tempdir;
99 sub execute_deploy_programs {
100 my @deploy_programs = @{$config{deploy} || []};
102 if ($config{bundle}) { # add all executable scripts in current directory
103 print {*STDERR} "### Auto-deploying all executables in main dir\n\n";
104 my %flag_for = map { $_ => 1 } @deploy_programs;
105 opendir my $dh, '.';
106 for my $item (sort readdir $dh) {
107 next if $flag_for{$item};
108 next unless ((-f $item) || (-l $item)) && (-x $item);
109 $flag_for{$item} = 1;
110 push @deploy_programs, $item;
111 } ## end while (my $item = readdir...
112 closedir $dh;
113 } ## end if ($config{bundle})
115 DEPLOY:
116 for my $deploy (@deploy_programs) {
117 $deploy = catfile('.', $deploy)
118 unless file_name_is_absolute($deploy);
119 if (!-x $deploy) {
120 print {*STDERR} "### Skipping '$deploy', not executable\n\n";
121 next DEPLOY;
123 print {*STDERR} "### Executing '$deploy'...\n";
124 system {$deploy} $deploy;
125 print {*STDERR} "\n";
126 } ## end for my $deploy (@deploy_programs)
128 return;
129 } ## end sub execute_deploy_programs
132 my ($last_line, $getlast);
133 sub unget_DATA_line { return $getlast = 1 }
135 sub get_DATA_line {
136 if (!$getlast) {
137 if (defined($last_line = <DATA>)) {
138 $last_line =~ s/\#.*//mxs;
139 $last_line =~ s/\s+//gmxs;
141 } ## end if (!$getlast)
142 $getlast = 0; # reset the flag anyway
143 return $_ = $last_line;
144 } ## end sub get_DATA_line
147 sub skip_DATA_spaces {
148 local $_ = undef;
149 while (defined get_DATA_line()) { last if /\S/mxs }
150 return unget_DATA_line();
153 sub get_config {
154 my %config;
156 while (defined get_DATA_line()) {
157 next unless length $_;
158 last if $_ eq '[files]';
160 my ($name, $value) = split /=/mxs, $_;
161 $value = pack 'H*', $value;
163 if (substr($name, -1) eq '@') {
164 substr $name, -1, 1, '';
165 push @{$config{$name}}, $value;
167 else {
168 $config{$name} = $value;
170 } ## end while (defined get_DATA_line...
172 return %config if wantarray;
173 return \%config;
174 } ## end sub get_config
176 sub save_files {
177 while (defined get_DATA_line()) {
178 next unless length $_;
180 my ($tag, $filename) = split /\s* = \s*/mxs, $_;
182 $filename = pack 'H*', $filename;
183 my $mode;
184 ($mode, $filename) = split /\s+/mxs, $filename, 2 if $tag eq 'file';
186 if ($config{inspect}) { # If inspecting...
187 $filename = basename($filename); # stick here
188 $filename = '.' if $filename eq '/'; # I mean, really!
191 print {*STDERR} "### Working on $tag '$filename'\n";
193 # Establish where to send the output. Note that tarred files
194 # are both saved locally for (possible) later inspection, and
195 # sent to pipe_to_tar for their expansion on-the-fly
196 my @fhs;
197 push @fhs, output_fh($filename)
198 if ($tag eq 'file') || ($tag eq 'tarfile');
199 push @fhs, pipe_to_tar($filename) if ($tag eq 'directory');
200 push @fhs, pipe_to_tar($filename) if ($tag eq 'tarfile');
201 print {*STDERR} "skipping invalid file tag '$tag'\n"
202 unless @fhs;
204 # Skip empty lines and filter input stuff to output handles
205 skip_DATA_spaces();
206 while (defined get_DATA_line()) {
207 last unless length $_; # empty line marks end of chunk
208 if (@fhs) { # work only if really necessary
209 my $line = pack 'H*', $_;
210 print {$_} $line for @fhs;
212 } ## end while (defined get_DATA_line...
213 close $_ for @fhs;
215 if ($tag eq 'file') {
216 chmod oct($mode), $filename
217 or carp "chmod(0$mode, '$filename'): $OS_ERROR";
220 print {*STDERR} "\n";
221 } ## end while (defined get_DATA_line...
223 return;
224 } ## end sub save_files
226 sub output_fh {
227 my ($filename) = @_;
228 open my $fh, '>', $filename or croak "open('$filename'): $OS_ERROR";
229 binmode $fh;
230 return $fh;
231 } ## end sub output_fh
233 sub pipe_to_tar {
234 my ($changedir) = @_;
235 my @command = ('/bin/tar', 'xvzf', '-', '--no-same-owner', '--touch');
236 push @command, '-C', $changedir
237 if defined($changedir) && -d $changedir;
238 open my $fh, '|-', @command
239 or croak "open() for /bin/tar: $OS_ERROR";
240 binmode $fh;
241 return $fh;
242 } ## end sub pipe_to_tar
244 sub usage {
245 print {*STDOUT} <<"END_OF_USAGE" ;
246 $0 version $VERSION
248 More or less, this script is intended to be launched without parameters.
249 Anyway, you can also set the following options, which will override any
250 present configuration (except in "--show-options"):
252 * --usage | --man | --help
253 print these help lines and exit
255 * --version
256 print script version and exit
258 * --bundle | --all-exec | -X
259 treat all executables in the main deployment directory as scripts
260 to be executed
262 * --cleanup | --no-cleanup
263 perform / don't perform temporary directory cleanup after work done
265 * --dryrun | --dry-run
266 print final options and exit
268 * --inspect <dirname>
269 just extract all the stuff into <dirname> for inspection. Implies
270 --no-deploy, --no-tempdir, ignores --bundle (as a consequence of
271 --no-deploy), disables --cleanup and sets the working directory
272 to <dirname>
274 * --no-deploy
275 prevent execution of deploy scripts (they are executed by default)
277 * --no-tempdir
278 execute directly in workdir (see below), without creating the
279 temporary directory
281 * --show-options | -s
282 print configured options and exit
284 * --workdir | -w
285 working base directory (a temporary subdirectory will be created
286 there anyway)
288 END_OF_USAGE
289 exit 1;
290 } ## end sub usage
292 sub version {
293 print "$0 version $VERSION\n";
294 exit 1;
297 __END__
298 ########################################################################
299 # General configurations
301 # workdir = /tmp/our-deploy
302 workdir = 2f746d702f6f75722d6465706c6f79
304 # cleanup = 0
305 cleanup = 30
307 # deploy@ = test-deploy
308 deploy@ = 746573742d6465706c6f79
310 ########################################################################
311 # List of files
312 [files]
314 # file saves both mode and filename
315 # file = 0755 test-deploy
316 file = 3037353520746573742d6465706c6f79
317 23212f62696e2f626173680a0a504154483d272f62696e3a2f7573722f62696e
318 3a2f7362696e3a2f7573722f7362696e270a0a6563686f20276369616f206120
319 7475747469270a7077640a66696e640a
321 # directory testdir, extracted into:
322 # directory = .
323 directory = 2e
324 1f8b0800afdb5a470003ed934b0ac3300c44bdee297c83caa9649dc7b459180a
325 098edaf3d72e31649366e58412bdcd182cd06718e92779c474350d810c131575
326 4cb0d48a71c09d27c42ebfc1ddd87963a9e55095d72421596bc6e1d98bc4d5ba
327 adffba48d53f4166ffc734bc43a31ee51e1ef187ff7ef6df2141ae73c80cc6ee
328 72c493fbfff5fd72f414ca51d4fcdf63185af5d8ce3f2df2cf25ff445ef3bf07
329 c5778dbfa228caf9f800fd955b7b000e00000000000000000000000000000000
330 0000000000000000000000000000000000000000000000000000000000000000
331 0000000000000000000000000000000000000000000000000000000000000000
332 0000000000000000000000000000000000000000000000000000000000000000
333 0000000000000000000000000000000000000000000000000000000000000000
334 0000000000000000000000000000000000000000000000000000000000000000
335 0000000000000000000000000000000000000000000000000000000000000000
336 0000000000000000000000000000000000000000000000000000000000000000
337 0000000000000000000000000000000000000000000000000000000000000000
338 0000000000000000000000000000000000000000000000000000000000000000
339 0000000000000000000000000000000000000000000000000000000000000000
341 # tarfile will also be extracted into .
342 # tarfile = tarfile-contents.tar.gz
343 tarfile = 74617266696c652d636f6e74656e74732e7461722e677a
344 1f8b080062c85a470003edd17d0ac2300c87e11ea51718b65db39e67c884c2e8
345 648bf7d709039988821f307c9f7f7e9414da24da8e87dc77d57e28da159d76e6
346 f3dc451299d32771b7b930dea5d0488cbef6c67991108d952ffce5ce69d276b4
347 d61c87be53cd0fef3dab2f8d2cb911badefff5944bb52ebcf1c63c8f26c6d7f7
348 1f42a893b13f19e29fef1f000000000000000000000000c0769d0198ae093a00
349 280000
351 # here-directory = heredir/, extracted into:
352 # directory = .
353 directory = 2e
354 1f8b0800afdb5a470003d3d367a039300002735353106d686e6a804cc30083a1
355 81b99199a9898981b9218381a191898131838229ed9dc6c0505a5c9258a4a0c0
356 50909f935a529289531d2179984760f410017afa19a945a9699939a9ba9979ba
357 20764a661195ed00858799890909f16f6668048c7fba04e2088fff51300a46c1
358 c8050061a52c3500080000000000000000000000000000000000000000000000
359 0000000000000000000000000000000000000000000000000000000000000000
360 0000000000000000000000000000000000000000000000000000000000000000
361 0000000000000000000000000000000000000000000000000000000000000000
362 0000000000000000000000000000000000000000000000000000000000000000
363 0000000000000000000000000000000000000000000000000000000000000000
364 0000000000000000000000000000000000000000000000000000000000000000
365 0000000000000000000000000000000000000000000000000000000000000000
366 0000000000000000000000000000000000000000000000000000000000000000
367 0000000000000000000000000000000000000000000000000000000000000000
368 0000000000000000000000000000000000000000000000000000000000000000
369 0000000000000000000000000000000000000000000000000000000000000000