330 lines
9.1 KiB
Perl
Executable File
330 lines
9.1 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
# $Id: slack 180 2008-01-19 08:26:19Z alan $
|
|
# vim:sw=2
|
|
# vim600:fdm=marker
|
|
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
|
|
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
|
|
# See the file COPYING for details.
|
|
|
|
# This script is in charge of copying files from the (possibly remote)
|
|
# master directory to a local cache, using rsync
|
|
|
|
require 5.006;
|
|
use warnings FATAL => qw(all);
|
|
use strict;
|
|
use sigtrap qw(die untrapped normal-signals
|
|
stack-trace any error-signals);
|
|
|
|
use File::Path;
|
|
use File::Find;
|
|
use POSIX; # for strftime
|
|
|
|
use constant LIBEXEC_DIR => '/usr/lib/slack';
|
|
use constant LIB_DIR => '/usr/lib/slack';
|
|
use lib LIB_DIR;
|
|
use Slack;
|
|
|
|
sub run_backend(@);
|
|
sub run_conditional_backend($@);
|
|
|
|
(my $PROG = $0) =~ s#.*/##;
|
|
|
|
# Arguments to pass to each backends (initialized to a hash of empty arrays)
|
|
my %backend_flags = ( map { $_ => [] }
|
|
qw(getroles sync stage preview preinstall fixfiles installfiles postinstall)
|
|
);
|
|
|
|
my @roles;
|
|
|
|
########################################
|
|
# Environment
|
|
# Helpful prefix to die messages
|
|
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
|
|
# Set a reasonable umask
|
|
umask 077;
|
|
# Get out of wherever (possibly NFS-mounted) we were
|
|
chdir("/")
|
|
or die "Could not chdir /: $!";
|
|
# Autoflush on STDERR
|
|
select((select(STDERR), $|=1)[0]);
|
|
|
|
########################################
|
|
# Config and option parsing {{{
|
|
my $usage = Slack::default_usage("$PROG [options] [<role>...]");
|
|
$usage .= <<EOF;
|
|
|
|
--preview MODE
|
|
Do a diff of scripts and files before running them.
|
|
MODE can be one of 'simple' or 'prompt'.
|
|
|
|
--no-files
|
|
Don't install any files in ROOT, but tell rsync to print what
|
|
it would do.
|
|
|
|
--no-scripts
|
|
Don't run scripts.
|
|
|
|
--no-sync
|
|
Skip the slack-sync step. (useful if you're pushing stuff into
|
|
the CACHE outside of slack)
|
|
|
|
--role-list
|
|
Role list for slack-getroles
|
|
|
|
--libexec-dir DIR
|
|
Look for backend scripts in this directory.
|
|
|
|
--diff PROG
|
|
Use this diff program for previews
|
|
|
|
--sleep TIME
|
|
Randomly sleep between 1 and TIME seconds before starting
|
|
operations
|
|
EOF
|
|
|
|
# Options
|
|
my %opt = ();
|
|
# So we can distinguish stuff on the command line from config file stuff
|
|
my %command_line_opt = ();
|
|
Slack::get_options(
|
|
opthash => \%opt,
|
|
command_line_options => [
|
|
'preview=s',
|
|
'role-list=s',
|
|
'no-scripts|noscripts',
|
|
'no-files|nofiles',
|
|
'no-sync|nosync',
|
|
'libexec-dir=s',
|
|
'diff=s',
|
|
'sleep=i',
|
|
],
|
|
required_options => [ qw(source cache stage root) ],
|
|
command_line_hash => \%command_line_opt,
|
|
usage => $usage,
|
|
);
|
|
|
|
# Special options
|
|
if ($opt{'dry-run'}) {
|
|
$opt{'no-scripts'} = 1;
|
|
$opt{'no-files'} = 1;
|
|
}
|
|
if ($opt{'no-scripts'}) {
|
|
for my $action (qw(fixfiles preinstall postinstall)) {
|
|
push @{$backend_flags{$action}},
|
|
'--dry-run';
|
|
}
|
|
}
|
|
if ($opt{'no-files'}) {
|
|
push @{$backend_flags{installfiles}},
|
|
'--dry-run';
|
|
}
|
|
# propagate verbosity - 1 to all backends
|
|
if (defined $command_line_opt{'verbose'} and
|
|
$command_line_opt{'verbose'} > 1) {
|
|
for my $action (keys %backend_flags) {
|
|
push @{$backend_flags{$action}},
|
|
('--verbose') x ($command_line_opt{'verbose'} - 1);
|
|
}
|
|
}
|
|
# propagate these flags to all the backends
|
|
for my $option (qw(config root cache stage source hostname rsh)) {
|
|
if ($command_line_opt{$option}) {
|
|
for my $action (keys %backend_flags) {
|
|
push @{$backend_flags{$action}},
|
|
"--$option=$command_line_opt{$option}";
|
|
}
|
|
}
|
|
}
|
|
# getroles also can take 'role-list'
|
|
if ($command_line_opt{'role-list'}) {
|
|
push @{$backend_flags{'getroles'}},
|
|
"--role-list=$command_line_opt{'role-list'}";
|
|
}
|
|
|
|
# The libexec dir defaults to this if it wasn't specified
|
|
# on the command line or in a config file.
|
|
if (not defined $opt{'libexec-dir'}) {
|
|
$opt{'libexec-dir'} = LIBEXEC_DIR;
|
|
}
|
|
|
|
# Pass diff option along to slack-rolediff
|
|
if ($opt{'diff'}) {
|
|
push @{$backend_flags{preview}},
|
|
"--diff=$opt{'diff'}";
|
|
}
|
|
|
|
# Preview takes an optional argument. If no argument is given,
|
|
# it gets "" from getopt.
|
|
if (defined $opt{'preview'}) {
|
|
if (not grep /^$opt{'preview'}$/, qw(simple prompt)) {
|
|
die "Unknown preview mode '$opt{'preview'}'!";
|
|
}
|
|
}
|
|
|
|
# The backup option defaults to on if it wasn't specified
|
|
# on the command line or in a config file
|
|
if (not defined $opt{backup}) {
|
|
$opt{backup} = 1;
|
|
}
|
|
# Figure out a place to put backups
|
|
if ($opt{backup} and $opt{'backup-dir'}) {
|
|
push @{$backend_flags{installfiles}},
|
|
'--backup',
|
|
'--backup-dir='.
|
|
$opt{'backup-dir'}.
|
|
"/".
|
|
strftime('%F-%T', localtime(time))
|
|
;
|
|
}
|
|
# }}}
|
|
|
|
# Random sleep, helpful when called from cron.
|
|
if ($opt{sleep}) {
|
|
my $secs = int(rand($opt{sleep})) + 1;
|
|
$opt{verbose} and print STDERR "$PROG: sleep $secs\n";
|
|
sleep($secs);
|
|
}
|
|
|
|
# Get a list of roles to install from slack-getroles {{{
|
|
if (not @ARGV) {
|
|
my @command = ($opt{'libexec-dir'}.'/slack-getroles',
|
|
@{$backend_flags{'getroles'}});
|
|
$opt{verbose} and print STDERR "$PROG: getroles\n";
|
|
($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command' to get a list of roles for this host.\n";
|
|
my ($roles_pid, $roles_fh);
|
|
if ($roles_pid = open($roles_fh, "-|")) {
|
|
# Parent
|
|
} elsif (defined $roles_pid) {
|
|
# Child
|
|
exec(@command);
|
|
die "Could not exec '@command': $!\n";
|
|
} else {
|
|
die "Could not fork to run '@command': $!\n";
|
|
}
|
|
@roles = split(/\s+/, join(" ", <$roles_fh>));
|
|
unless (close($roles_fh)) {
|
|
Slack::check_system_exit(@command);
|
|
}
|
|
} else {
|
|
@roles = @ARGV;
|
|
}
|
|
# }}}
|
|
|
|
# Check role name syntax {{{
|
|
for my $role (@roles) {
|
|
# Roles MUST begin with a letter. All else is reserved.
|
|
if ($role !~ m/^[a-zA-Z]/) {
|
|
die "Role '$role' does not begin with a letter!";
|
|
}
|
|
}
|
|
# }}}
|
|
|
|
$opt{verbose} and print STDERR "$PROG: installing roles: @roles\n";
|
|
|
|
unless ($opt{'no-sync'}) {
|
|
# sync all the roles down at once
|
|
$opt{verbose} and print STDERR "$PROG: sync @roles\n";
|
|
run_backend('slack-sync',
|
|
@{$backend_flags{sync}}, @roles);
|
|
}
|
|
|
|
ROLE: for my $role (@roles) {
|
|
# stage
|
|
$opt{verbose} and print STDERR "$PROG: stage files $role\n";
|
|
run_backend('slack-stage',
|
|
@{$backend_flags{stage}}, '--subdir=files', $role);
|
|
|
|
if ($opt{preview}) {
|
|
if ($opt{preview} eq 'simple') {
|
|
$opt{verbose} and print STDERR "$PROG: preview $role\n";
|
|
# Here, we run the backend in no-prompt mode.
|
|
run_conditional_backend(0, 'slack-rolediff',
|
|
@{$backend_flags{preview}}, $role);
|
|
# ...and we skip further action in the ROLE after showing the diff.
|
|
next ROLE;
|
|
} elsif ($opt{preview} eq 'prompt') {
|
|
$opt{verbose} and print STDERR "$PROG: preview scripts $role\n";
|
|
# Here, we want to prompt and just do the scripts, since
|
|
# we need to run preinstall and fixfiles before doing the files.
|
|
run_conditional_backend(1, 'slack-rolediff',
|
|
@{$backend_flags{preview}}, '--subdir=scripts', $role);
|
|
} else {
|
|
# Should get caught in option processing, above
|
|
die "Unknown preview mode!\n";
|
|
}
|
|
}
|
|
|
|
$opt{verbose} and print STDERR "$PROG: stage scripts $role\n";
|
|
run_backend('slack-stage',
|
|
@{$backend_flags{stage}}, '--subdir=scripts', $role);
|
|
|
|
# preinstall
|
|
$opt{verbose} and print STDERR "$PROG: preinstall $role\n";
|
|
run_backend('slack-runscript',
|
|
@{$backend_flags{preinstall}}, 'preinstall', $role);
|
|
|
|
# fixfiles
|
|
$opt{verbose} and print STDERR "$PROG: fixfiles $role\n";
|
|
run_backend('slack-runscript',
|
|
@{$backend_flags{fixfiles}}, 'fixfiles', $role);
|
|
|
|
# preview files
|
|
if ($opt{preview} and $opt{preview} eq 'prompt') {
|
|
$opt{verbose} and print STDERR "$PROG: preview files $role\n";
|
|
run_conditional_backend(1, 'slack-rolediff',
|
|
@{$backend_flags{preview}}, '--subdir=files', $role);
|
|
}
|
|
|
|
# installfiles
|
|
$opt{verbose} and print STDERR "$PROG: install $role\n";
|
|
run_backend('slack-installfiles',
|
|
@{$backend_flags{installfiles}}, $role);
|
|
|
|
# postinstall
|
|
$opt{verbose} and print STDERR "$PROG: postinstall $role\n";
|
|
run_backend('slack-runscript',
|
|
@{$backend_flags{postinstall}}, 'postinstall', $role);
|
|
}
|
|
exit 0;
|
|
|
|
sub run_backend (@) {
|
|
my ($backend, @args) = @_;
|
|
# If we weren't given an explicit path, prepend the libexec dir
|
|
unless ($backend =~ m#^/#) {
|
|
$backend = $opt{'libexec-dir'} . '/' . $backend;
|
|
}
|
|
|
|
# Assemble our command line
|
|
my (@command) = ($backend, @args);
|
|
($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n";
|
|
unless (system(@command) == 0) {
|
|
Slack::check_system_exit(@command);
|
|
}
|
|
}
|
|
|
|
sub run_conditional_backend ($@) {
|
|
my ($prompt, $backend, @args) = @_;
|
|
# If we weren't given an explicit path, prepend the libexec dir
|
|
unless ($backend =~ m#^/#) {
|
|
$backend = $opt{'libexec-dir'} . '/' . $backend;
|
|
}
|
|
|
|
# Assemble our command line
|
|
my (@command) = ($backend, @args);
|
|
($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n";
|
|
unless (system(@command) == 0) {
|
|
my $exit = Slack::get_system_exit(@command);
|
|
|
|
if ($exit == 1) {
|
|
# exit 1 means a difference found or something normal that requires
|
|
# a prompt before continuing.
|
|
if ($prompt) {
|
|
exit 1 unless Slack::prompt("Continue? [yN] ") eq 'y';
|
|
}
|
|
} else {
|
|
# any other non-successful exit is a serious error.
|
|
die "'@command' exited $exit";
|
|
}
|
|
}
|
|
}
|