Slight changes (e.g. hostname list management) and documentation.
[deployable.git] / remote
bloba4b94d7511d091d0543b37bd89e1c971345b68ff
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use 5.006_002;
5 our $VERSION = '0.0.1';
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=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';
185 $filename = basename($filename) if $config{inspect};
187 print {*STDERR} "### Working on $tag '$filename'\n";
189 # Establish where to send the output
190 my @fhs;
191 push @fhs, output_fh($filename)
192 if ($tag eq 'file') || ($tag eq 'tarfile');
193 push @fhs, pipe_to_tar($filename) if ($tag eq 'directory');
194 push @fhs, pipe_to_tar($filename) if ($tag eq 'tarfile');
195 print {*STDERR} "skipping invalid file tag '$tag'\n"
196 unless @fhs;
198 # Skip empty lines and filter input stuff to output handles
199 skip_DATA_spaces();
200 while (defined get_DATA_line()) {
201 last unless length $_; # empty line marks end of chunk
202 if (@fhs) { # work only if really necessary
203 my $line = pack 'H*', $_;
204 print {$_} $line for @fhs;
206 } ## end while (defined get_DATA_line...
207 close $_ for @fhs;
209 if ($tag eq 'file') {
210 chmod oct($mode), $filename
211 or carp "chmod(0$mode, '$filename'): $OS_ERROR";
214 print {*STDERR} "\n";
215 } ## end while (defined get_DATA_line...
217 return;
218 } ## end sub save_files
220 sub output_fh {
221 my ($filename) = @_;
222 open my $fh, '>', $filename or croak "open('$filename'): $OS_ERROR";
223 binmode $fh;
224 return $fh;
225 } ## end sub output_fh
227 sub pipe_to_tar {
228 my ($changedir) = @_;
229 my @command = ('/bin/tar', 'xvzf', '-', '--no-same-owner', '--touch');
230 push @command, '-C', $changedir
231 if defined($changedir) && -d $changedir;
232 open my $fh, '|-', @command
233 or croak "open() for /bin/tar: $OS_ERROR";
234 binmode $fh;
235 return $fh;
236 } ## end sub pipe_to_tar
238 sub usage {
239 print {*STDOUT} <<"END_OF_USAGE" ;
240 $0 version $VERSION
242 More or less, this script is intended to be launched without parameters.
243 Anyway, you can also set the following options, which will override any
244 present configuration (except in "--show-options"):
246 * --usage | --man | --help
247 print these help lines and exit
249 * --version
250 print script version and exit
252 * --bundle | --all-exec | -X
253 treat all executables in the main deployment directory as scripts
254 to be executed
256 * --cleanup | --no-cleanup
257 perform / don't perform temporary directory cleanup after work done
259 * --dryrun | --dry-run
260 print final options and exit
262 * --inspect <dirname>
263 just extract all the stuff into <dirname> for inspection. Implies
264 --no-deploy, --no-tempdir, ignores --bundle (as a consequence of
265 --no-deploy), disables --cleanup and sets the working directory
266 to <dirname>
268 * --no-deploy
269 prevent execution of deploy scripts (they are executed by default)
271 * --no-tempdir
272 execute directly in workdir (see below), without creating the
273 temporary directory
275 * --show-options | -s
276 print configured options and exit
278 * --workdir | -w
279 working base directory (a temporary subdirectory will be created
280 there anyway)
282 END_OF_USAGE
283 exit 1;
284 } ## end sub usage
286 sub version {
287 print "$0 version $VERSION\n";
288 exit 1;
291 __END__
292 ########################################################################
293 # General configurations
295 # workdir = /tmp/our-deploy
296 workdir = 2f746d702f6f75722d6465706c6f79
298 # cleanup = 0
299 cleanup = 30
301 # deploy@ = test-deploy
302 deploy@ = 746573742d6465706c6f79
304 ########################################################################
305 # List of files
306 [files]
308 # file saves both mode and filename
309 # file = 0755 test-deploy
310 file = 3037353520746573742d6465706c6f79
311 23212f62696e2f626173680a0a504154483d272f62696e3a2f7573722f62696e
312 3a2f7362696e3a2f7573722f7362696e270a0a6563686f20276369616f206120
313 7475747469270a7077640a66696e640a
315 # directory testdir, extracted into:
316 # directory = .
317 directory = 2e
318 1f8b0800afdb5a470003ed934b0ac3300c44bdee297c83caa9649dc7b459180a
319 098edaf3d72e31649366e58412bdcd182cd06718e92779c474350d810c131575
320 4cb0d48a71c09d27c42ebfc1ddd87963a9e55095d72421596bc6e1d98bc4d5ba
321 adffba48d53f4166ffc734bc43a31ee51e1ef187ff7ef6df2141ae73c80cc6ee
322 72c493fbfff5fd72f414ca51d4fcdf63185af5d8ce3f2df2cf25ff445ef3bf07
323 c5778dbfa228caf9f800fd955b7b000e00000000000000000000000000000000
324 0000000000000000000000000000000000000000000000000000000000000000
325 0000000000000000000000000000000000000000000000000000000000000000
326 0000000000000000000000000000000000000000000000000000000000000000
327 0000000000000000000000000000000000000000000000000000000000000000
328 0000000000000000000000000000000000000000000000000000000000000000
329 0000000000000000000000000000000000000000000000000000000000000000
330 0000000000000000000000000000000000000000000000000000000000000000
331 0000000000000000000000000000000000000000000000000000000000000000
332 0000000000000000000000000000000000000000000000000000000000000000
333 0000000000000000000000000000000000000000000000000000000000000000
335 # tarfile will also be extracted into .
336 # tarfile = tarfile-contents.tar.gz
337 tarfile = 74617266696c652d636f6e74656e74732e7461722e677a
338 1f8b080062c85a470003edd17d0ac2300c87e11ea51718b65db39e67c884c2e8
339 648bf7d709039988821f307c9f7f7e9414da24da8e87dc77d57e28da159d76e6
340 f3dc451299d32771b7b930dea5d0488cbef6c67991108d952ffce5ce69d276b4
341 d61c87be53cd0fef3dab2f8d2cb911badefff5944bb52ebcf1c63c8f26c6d7f7
342 1f42a893b13f19e29fef1f000000000000000000000000c0769d0198ae093a00
343 280000
345 # here-directory = heredir/, extracted into:
346 # directory = .
347 directory = 2e
348 1f8b0800afdb5a470003d3d367a039300002735353106d686e6a804cc30083a1
349 81b99199a9898981b9218381a191898131838229ed9dc6c0505a5c9258a4a0c0
350 50909f935a529289531d2179984760f410017afa19a945a9699939a9ba9979ba
351 20764a661195ed00858799890909f16f6668048c7fba04e2088fff51300a46c1
352 c8050061a52c3500080000000000000000000000000000000000000000000000
353 0000000000000000000000000000000000000000000000000000000000000000
354 0000000000000000000000000000000000000000000000000000000000000000
355 0000000000000000000000000000000000000000000000000000000000000000
356 0000000000000000000000000000000000000000000000000000000000000000
357 0000000000000000000000000000000000000000000000000000000000000000
358 0000000000000000000000000000000000000000000000000000000000000000
359 0000000000000000000000000000000000000000000000000000000000000000
360 0000000000000000000000000000000000000000000000000000000000000000
361 0000000000000000000000000000000000000000000000000000000000000000
362 0000000000000000000000000000000000000000000000000000000000000000
363 0000000000000000000000000000000000000000000000000000000000000000