Added mcarry, modified Makefile accordingly.
[deployable.git] / deployable
blob266b9f8cca0229d8c00bcb0d5c925f8ccb572044
1 #!/usr/bin/env perl
2 use strict;
3 use warnings;
4 use Carp;
5 use version; our $VERSION = qv('0.0.1');
6 use Fatal qw( close );
7 use Pod::Usage qw( pod2usage );
8 use Getopt::Long qw( :config gnu_getopt );
9 use English qw( -no_match_vars );
10 use File::Basename qw( basename dirname );
11 use File::Spec::Functions qw( file_name_is_absolute catfile );
12 use POSIX qw( strftime );
13 use Cwd qw( cwd realpath );
14 use Archive::Tar;
15 use File::Find::Rule;
16 use Data::Dumper;
18 my %config = (
19 output => '-',
20 remote => catfile(dirname(realpath(__FILE__)), 'remote'),
21 tarfile => [],
22 heredir => [],
23 rootdir => [],
24 root => [],
25 deploy => [],
27 GetOptions(
28 \%config, 'usage',
29 'help', 'man',
30 'version', 'output|o=s',
31 'deploy|exec|d=s@', 'workdir|work-directory|deploy-directory|w=s',
32 'cleanup|c!',
33 'heredir|H=s@', 'bundle|all-exec|X!',
34 'rootdir|R=s@', 'root|r=s@',
35 'include-archive-tar|T!',
37 pod2usage(message => "$0 $VERSION", -verbose => 99, -sections => '')
38 if $config{version};
39 pod2usage(-verbose => 99, -sections => 'USAGE') if $config{usage};
40 pod2usage(-verbose => 99, -sections => 'USAGE|EXAMPLES|OPTIONS')
41 if $config{help};
42 pod2usage(-verbose => 2) if $config{man};
44 pod2usage(
45 message => 'working directory must be an absolute path',
46 -verbose => 99,
47 -sections => ''
49 if exists $config{workdir} && !file_name_is_absolute($config{workdir});
51 $config{remote} = catfile(dirname(realpath(__FILE__)), 'remote-at')
52 if $config{'include-archive-tar'};
55 # Where all the data will be kept
56 my $tar = Archive::Tar->new();
58 { # Add a configuration file
59 my %general_configuration;
60 for my $name (qw( workdir cleanup bundle deploy )) {
61 $general_configuration{$name} = $config{$name}
62 if exists $config{$name};
64 $tar->add_data('deployable/config.pl', Dumper \%general_configuration);
67 # Process files and directories. All these will be reported in the
68 # extraction directory, i.e. basename() will be applied to them. For
69 # directories, they will be re-created
70 for my $file (@ARGV) {
71 croak "'$file' not readable" unless -r $file;
72 if (-d $file) {
73 print {*STDERR} "processing directory '$file'\n";
74 save_directory($tar, '.', '.', 'here');
75 } ## end if (-d $file)
76 else {
77 print {*STDERR} "processing file '$file'\n";
78 save_file($tar, $file, 'here');
79 } ## end else [ if (-d $file)
80 } ## end for my $file (@ARGV)
82 # heredir-s are directories that are extracted directly into the ex dir
83 for my $heredir (@{$config{heredir}}) {
84 croak "'$heredir' not readable" unless -r $heredir;
85 print {*STDERR} "processing here-directory '$heredir'\n";
86 save_directory($tar, $heredir, '.', 'here');
87 } ## end for my $heredir (@{$config...
89 # rootdir-s are directories that will go under root
90 for my $rootdir (@{$config{rootdir}}) {
91 croak "'$rootdir' not readable" unless -r $rootdir;
92 print {*STDERR} "processing root-directory '$rootdir'\n";
93 save_directory($tar, '.', $rootdir, 'root');
96 # root-s are directories whose contents go under root
97 for my $root (@{$config{root}}) {
98 croak "'$root' not readable" unless -r $root;
99 print {*STDERR} "processing root-directory '$root'\n";
100 save_directory($tar, $root, '.', 'root');
103 # Establish output channel
104 my $out_fh = \*STDOUT;
105 if ($config{output} ne '-') {
106 open my $fh, '>', $config{output} ## no critic
107 or croak "open('$config{output}'): $OS_ERROR";
108 $out_fh = $fh;
110 binmode $out_fh;
112 # Emit script code to be executed remotely. It is guaranteed to end
113 # with __END__, so that all what comes next is data
114 print {$out_fh} get_remote_script();
116 # Save tar file, it will close the filehandle as well
117 $tar->write($out_fh);
119 # Set as executable
120 if ($config{output} ne '-') {
121 chmod oct(755), $config{output}
122 or carp "chmod(0755, '$config{output}'): $OS_ERROR";
126 my $cwd;
127 sub save_directory {
128 my ($tar, $localstart, $localdir, $remotestart) = @_;
130 $cwd ||= cwd();
131 chdir $localstart;
133 save_file($tar, $_, $remotestart) for File::Find::Rule->file()->in($localdir);
135 chdir $cwd;
137 return;
138 } ## end sub save_directory
141 sub save_file {
142 my ($tar, $filename, $remotestart) = @_;
144 my ($ftar) = $tar->add_files($filename);
145 $ftar->rename("$remotestart/$filename");
147 return;
148 } ## end sub save_file
150 sub get_remote_script {
151 open my $fh, '<', $config{remote}
152 or croak "open('$config{remote}'): $OS_ERROR";
153 my @lines;
154 while (<$fh>) {
155 last if /\A __END__ \s*\z/mxs;
156 push @lines, $_;
158 close $fh;
159 return join '', @lines, "__END__\n";
160 } ## end sub get_remote_script
162 __END__
164 =head1 NAME
166 deployable - create a deploy script for some files/scripts
169 =head1 VERSION
171 See version at beginning of script, variable $VERSION, or call
173 shell$ deployable --version
175 =head1 USAGE
177 deployable [--usage] [--help] [--man] [--version]
179 deployable [--bundle|--all-exec|-X] [--cleanup|-c]
180 [--deploy|--exec|d <program>] [--heredir|-H <dirname>]
181 [--output|-o <filename>] [--root|-r <dirname>]
182 [--rootdir|-R <dirname>] [--tarfile|-T <filename>]
183 [--workdir|-w <path>] [ files or directories... ]
186 =head1 EXAMPLES
188 shell$ deployable
190 # Create script.pl embedding distro.tar.gz. When executed, this
191 # tar file will be extracted, and script.sh will be executed.
192 shell$ deployable -o script.pl --exec script.sh -T distro.tar.gz
194 # Use a directory's contents as elements for the target root
195 shell$ ls -1 /path/to/target/root
200 # The above will be deployed as /etc, /opt, /usr and /var
201 shell$ deployable -o dep.pl --root /path/to/target/root
203 # Include directory /path/to/etc for inclusion and extraction
204 # directly as /etc
205 shell$ deployable -o dep.pl --rootdir /path/to/etc
207 =head1 DESCRIPTION
209 This is a meta-script to create deploy scripts. The latter ones are
210 suitable to be distributed in order to deploy something.
212 You basically have to provide two things: files to install and programs
213 to be executed. Files can be put directly into the deployed script, or
214 can be included in gzipped tar archives.
216 When called, this script creates a deploy script for you. This script
217 includes all the specified files, and when executed it will extract
218 those files and execute the given programs. In this way, you can ship
219 both files and logic needed to correctly install those files, but this
220 is of course of of scope.
222 All files and archives will be extracted under a configured path
223 (see L<--workdir> below), which we'll call I<workdir> from now on. Under
224 the I<workdir> a temporary directory will be created, and the files
225 will be put in the temporary directory. You can specify if you want to
226 clean up this temporary directory or keep it, at your choice. (You're able
227 to both set a default for this cleanup when invoking deployable, or when
228 invoking the deploy script itself). The temporary directory will be
229 called I<tmpdir> in the following.
231 There are several ways to embed files to be shipped:
233 =over
235 =item *
237 specify the file name directly on the command line. A file given in this
238 way will always be extracted into the I<tmpdir>, whatever its initial path
239 was.
241 =item *
243 specify the name of a directory on the command line. In this case,
244 C<tar> will be used to archive the directory, with the usual option to
245 turn absolute paths into relative ones; this means that directories will
246 be re-created under I<tmpdir> when extraction is performed.
248 =item *
250 give the name of an already available gzipped C<tar> archive, using the
251 C<--tarfile|-T> option. This lets you have the maximum flexibility.
253 =item *
255 give the name of a directory to be used as a "here directory", using
256 the C<--heredir|-H> option. This is much the same as giving the directory
257 name (see above), but in this case C<tar> will be told to change into the
258 directory first, and archive '.'. This means that the contents of the
259 "here-directory" will be extracted directly into I<tmpdir>.
261 =back
263 =head2 Extended Example
265 Suppose you have a few server which have the same configuration, apart
266 from some specific stuff (e.g. the hostname, the IP addresses, etc.).
267 You'd like to perform changes to all with the minimum work possible...
268 so you know you should script something.
270 For example, suppose you want to update a few files in /etc, setting these
271 files equal for all hosts. You would typically do the following:
273 # In your computer
274 shell$ mkdir -p /tmp/newfiles/etc
275 shell$ cd /tmp/newfiles/etc
276 # Craft the new files
277 shell$ cd ..
278 shell$ tar cvzf newetc.tar.gz etc
280 # Now, for each server:
281 shell$ scp newetc.tar.gz $server:/tmp
282 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
285 So far, so good. But what if you need to kick in a little more logic?
286 For example, if you update some configuration files, you'll most likey
287 want to restart some services. So you could do the following:
289 shell$ mkdir -p /tmp/newfiles/tmp
290 shell$ cd /tmp/newfiles/tmp
291 # craft a shell script to be executed remotely and set the exec bit
292 # Suppose it's called deploy.sh
293 shell$ cd ..
294 shell$ tar cvzf newetc.tar.gz etc tmp
296 # Now, for each server:
297 shell$ scp newetc.tar.gz $server:/tmp
298 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
299 shell$ ssh $server /tmp/deploy.sh
301 And what if you want to install files depending on the particular machine?
302 Or you have a bundle of stuff to deploy and a bunch of scripts to execute?
303 You can use deployable. In this case, you can do the following:
305 shell$ mkdir -p /tmp/newfiles/etc
306 shell$ cd /tmp/newfiles/etc
307 # Craft the new files
308 shell$ cd ..
309 # craft a shell script to be executed remotely and set the exec bit
310 # Suppose it's called deploy.sh
311 shell$ deployable -o deploy.pl etc deploy.sh --exec deploy.sh
313 # Now, for each server
314 shell$ scp deploy.pl $server:/tmp
315 shell$ ssh $server /tmp/deploy.pl
317 And you're done. This can be particularly useful if you have another
318 layer of deployment, e.g. if you have to run a script to decide which
319 of a group of archives should be deployed. For example, you could craft
320 a different new "etc" for each server (which is particularly true if
321 network configurations are in the package), and produce a simple script
322 to choose which file to use based on the MAC address of the machine. In
323 this case you could have:
325 =over
327 =item newetc.*.tar.gz
329 a bunch of tar files with the configurations for each different server
331 =item newetc.list
333 a list file with the association between the MAC addresses and the
334 real tar file to deploy from the bunch in the previous bullet
336 =item deploy-the-right-stuff.sh
338 a script to get the real MAC address of the machine, select the right
339 tar file and do the deployment.
341 =back
343 So, you can do the following:
345 shell$ deployable -o deploy.pl newetc.*.tar.gz newetc.list \
346 deploy-the-right-stuff.sh --exec deploy-the-right-stuff.sh
348 # Now, for each server:
349 shell$ scp deploy.pl $server:/tmp
350 shell$ ssh $server /tmp/deploy.pl
352 So, once you have the deploy script on the target machine all you need
353 to do is to execute it. This can come handy when you cannot access the
354 machines from the network, but you have to go there physically: you
355 can prepare all in advance, and just call the deploy script.
358 =head1 OPTIONS
360 Meta-options:
362 =over
364 =item B<--help>
366 print a somewhat more verbose help, showing usage, this description of
367 the options and some examples from the synopsis.
369 =item B<--man>
371 print out the full documentation for the script.
373 =item B<--usage>
375 print a concise usage line and exit.
377 =item B<--version>
379 print the version of the script.
381 =back
383 Real-world options:
385 =over
387 =item B<< --bundle | --all-exec | -X >>
389 Set bundle flag in the produced script. If the bundle flag is set, the
390 I<deploy script> will treat all executables in the main deployment
391 directory as scripts to be executed.
393 By default the flag is not set.
395 =item B<< --cleanup | -c >>
397 Set cleanup flag in the produced script. If the cleanup flag is set, the
398 I<deploy script> will clean up after having performed all operations.
400 You can set this flag to C<0> by using C<--no-cleanup>.
402 =item B<< --deploy | --exec | -d <filename> >>
404 Set the name of a program to execute after extraction. You can provide
405 multiple program names, they will be executed in the same order.
407 =item B<< --heredir | -H <path> >>
409 Set the name of a "here directory" (see L<DESCRIPTION>). You can use this
410 option multiple times to provide multiple directories.
412 =item B<< --output | -o <filename> >>
414 Set the output file name. By default the I<deploy script> will be given
415 out on the standard output; if you provide a filename (different from
416 C<->, of course!) the script will be saved there and the permissions will
417 be set to 0755.
419 =item B<< --root | -r <dirname> >>
421 Include C<dirname> contents for deployment under root directory. The
422 actual production procedure is: hop into C<dirname> and grab a tarball
423 of C<.>. During deployment, hop into C</> and extract the tarball.
425 This is useful if you're already building up the absolute deployment
426 layout under a given directory: just treat that directory as if it were
427 the root of the target system.
429 =item B<< --rootdir | -R <dirname >>
431 Include C<dirname> as a directory that will be extracted under root
432 directory. The actual production procedure is: grab a tarball of
433 C<dirname>. During deployment, hop into C</> and extract the tarball.
435 This is useful if you have a directory (or a group of directories) that
436 you want to deploy directly under the root.
438 =item B<< --tarfile | -T <filename >>
440 Set the name of a B<tar> file, see L<DESCRIPTION>. You can use this option
441 multiple times to provide multiple B<tar> archives.
443 =item B<< --workdir | --deploy-directory | -w <path> >>
445 Set the working directory for the deploy.
447 =back
449 =head1 THE DEPLOY SCRIPT
451 The net result of calling this script is to produce another script,
452 that we call the I<deploy script>. This script is made of two parts: the
453 code, which is fixed, and the configurations/files, which is what is
454 actually produced. The latter part is put after the C<__END__> marker,
455 as usual.
457 Stuff in the configuration part is always hexified in order to prevent
458 strange tricks or errors. Comments will help you devise what's inside the
459 configurations themselves.
461 The I<deploy script> has options itself, even if they are quite minimal.
462 In particular, it supports the same options C<--workdir|-w> and
463 C<--cleanup> described above, allowing the final user to override the
464 configured values. By default, the I<workdir> is set to C</tmp/our-deploy>
465 and the script will clean up after itself.
467 The following options are supported in the I<deploy script>:
469 =over
471 =item B<--usage | --man | --help>
473 print a minimal help and exit
475 =item B<--version>
477 print script version and exit
479 =item B<--bundle | --all-exec | -X>
481 treat all executables in the main deployment directory as scripts
482 to be executed
484 =item B<--cleanup | --no-cleanup>
486 perform / don't perform temporary directory cleanup after work done
488 =item B<--dryrun | --dry-run>
490 print final options and exit
492 =item B<< --inspect <dirname> >>
494 just extract all the stuff into <dirname> for inspection. Implies
495 C<--no-deploy>, C<--no-tempdir>, ignores C<--bundle> (as a consequence of
496 C<--no-deploy>), disables C<--cleanup> and sets the working directory
497 to C<dirname>
499 =item B<--no-deploy>
501 prevent execution of deploy scripts (they are executed by default)
503 =item B<--no-workdir>
505 execute directly in workdir (see below), without creating the
506 temporary directory
508 =item B<--show | --show-options | -s>
510 print configured options and exit
512 =item B<--workdir | -w>
514 working base directory (a temporary subdirectory will be created
515 there anyway)
517 =back
519 Note the difference between C<--show> and C<--dryrun>: the former will
520 give you the options that are "embedded" in the I<deploy script> without
521 taking into account other options given on the command line, while the
522 latter will give you the final options that would be used if the script
523 were called without C<--dryrun>.
525 =head2 Deploy Script Example Usage
527 In the following, we'll assume that the I<deploy script> is called
528 C<deploy.pl>.
530 To execute the script with the already configured options, you just have
531 to call it:
533 shell$ ./deploy.pl
535 If you just want to see which configurations are in the I<deploy script>:
537 shell$ ./deploy.pl --show
539 Extract contents of the script in a temp directory and simply inspect
540 what's inside:
542 # extract stuff into subdirectory 'inspect' for... inspection
543 shell$ ./deploy.pl --no-tempdir --no-deploy --workdir inspect
545 =head2 Deploy Script Requirements
547 Care has been taken to make the requirements of the deploy script as low
548 as possible. The result is that you'll need a working Perl with version
549 at least 5.6.2, and GNU B<tar> with support for options C<--touch> and
550 C<--no-same-owner>. Good luck.
553 =head1 DIAGNOSTICS
555 Each error message should be enough explicit to be understood without the
556 need for furter explainations. Which is another way to say that I'm way
557 too lazy to list all possible ways that this script has to fail.
560 =head1 CONFIGURATION AND ENVIRONMENT
562 deployable requires no configuration files or environment variables.
564 Please note that deployable B<needs> to find its master B<remote> file
565 to produce the final script. This must be put in the same directory where
566 deployable is put. You should be able to B<symlink> deployable where you
567 think it's better, anyway - it will go search for the original file
568 and look for B<remote> inside the same directory. This does not apply to
569 hard links, of course.
572 =head1 DEPENDENCIES
574 All core modules, apart from L<version> which is nearly-core.
577 =head1 BUGS AND LIMITATIONS
579 No bugs have been reported.
581 Please report any bugs or feature requests to the AUTHOR below.
584 =head1 AUTHOR
586 Flavio Poletti C<flavio [AT] polettix.it>
589 =head1 LICENSE AND COPYRIGHT
591 Copyright (c) 2008, Flavio Poletti C<flavio [AT] polettix.it>. All rights reserved.
593 This script is free software; you can redistribute it and/or
594 modify it under the same terms as Perl itself. See L<perlartistic>
595 and L<perlgpl>.
597 Questo script è software libero: potete ridistribuirlo e/o
598 modificarlo negli stessi termini di Perl stesso. Vedete anche
599 L<perlartistic> e L<perlgpl>.
602 =head1 DISCLAIMER OF WARRANTY
604 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
605 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
606 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
607 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
608 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
609 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
610 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
611 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
612 NECESSARY SERVICING, REPAIR, OR CORRECTION.
614 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
615 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
616 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
617 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
618 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
619 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
620 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
621 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
622 SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
623 SUCH DAMAGES.
625 =head1 NEGAZIONE DELLA GARANZIA
627 Poiché questo software viene dato con una licenza gratuita, non
628 c'è alcuna garanzia associata ad esso, ai fini e per quanto permesso
629 dalle leggi applicabili. A meno di quanto possa essere specificato
630 altrove, il proprietario e detentore del copyright fornisce questo
631 software "così com'è" senza garanzia di alcun tipo, sia essa espressa
632 o implicita, includendo fra l'altro (senza però limitarsi a questo)
633 eventuali garanzie implicite di commerciabilità e adeguatezza per
634 uno scopo particolare. L'intero rischio riguardo alla qualità ed
635 alle prestazioni di questo software rimane a voi. Se il software
636 dovesse dimostrarsi difettoso, vi assumete tutte le responsabilità
637 ed i costi per tutti i necessari servizi, riparazioni o correzioni.
639 In nessun caso, a meno che ciò non sia richiesto dalle leggi vigenti
640 o sia regolato da un accordo scritto, alcuno dei detentori del diritto
641 di copyright, o qualunque altra parte che possa modificare, o redistribuire
642 questo software così come consentito dalla licenza di cui sopra, potrà
643 essere considerato responsabile nei vostri confronti per danni, ivi
644 inclusi danni generali, speciali, incidentali o conseguenziali, derivanti
645 dall'utilizzo o dall'incapacità di utilizzo di questo software. Ciò
646 include, a puro titolo di esempio e senza limitarsi ad essi, la perdita
647 di dati, l'alterazione involontaria o indesiderata di dati, le perdite
648 sostenute da voi o da terze parti o un fallimento del software ad
649 operare con un qualsivoglia altro software. Tale negazione di garanzia
650 rimane in essere anche se i dententori del copyright, o qualsiasi altra
651 parte, è stata avvisata della possibilità di tali danneggiamenti.
653 Se decidete di utilizzare questo software, lo fate a vostro rischio
654 e pericolo. Se pensate che i termini di questa negazione di garanzia
655 non si confacciano alle vostre esigenze, o al vostro modo di
656 considerare un software, o ancora al modo in cui avete sempre trattato
657 software di terze parti, non usatelo. Se lo usate, accettate espressamente
658 questa negazione di garanzia e la piena responsabilità per qualsiasi
659 tipo di danno, di qualsiasi natura, possa derivarne.
661 =cut