This commit is contained in:
Charles N Wyble - admin 2018-01-24 21:26:01 -06:00
parent ca72570d12
commit 25296801b6
12 changed files with 2405 additions and 2405 deletions

View File

@ -1,39 +1,39 @@
# Makefile for slack/src # Makefile for slack/src
# $Id: Makefile 187 2008-03-03 02:00:18Z alan $ # $Id: Makefile 187 2008-03-03 02:00:18Z alan $
include Makefile.common include Makefile.common
BACKENDS = slack-getroles slack-installfiles slack-runscript slack-sync slack-stage slack-rolediff BACKENDS = slack-getroles slack-installfiles slack-runscript slack-sync slack-stage slack-rolediff
all: all:
install: install-bin install-conf install-lib install-man install: install-bin install-conf install-lib install-man
install-bin: all install-bin: all
$(MKDIR) $(DESTDIR)$(sbindir) $(MKDIR) $(DESTDIR)$(sbindir)
$(INSTALL) slack $(DESTDIR)$(sbindir) $(INSTALL) slack $(DESTDIR)$(sbindir)
$(MKDIR) $(DESTDIR)$(bindir) $(MKDIR) $(DESTDIR)$(bindir)
$(INSTALL) slack-diff $(DESTDIR)$(bindir) $(INSTALL) slack-diff $(DESTDIR)$(bindir)
$(MKDIR) $(DESTDIR)$(slack_libexecdir) $(MKDIR) $(DESTDIR)$(slack_libexecdir)
@set -ex;\ @set -ex;\
for i in $(BACKENDS); do \ for i in $(BACKENDS); do \
$(INSTALL) $$i $(DESTDIR)$(slack_libexecdir); done $(INSTALL) $$i $(DESTDIR)$(slack_libexecdir); done
$(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localstatedir) $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localstatedir)
$(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localcachedir) $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localcachedir)
install-conf: all install-conf: all
$(MKDIR) $(DESTDIR)$(sysconfdir) $(MKDIR) $(DESTDIR)$(sysconfdir)
$(INSTALL) -m 0644 slack.conf $(DESTDIR)$(sysconfdir) $(INSTALL) -m 0644 slack.conf $(DESTDIR)$(sysconfdir)
install-lib: all install-lib: all
$(MKDIR) $(DESTDIR)$(slack_libdir) $(MKDIR) $(DESTDIR)$(slack_libdir)
$(INSTALL) -m 0644 Slack.pm $(DESTDIR)$(slack_libdir) $(INSTALL) -m 0644 Slack.pm $(DESTDIR)$(slack_libdir)
install-man: all install-man: all
clean: clean:
realclean: clean realclean: clean
distclean: clean distclean: clean
test: test:

View File

@ -1,27 +1,27 @@
# Common code included in every Makefile # Common code included in every Makefile
# $Id: Makefile.common 189 2008-04-21 00:52:56Z sundell $ # $Id: Makefile.common 189 2008-04-21 00:52:56Z sundell $
PACKAGE=slack PACKAGE=slack
VERSION=0.15.2 VERSION=0.15.2
DESTDIR = DESTDIR =
prefix = / prefix = /
exec_prefix = /usr exec_prefix = /usr
sysconfdir = ${prefix}/etc sysconfdir = ${prefix}/etc
mandir = ${exec_prefix}/share/man mandir = ${exec_prefix}/share/man
bindir = ${exec_prefix}/bin bindir = ${exec_prefix}/bin
sbindir = ${exec_prefix}/sbin sbindir = ${exec_prefix}/sbin
libdir = ${exec_prefix}/lib libdir = ${exec_prefix}/lib
libexecdir = ${exec_prefix}/lib libexecdir = ${exec_prefix}/lib
localstatedir = ${prefix}/var localstatedir = ${prefix}/var
slack_libdir = ${libdir}/slack slack_libdir = ${libdir}/slack
slack_libexecdir = ${libexecdir}/slack slack_libexecdir = ${libexecdir}/slack
slack_localstatedir = ${localstatedir}/lib/slack slack_localstatedir = ${localstatedir}/lib/slack
slack_localcachedir = ${localstatedir}/cache/slack slack_localcachedir = ${localstatedir}/cache/slack
INSTALL = install INSTALL = install
MKDIR = mkdir -p MKDIR = mkdir -p
PRIVDIRMODE = 0700 PRIVDIRMODE = 0700

View File

@ -1,371 +1,371 @@
# $Id: Slack.pm 189 2008-04-21 00:52:56Z sundell $ # $Id: Slack.pm 189 2008-04-21 00:52:56Z sundell $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
package Slack; package Slack;
require 5.006; require 5.006;
use strict; use strict;
use Carp qw(cluck confess croak); use Carp qw(cluck confess croak);
use File::Find; use File::Find;
use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
use base qw(Exporter); use base qw(Exporter);
use vars qw($VERSION @EXPORT @EXPORT_OK $DEFAULT_CONFIG_FILE); use vars qw($VERSION @EXPORT @EXPORT_OK $DEFAULT_CONFIG_FILE);
$VERSION = '0.15.2'; $VERSION = '0.15.2';
@EXPORT = qw(); @EXPORT = qw();
@EXPORT_OK = qw(); @EXPORT_OK = qw();
$DEFAULT_CONFIG_FILE = '/etc/slack.conf'; $DEFAULT_CONFIG_FILE = '/etc/slack.conf';
my $term; my $term;
my @default_options = ( my @default_options = (
'help|h|?', 'help|h|?',
'version', 'version',
'verbose|v+', 'verbose|v+',
'quiet', 'quiet',
'config|C=s', 'config|C=s',
'source|s=s', 'source|s=s',
'rsh|e=s', 'rsh|e=s',
'cache|c=s', 'cache|c=s',
'stage|t=s', 'stage|t=s',
'root|r=s', 'root|r=s',
'dry-run|n', 'dry-run|n',
'backup|b', 'backup|b',
'backup-dir=s', 'backup-dir=s',
'hostname|H=s', 'hostname|H=s',
); );
sub default_usage ($) { sub default_usage ($) {
my ($synopsis) = @_; my ($synopsis) = @_;
return <<EOF; return <<EOF;
Usage: $synopsis Usage: $synopsis
Options: Options:
-h, -?, --help -h, -?, --help
Print this help message and exit. Print this help message and exit.
--version --version
Print the version number and exit. Print the version number and exit.
-v, --verbose -v, --verbose
Be verbose. Be verbose.
--quiet --quiet
Don't be verbose (Overrides previous uses of --verbose) Don't be verbose (Overrides previous uses of --verbose)
-C, --config FILE -C, --config FILE
Use this config file instead of '$DEFAULT_CONFIG_FILE'. Use this config file instead of '$DEFAULT_CONFIG_FILE'.
-s, --source DIR -s, --source DIR
Source for slack files Source for slack files
-e, --rsh COMMAND -e, --rsh COMMAND
Remote shell for rsync Remote shell for rsync
-c, --cache DIR -c, --cache DIR
Local cache directory for slack files Local cache directory for slack files
-t, --stage DIR -t, --stage DIR
Local staging directory for slack files Local staging directory for slack files
-r, --root DIR -r, --root DIR
Root destination for slack files Root destination for slack files
-n, --dry-run -n, --dry-run
Don't write any files to disk -- just report what would have been done. Don't write any files to disk -- just report what would have been done.
-b, --backup -b, --backup
Make backups of existing files in ROOT that are overwritten. Make backups of existing files in ROOT that are overwritten.
--backup-dir DIR --backup-dir DIR
Put backups into this directory. Put backups into this directory.
-H, --hostname HOST -H, --hostname HOST
Pretend to be running on HOST, instead of the name given by Pretend to be running on HOST, instead of the name given by
gethostname(2). gethostname(2).
EOF EOF
} }
# Read options from a config file. Arguments: # Read options from a config file. Arguments:
# file => config file to read # file => config file to read
# opthash => hashref in which to store the options # opthash => hashref in which to store the options
# verbose => whether to be verbose # verbose => whether to be verbose
sub read_config (%) { sub read_config (%) {
my %arg = @_; my %arg = @_;
my ($config_fh); my ($config_fh);
local $_; local $_;
confess "Slack::read_config: no config file given" confess "Slack::read_config: no config file given"
if not defined $arg{file}; if not defined $arg{file};
$arg{opthash} = {} $arg{opthash} = {}
if not defined $arg{opthash}; if not defined $arg{opthash};
open($config_fh, '<', $arg{file}) open($config_fh, '<', $arg{file})
or confess "Could not open config file '$arg{file}': $!"; or confess "Could not open config file '$arg{file}': $!";
# Make this into a hash so we can quickly see if we're looking # Make this into a hash so we can quickly see if we're looking
# for a particular option # for a particular option
my %looking_for; my %looking_for;
if (ref $arg{options} eq 'ARRAY') { if (ref $arg{options} eq 'ARRAY') {
%looking_for = map { $_ => 1 } @{$arg{options}}; %looking_for = map { $_ => 1 } @{$arg{options}};
} }
while(<$config_fh>) { while(<$config_fh>) {
chomp; chomp;
s/#.*//; # delete comments s/#.*//; # delete comments
s/\s+$//; # delete trailing spaces s/\s+$//; # delete trailing spaces
next if m/^$/; # skip empty lines next if m/^$/; # skip empty lines
if (m/^[A-Z_]+=\S+/) { if (m/^[A-Z_]+=\S+/) {
my ($key, $value) = split(/=/, $_, 2); my ($key, $value) = split(/=/, $_, 2);
$key =~ tr/A-Z_/a-z-/; $key =~ tr/A-Z_/a-z-/;
# Only set options we're looking for # Only set options we're looking for
next if (%looking_for and not $looking_for{$key}); next if (%looking_for and not $looking_for{$key});
# Don't set options that are already set # Don't set options that are already set
next if defined $arg{opthash}->{$key}; next if defined $arg{opthash}->{$key};
$arg{verbose} and print STDERR "Slack::read_config: Setting '$key' to '$value'\n"; $arg{verbose} and print STDERR "Slack::read_config: Setting '$key' to '$value'\n";
$arg{opthash}->{$key} = $value; $arg{opthash}->{$key} = $value;
} else { } else {
cluck "Slack::read_config: Garbage line '$_' in '$arg{file}' line $. ignored"; cluck "Slack::read_config: Garbage line '$_' in '$arg{file}' line $. ignored";
} }
} }
close($config_fh) close($config_fh)
or confess "Slack::read_config: Could not close config file: $!"; or confess "Slack::read_config: Could not close config file: $!";
# The verbose option is treated specially in so many places that # The verbose option is treated specially in so many places that
# we need to make sure it's defined. # we need to make sure it's defined.
$arg{opthash}->{verbose} ||= 0; $arg{opthash}->{verbose} ||= 0;
return $arg{opthash}; return $arg{opthash};
} }
# Just get the exit code from a command that failed. # Just get the exit code from a command that failed.
# croaks if anything weird happened. # croaks if anything weird happened.
sub get_system_exit (@) { sub get_system_exit (@) {
my @command = @_; my @command = @_;
if (WIFEXITED($?)) { if (WIFEXITED($?)) {
my $exit = WEXITSTATUS($?); my $exit = WEXITSTATUS($?);
return $exit if $exit; return $exit if $exit;
} }
if (WIFSIGNALED($?)) { if (WIFSIGNALED($?)) {
my $sig = WTERMSIG($?); my $sig = WTERMSIG($?);
croak "'@command' caught sig $sig"; croak "'@command' caught sig $sig";
} }
if ($!) { if ($!) {
croak "Syserr on system '@command': $!"; croak "Syserr on system '@command': $!";
} }
croak "Unknown error on '@command'"; croak "Unknown error on '@command'";
} }
sub check_system_exit (@) { sub check_system_exit (@) {
my @command = @_; my @command = @_;
my $exit = get_system_exit(@command); my $exit = get_system_exit(@command);
# Exit is non-zero if get_system_exit() didn't croak. # Exit is non-zero if get_system_exit() didn't croak.
croak "'@command' exited $exit"; croak "'@command' exited $exit";
} }
# get options from the command line and the config file # get options from the command line and the config file
# Arguments # Arguments
# opthash => hashref in which to store options # opthash => hashref in which to store options
# usage => usage statement # usage => usage statement
# required_options => arrayref of options to require -- an exception # required_options => arrayref of options to require -- an exception
# will be thrown if these options are not defined # will be thrown if these options are not defined
# command_line_hash => store options specified on the command line here # command_line_hash => store options specified on the command line here
sub get_options { sub get_options {
my %arg = @_; my %arg = @_;
use Getopt::Long; use Getopt::Long;
Getopt::Long::Configure('bundling'); Getopt::Long::Configure('bundling');
if (not defined $arg{opthash}) { if (not defined $arg{opthash}) {
$arg{opthash} = {}; $arg{opthash} = {};
} }
if (not defined $arg{usage}) { if (not defined $arg{usage}) {
$arg{usage} = default_usage($0); $arg{usage} = default_usage($0);
} }
my @extra_options = (); # extra arguments to getoptions my @extra_options = (); # extra arguments to getoptions
if (defined $arg{command_line_options}) { if (defined $arg{command_line_options}) {
@extra_options = @{$arg{command_line_options}}; @extra_options = @{$arg{command_line_options}};
} }
# Make a --quiet function that turns off verbosity # Make a --quiet function that turns off verbosity
$arg{opthash}->{quiet} = sub { $arg{opthash}->{verbose} = 0; }; $arg{opthash}->{quiet} = sub { $arg{opthash}->{verbose} = 0; };
unless (GetOptions($arg{opthash}, unless (GetOptions($arg{opthash},
@default_options, @default_options,
@extra_options, @extra_options,
)) { )) {
print STDERR $arg{usage}; print STDERR $arg{usage};
exit 1; exit 1;
} }
if ($arg{opthash}->{help}) { if ($arg{opthash}->{help}) {
print $arg{usage}; print $arg{usage};
exit 0; exit 0;
} }
if ($arg{opthash}->{version}) { if ($arg{opthash}->{version}) {
print "slack version $VERSION\n"; print "slack version $VERSION\n";
exit 0; exit 0;
} }
# Get rid of the quiet handler # Get rid of the quiet handler
delete $arg{opthash}->{quiet}; delete $arg{opthash}->{quiet};
# If we've been given a hashref, save our options there at this # If we've been given a hashref, save our options there at this
# stage, so the caller can see what was passed on the command line. # stage, so the caller can see what was passed on the command line.
# Unfortunately, perl has no .replace function, so we iterate. # Unfortunately, perl has no .replace function, so we iterate.
if (ref $arg{command_line_hash} eq 'HASH') { if (ref $arg{command_line_hash} eq 'HASH') {
while (my ($k, $v) = each %{$arg{opthash}}) { while (my ($k, $v) = each %{$arg{opthash}}) {
$arg{command_line_hash}->{$k} = $v; $arg{command_line_hash}->{$k} = $v;
} }
} }
# Use the default config file # Use the default config file
if (not defined $arg{opthash}->{config}) { if (not defined $arg{opthash}->{config}) {
$arg{opthash}->{config} = $DEFAULT_CONFIG_FILE; $arg{opthash}->{config} = $DEFAULT_CONFIG_FILE;
} }
# We need to decide whether to be verbose about reading the config file # We need to decide whether to be verbose about reading the config file
# Currently we just do it if global verbosity > 2 # Currently we just do it if global verbosity > 2
my $verbose_config = 0; my $verbose_config = 0;
if (defined $arg{opthash}->{verbose} if (defined $arg{opthash}->{verbose}
and $arg{opthash}->{verbose} > 2) { and $arg{opthash}->{verbose} > 2) {
$verbose_config = 1; $verbose_config = 1;
} }
# Read options from the config file, passing along the options we've # Read options from the config file, passing along the options we've
# gotten so far # gotten so far
read_config( read_config(
file => $arg{opthash}->{config}, file => $arg{opthash}->{config},
opthash => $arg{opthash}, opthash => $arg{opthash},
verbose => $verbose_config, verbose => $verbose_config,
); );
# The "verbose" option gets compared a lot and needs to be defined # The "verbose" option gets compared a lot and needs to be defined
$arg{opthash}->{verbose} ||= 0; $arg{opthash}->{verbose} ||= 0;
# The "hostname" option is set specially if it's not defined # The "hostname" option is set specially if it's not defined
if (not defined $arg{opthash}->{hostname}) { if (not defined $arg{opthash}->{hostname}) {
use Sys::Hostname; use Sys::Hostname;
$arg{opthash}->{hostname} = hostname; $arg{opthash}->{hostname} = hostname;
} }
# We can require some options to be set # We can require some options to be set
if (ref $arg{required_options} eq 'ARRAY') { if (ref $arg{required_options} eq 'ARRAY') {
for my $option (@{$arg{required_options}}) { for my $option (@{$arg{required_options}}) {
if (not defined $arg{opthash}->{$option}) { if (not defined $arg{opthash}->{$option}) {
croak "Required option '$option' not given on command line or specified in config file!\n"; croak "Required option '$option' not given on command line or specified in config file!\n";
} }
} }
} }
return $arg{opthash}; return $arg{opthash};
} }
sub prompt ($) { sub prompt ($) {
my ($prompt) = @_; my ($prompt) = @_;
if (not defined $term) { if (not defined $term) {
require Term::ReadLine; require Term::ReadLine;
$term = new Term::ReadLine 'slack' $term = new Term::ReadLine 'slack'
} }
$term->readline($prompt); $term->readline($prompt);
} }
# Calls the callback on absolute pathnames of files in the source directory, # Calls the callback on absolute pathnames of files in the source directory,
# and also on names of directories that don't exist in the destination # and also on names of directories that don't exist in the destination
# directory (i.e. where $source/foo exists but $destination/foo does not). # directory (i.e. where $source/foo exists but $destination/foo does not).
sub find_files_to_install ($$$) { sub find_files_to_install ($$$) {
my ($source, $destination, $callback) = @_; my ($source, $destination, $callback) = @_;
return find ({ return find ({
wanted => sub { wanted => sub {
if (-l or not -d _) { if (-l or not -d _) {
# Copy all files, links, etc # Copy all files, links, etc
my $file = $File::Find::name; my $file = $File::Find::name;
&$callback($file); &$callback($file);
} elsif (-d _) { } elsif (-d _) {
# For directories, we only want to copy it if it doesn't # For directories, we only want to copy it if it doesn't
# exist in the destination yet. # exist in the destination yet.
my $dir = $File::Find::name; my $dir = $File::Find::name;
# We know the root directory will exist (we make it above), # We know the root directory will exist (we make it above),
# so skip the base of the source # so skip the base of the source
(my $short_source = $source) =~ s#/$##; (my $short_source = $source) =~ s#/$##;
return if $dir eq $short_source; return if $dir eq $short_source;
# Strip the $source from the path, # Strip the $source from the path,
# so we can build the destination dir from it. # so we can build the destination dir from it.
my $subdir = $dir; my $subdir = $dir;
($subdir =~ s#^$source##) ($subdir =~ s#^$source##)
or croak "sub failed: $source|$subdir"; or croak "sub failed: $source|$subdir";
if (not -d "$destination/$subdir") { if (not -d "$destination/$subdir") {
&$callback($dir); &$callback($dir);
} }
} }
} }
}, },
$source, $source,
); );
} }
# Runs rsync with the necessary redirection to its filehandles # Runs rsync with the necessary redirection to its filehandles
sub wrap_rsync (@) { sub wrap_rsync (@) {
my @command = @_; my @command = @_;
my ($pid); my ($pid);
if ($pid = fork) { if ($pid = fork) {
# Parent # Parent
} elsif (defined $pid) { } elsif (defined $pid) {
# Child # Child
open(STDIN, "<", "/dev/null") open(STDIN, "<", "/dev/null")
or die "Could not redirect STDIN from /dev/null\n"; or die "Could not redirect STDIN from /dev/null\n";
# This redirection is necessary because rsync sends # This redirection is necessary because rsync sends
# verbose output to STDOUT # verbose output to STDOUT
open(STDOUT, ">&STDERR") open(STDOUT, ">&STDERR")
or die "Could not redirect STDOUT to STDERR\n"; or die "Could not redirect STDOUT to STDERR\n";
exec(@command); exec(@command);
die "Could not exec '@command': $!\n"; die "Could not exec '@command': $!\n";
} else { } else {
die "Could not fork: $!\n"; die "Could not fork: $!\n";
} }
my $kid = waitpid($pid, 0); my $kid = waitpid($pid, 0);
if ($kid != $pid) { if ($kid != $pid) {
die "waitpid returned $kid\n"; die "waitpid returned $kid\n";
} elsif ($?) { } elsif ($?) {
Slack::check_system_exit(@command); Slack::check_system_exit(@command);
} }
} }
# Runs rsync with the necessary redirection to its filehandles, but also # Runs rsync with the necessary redirection to its filehandles, but also
# returns an FH to stdin and a PID. # returns an FH to stdin and a PID.
sub wrap_rsync_fh (@) { sub wrap_rsync_fh (@) {
my @command = @_; my @command = @_;
my ($fh, $pid); my ($fh, $pid);
if ($pid = open($fh, "|-")) { if ($pid = open($fh, "|-")) {
# Parent # Parent
} elsif (defined $pid) { } elsif (defined $pid) {
# Child # Child
# This redirection is necessary because rsync sends # This redirection is necessary because rsync sends
# verbose output to STDOUT # verbose output to STDOUT
open(STDOUT, ">&STDERR") open(STDOUT, ">&STDERR")
or die "Could not redirect STDOUT to STDERR\n"; or die "Could not redirect STDOUT to STDERR\n";
exec(@command); exec(@command);
die "Could not exec '@command': $!\n"; die "Could not exec '@command': $!\n";
} else { } else {
die "Could not fork: $!\n"; die "Could not fork: $!\n";
} }
return($fh, $pid); return($fh, $pid);
} }
1; 1;

658
slack-dist/dist/slack vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,161 +1,161 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-getroles 180 2008-01-19 08:26:19Z alan $ # $Id: slack-getroles 180 2008-01-19 08:26:19Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# This script is in charge of copying files from the (possibly remote) # This script is in charge of copying files from the (possibly remote)
# master directory to a local cache, using rsync # master directory to a local cache, using rsync
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
my @rsync = ('rsync', my @rsync = ('rsync',
'--links', '--links',
'--times', '--times',
); );
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
sub sync_list (); sub sync_list ();
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir("/") chdir("/")
or die "Could not chdir /: $!"; or die "Could not chdir /: $!";
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options]"); my $usage = Slack::default_usage("$PROG [options]");
$usage .= <<EOF; $usage .= <<EOF;
--role-list --role-list
Role list location (can be relative to SOURCE) Role list location (can be relative to SOURCE)
--remote-role-list --remote-role-list
Role list is remote and should be copied down with rsync Role list is remote and should be copied down with rsync
(implied by certain forms of role list or SOURCE) (implied by certain forms of role list or SOURCE)
EOF EOF
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
command_line_options => [ command_line_options => [
'role-list=s', 'role-list=s',
'remote-role-list', 'remote-role-list',
], ],
required_options => [ qw(role-list hostname) ], required_options => [ qw(role-list hostname) ],
usage => $usage, usage => $usage,
); );
# Prepare for backups # Prepare for backups
if ($opt{backup} and $opt{'backup-dir'}) { if ($opt{backup} and $opt{'backup-dir'}) {
# Make sure backup directory exists # Make sure backup directory exists
unless (-d $opt{'backup-dir'}) { unless (-d $opt{'backup-dir'}) {
($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { mkpath($opt{'backup-dir'}); }; eval { mkpath($opt{'backup-dir'}); };
die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@;
} }
} }
push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}");
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
push @rsync, '--dry-run'; push @rsync, '--dry-run';
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'verbose'} > 1) { if ($opt{'verbose'} > 1) {
push @rsync, '--verbose'; push @rsync, '--verbose';
} }
# }}} # }}}
# See if role-list is actually relative to source, and pre-pend source # See if role-list is actually relative to source, and pre-pend source
# if need be. # if need be.
unless ($opt{'role-list'} =~ m#^/# or unless ($opt{'role-list'} =~ m#^/# or
$opt{'role-list'} =~ m#^\./# or $opt{'role-list'} =~ m#^\./# or
$opt{'role-list'} =~ m#^[\w@\.-]+:#) { $opt{'role-list'} =~ m#^[\w@\.-]+:#) {
if (not defined $opt{source}) { if (not defined $opt{source}) {
die "Relative path to role-list given, but source not defined!\n\n$usage\n"; die "Relative path to role-list given, but source not defined!\n\n$usage\n";
} }
$opt{'role-list'} = $opt{source} . '/' . $opt{'role-list'}; $opt{'role-list'} = $opt{source} . '/' . $opt{'role-list'};
} }
# auto-detect remote role list # auto-detect remote role list
if ($opt{'role-list'} =~ m#^[\w@\.-]+:#) { if ($opt{'role-list'} =~ m#^[\w@\.-]+:#) {
$opt{'remote-role-list'} = 1; $opt{'remote-role-list'} = 1;
} }
# Copy a remote list locally # Copy a remote list locally
if ($opt{'remote-role-list'}) { if ($opt{'remote-role-list'}) {
# We need a cache directory if the role list is not local # We need a cache directory if the role list is not local
if (not defined $opt{cache}) { if (not defined $opt{cache}) {
die "Remote path to role-list given, but cache not defined!\n\n$usage\n"; die "Remote path to role-list given, but cache not defined!\n\n$usage\n";
} }
# Look at source type, and add options if necessary # Look at source type, and add options if necessary
if ($opt{'rsh'} or $opt{'role-list'} =~ m/^[\w@\.-]+::/) { if ($opt{'rsh'} or $opt{'role-list'} =~ m/^[\w@\.-]+::/) {
# This is tunnelled rsync, and so needs an extra option # This is tunnelled rsync, and so needs an extra option
if ($opt{'rsh'}) { if ($opt{'rsh'}) {
push @rsync, '-e', $opt{'rsh'}; push @rsync, '-e', $opt{'rsh'};
} else { } else {
push @rsync, '-e', 'ssh'; push @rsync, '-e', 'ssh';
} }
} }
sync_list(); sync_list();
} }
# Read in the roles list # Read in the roles list
my @roles = (); my @roles = ();
my $host_found = 0; my $host_found = 0;
($opt{verbose} > 0) and print STDERR "$PROG: Reading '$opt{'role-list'}'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Reading '$opt{'role-list'}'\n";
open(ROLES, "<", $opt{'role-list'}) open(ROLES, "<", $opt{'role-list'})
or die "Could not open '$opt{'role-list'}' for reading: $!\n"; or die "Could not open '$opt{'role-list'}' for reading: $!\n";
while(<ROLES>) { while(<ROLES>) {
s/#.*//; # Strip comments s/#.*//; # Strip comments
chomp; chomp;
if (s/^$opt{hostname}:\s*//) { if (s/^$opt{hostname}:\s*//) {
$host_found++; $host_found++;
push @roles, split(); push @roles, split();
} }
} }
close(ROLES) close(ROLES)
or die "Could not close '$opt{'role-list'}': $!\n"; or die "Could not close '$opt{'role-list'}': $!\n";
if (not $host_found) { if (not $host_found) {
die "Host '$opt{hostname}' not found in '$opt{'role-list'}'!\n"; die "Host '$opt{hostname}' not found in '$opt{'role-list'}'!\n";
} }
print join("\n", @roles), "\n"; print join("\n", @roles), "\n";
exit 0; exit 0;
sub sync_list () { sub sync_list () {
my $source = $opt{'role-list'}; my $source = $opt{'role-list'};
my $destination = $opt{cache} . "/_role_list"; my $destination = $opt{cache} . "/_role_list";
unless (-d $opt{cache}) { unless (-d $opt{cache}) {
eval { mkpath($opt{cache}); }; eval { mkpath($opt{cache}); };
die "Could not mkpath '$opt{cache}': $@\n" if $@; die "Could not mkpath '$opt{cache}': $@\n" if $@;
} }
# All this to run an rsync command # All this to run an rsync command
my @command = (@rsync, $source, $destination); my @command = (@rsync, $source, $destination);
($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command'\n";
Slack::wrap_rsync(@command); Slack::wrap_rsync(@command);
$opt{'role-list'} = $destination; $opt{'role-list'} = $destination;
} }

View File

@ -1,149 +1,149 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-installfiles 180 2008-01-19 08:26:19Z alan $ # $Id: slack-installfiles 180 2008-01-19 08:26:19Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script is in charge of copying files from the local stage to the root # This script is in charge of copying files from the local stage to the root
# of the local filesystem # of the local filesystem
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
my @rsync = ('rsync', my @rsync = ('rsync',
'--relative', '--relative',
'--times', '--times',
'--perms', '--perms',
'--group', '--group',
'--owner', '--owner',
'--links', '--links',
'--devices', '--devices',
'--sparse', '--sparse',
'--no-implied-dirs', # SO GOOD! '--no-implied-dirs', # SO GOOD!
'--files-from=-', '--files-from=-',
'--from0', '--from0',
); );
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
sub install_files ($); sub install_files ($);
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir("/") chdir("/")
or die "Could not chdir /: $!"; or die "Could not chdir /: $!";
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]");
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
usage => $usage, usage => $usage,
required_options => [ qw(root stage) ], required_options => [ qw(root stage) ],
); );
# }}} # }}}
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
unless (-d $opt{root}) { unless (-d $opt{root}) {
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { eval {
mkpath($opt{root}); mkpath($opt{root});
# We have a tight umask, and a root of mode 0700 would be undesirable # We have a tight umask, and a root of mode 0700 would be undesirable
# in most cases. # in most cases.
chmod(0755, $opt{root}); chmod(0755, $opt{root});
}; };
die "Could not mkpath destination directory '$opt{root}': $@\n" if $@; die "Could not mkpath destination directory '$opt{root}': $@\n" if $@;
} }
warn "WARNING[$PROG]: Created destination directory '".$opt{root}."'\n"; warn "WARNING[$PROG]: Created destination directory '".$opt{root}."'\n";
} }
# Prepare for backups # Prepare for backups
if ($opt{backup} and $opt{'backup-dir'}) { if ($opt{backup} and $opt{'backup-dir'}) {
# Make sure backup directory exists # Make sure backup directory exists
unless (-d $opt{'backup-dir'}) { unless (-d $opt{'backup-dir'}) {
($opt{verbose} > 0) and print STDERR "$PROG: Creating backup directory '$opt{'backup-dir'}'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Creating backup directory '$opt{'backup-dir'}'\n";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { mkpath($opt{'backup-dir'}); }; eval { mkpath($opt{'backup-dir'}); };
die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@;
} }
} }
push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}");
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
push @rsync, '--dry-run'; push @rsync, '--dry-run';
} }
if ($opt{'verbose'} > 1) { if ($opt{'verbose'} > 1) {
push @rsync, '--verbose'; push @rsync, '--verbose';
} }
# copy over the new files # copy over the new files
for my $role (@ARGV) { for my $role (@ARGV) {
install_files($role); install_files($role);
} }
exit 0; exit 0;
# This subroutine takes care of actually installing the files for a role # This subroutine takes care of actually installing the files for a role
sub install_files ($) { sub install_files ($) {
my ($role) = @_; my ($role) = @_;
# final / is important for rsync # final / is important for rsync
my $source = $opt{stage} . "/roles/" . $role . "/files/"; my $source = $opt{stage} . "/roles/" . $role . "/files/";
my $destination = $opt{root} . "/"; my $destination = $opt{root} . "/";
my @command = (@rsync, $source, $destination); my @command = (@rsync, $source, $destination);
if (not -d $source) { if (not -d $source) {
($opt{verbose} > 0) and ($opt{verbose} > 0) and
print STDERR "$PROG: No files to install -- '$source' does not exist\n"; print STDERR "$PROG: No files to install -- '$source' does not exist\n";
return; return;
} }
# Try to give some sensible message here # Try to give some sensible message here
if ($opt{verbose} > 0) { if ($opt{verbose} > 0) {
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
print STDERR "$PROG: Dry-run syncing '$source' to '$destination'\n"; print STDERR "$PROG: Dry-run syncing '$source' to '$destination'\n";
} else { } else {
print STDERR "$PROG: Syncing '$source' to '$destination'\n"; print STDERR "$PROG: Syncing '$source' to '$destination'\n";
} }
} }
my ($fh) = Slack::wrap_rsync_fh(@command); my ($fh) = Slack::wrap_rsync_fh(@command);
select((select($fh), $|=1)[0]); # Turn on autoflush select((select($fh), $|=1)[0]); # Turn on autoflush
my $callback = sub { my $callback = sub {
my ($file) = @_; my ($file) = @_;
($file =~ s#^$source##) ($file =~ s#^$source##)
or die "sub failed: $source|$file"; or die "sub failed: $source|$file";
print $fh "$file\0"; print $fh "$file\0";
}; };
# This will print files to be synced to the $fh # This will print files to be synced to the $fh
Slack::find_files_to_install($source, $destination, $callback); Slack::find_files_to_install($source, $destination, $callback);
# Close fh, waitpid, and check return value # Close fh, waitpid, and check return value
unless (close($fh)) { unless (close($fh)) {
Slack::check_system_exit(@command); Slack::check_system_exit(@command);
} }
} }

View File

@ -1,146 +1,146 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-rolediff 125 2006-09-27 07:50:07Z alan $ # $Id: slack-rolediff 125 2006-09-27 07:50:07Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script provides a preview of scripts or files about to be installed. # This script provides a preview of scripts or files about to be installed.
# Basically, it calls diff -- its smarts are in knowing where things are. # Basically, it calls diff -- its smarts are in knowing where things are.
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use File::Find; use File::Find;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
my @diff = ('slack-diff', my @diff = ('slack-diff',
'-uN', '-uN',
); );
# directories to compare # directories to compare
my %subdir = ( my %subdir = (
files => 1, files => 1,
scripts => 1, scripts => 1,
); );
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
sub diff ($$;@); sub diff ($$;@);
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir("/") chdir("/")
or die "Could not chdir /: $!"; or die "Could not chdir /: $!";
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]");
$usage .= <<EOF; $usage .= <<EOF;
--subdir DIR --subdir DIR
Check this subdir only. Possible values for DIR are 'files' and Check this subdir only. Possible values for DIR are 'files' and
'scripts'. 'scripts'.
--diff PROG --diff PROG
Use this program to do diffs. [@diff] Use this program to do diffs. [@diff]
EOF EOF
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
command_line_options => [ command_line_options => [
'subdir=s', 'subdir=s',
'diff=s', 'diff=s',
], ],
usage => $usage, usage => $usage,
required_options => [ qw(cache stage root) ], required_options => [ qw(cache stage root) ],
); );
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
# We only allow certain values for this option # We only allow certain values for this option
if ($opt{subdir}) { if ($opt{subdir}) {
unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') {
die "--subdir option must be 'files' or 'scripts'\n\n$usage"; die "--subdir option must be 'files' or 'scripts'\n\n$usage";
} }
# Only do this subdir # Only do this subdir
%subdir = ( $opt{subdir} => 1 ); %subdir = ( $opt{subdir} => 1 );
} }
# Let people override our diff. Split on spaces so they can pass args. # Let people override our diff. Split on spaces so they can pass args.
if ($opt{diff}) { if ($opt{diff}) {
@diff = split(/\s+/, $opt{diff}); @diff = split(/\s+/, $opt{diff});
} }
# }}} # }}}
my $exit = 0; my $exit = 0;
# Do the diffs # Do the diffs
for my $full_role (@ARGV) { for my $full_role (@ARGV) {
# Split the full role (e.g. google.foogle.woogle) into components # Split the full role (e.g. google.foogle.woogle) into components
my @role = split(/\./, $full_role); my @role = split(/\./, $full_role);
if ($subdir{scripts}) { if ($subdir{scripts}) {
# Then we compare the cache vs the stage # Then we compare the cache vs the stage
my $old = $opt{stage} . "/roles/" . $full_role . "/scripts"; my $old = $opt{stage} . "/roles/" . $full_role . "/scripts";
my $new = $opt{cache} . "/roles/" . $role[0] . "/scripts"; my $new = $opt{cache} . "/roles/" . $role[0] . "/scripts";
# For scripts, we don't care so much about mode and owner (since those are # For scripts, we don't care so much about mode and owner (since those are
# inherited in the CACHE from the SOURCE), so --noperms. # inherited in the CACHE from the SOURCE), so --noperms.
$exit |= diff($old, $new, '--noperms'); $exit |= diff($old, $new, '--noperms');
} }
if ($subdir{files}) { if ($subdir{files}) {
# Then we compare the stage vs the root # Then we compare the stage vs the root
my $old = $opt{root}; my $old = $opt{root};
my $new = $opt{stage} . "/roles/" . $full_role . "/files"; my $new = $opt{stage} . "/roles/" . $full_role . "/files";
# For files, we don't care about files that exist in $old but not $new # For files, we don't care about files that exist in $old but not $new
$exit |= diff($old, $new, '--unidirectional-new-file'); $exit |= diff($old, $new, '--unidirectional-new-file');
} }
} }
exit $exit; exit $exit;
sub diff ($$;@) { sub diff ($$;@) {
my ($old, $new, @options) = @_; my ($old, $new, @options) = @_;
my @command = (@diff, @options); my @command = (@diff, @options);
# return if there's nothing to do # return if there's nothing to do
return 0 if (not -d $old and not -d $new); return 0 if (not -d $old and not -d $new);
($opt{verbose} > 0) and print STDERR "$PROG: Previewing with '@command'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Previewing with '@command'\n";
my $return = 0; my $return = 0;
my $callback = sub { my $callback = sub {
my ($new_file) = @_; my ($new_file) = @_;
my $old_file = $new_file; my $old_file = $new_file;
($old_file =~ s#^$new#$old#) ($old_file =~ s#^$new#$old#)
or die "sub failed: $new|$new_file"; or die "sub failed: $new|$new_file";
if (system(@command, $old_file, $new_file) != 0) { if (system(@command, $old_file, $new_file) != 0) {
$return |= Slack::get_system_exit(@command); $return |= Slack::get_system_exit(@command);
} }
}; };
# We have to use this function, rather than recursive mode for slack-diff, # We have to use this function, rather than recursive mode for slack-diff,
# because otherwise we'll print a bunch of bogus stuff about directories # because otherwise we'll print a bunch of bogus stuff about directories
# that exist in $ROOT and therefore aren't being synced. # that exist in $ROOT and therefore aren't being synced.
Slack::find_files_to_install($new, $old, $callback); Slack::find_files_to_install($new, $old, $callback);
return $return; return $return;
} }

View File

@ -1,111 +1,111 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ # $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script is in charge of running scripts out of the local stage # This script is in charge of running scripts out of the local stage
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use File::Find; use File::Find;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
# Export these options to the environment of the script # Export these options to the environment of the script
my @export_options = qw(root stage hostname verbose); my @export_options = qw(root stage hostname verbose);
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir('/') chdir('/')
or die "Could not chdir '/': $!"; or die "Could not chdir '/': $!";
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <action> <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <action> <role> [<role>...]");
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
usage => $usage, usage => $usage,
required_options => \@export_options, required_options => \@export_options,
); );
my $action = shift || die "No script to run!\n\n$usage"; my $action = shift || die "No script to run!\n\n$usage";
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
# }}} # }}}
# Start with a clean environment # Start with a clean environment
%ENV = ( %ENV = (
PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
); );
# Export certain variables to the environment. These are guaranteed to # Export certain variables to the environment. These are guaranteed to
# be set because we require them in get_options above. # be set because we require them in get_options above.
for my $option (@export_options) { for my $option (@export_options) {
my $env_var = $option; my $env_var = $option;
$env_var =~ tr/a-z-/A-Z_/; $env_var =~ tr/a-z-/A-Z_/;
$ENV{$env_var} = $opt{$option}; $ENV{$env_var} = $opt{$option};
} }
# We want to decrement the verbose value for the child if it's set. # We want to decrement the verbose value for the child if it's set.
$ENV{VERBOSE}-- if $ENV{VERBOSE}; $ENV{VERBOSE}-- if $ENV{VERBOSE};
# Run the script for each role given, if it exists and is executable # Run the script for each role given, if it exists and is executable
for my $role (@ARGV) { for my $role (@ARGV) {
my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; my $script_to_run = "$opt{stage}/roles/$role/scripts/$action";
unless (-x $script_to_run) { unless (-x $script_to_run) {
if (-e _) { if (-e _) {
# A helpful warning # A helpful warning
warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n";
} elsif ($opt{verbose} > 0) { } elsif ($opt{verbose} > 0) {
print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n";
} }
next; next;
} }
my $dir; my $dir;
if ($action eq 'fixfiles') { if ($action eq 'fixfiles') {
$dir = "$opt{stage}/roles/$role/files"; $dir = "$opt{stage}/roles/$role/files";
} else { } else {
$dir = "$opt{stage}/roles/$role/scripts"; $dir = "$opt{stage}/roles/$role/scripts";
} }
my @command = ($script_to_run , $role); my @command = ($script_to_run , $role);
# It's OK to chdir even if we're not going to run the script. # It's OK to chdir even if we're not going to run the script.
# Might as well see if it works. # Might as well see if it works.
chdir($dir) chdir($dir)
or die "Could not chdir '$dir': $!\n"; or die "Could not chdir '$dir': $!\n";
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
($opt{verbose} > 0) ($opt{verbose} > 0)
and print STDERR "$PROG: Not calling '@command' in '$dir' ". and print STDERR "$PROG: Not calling '@command' in '$dir' ".
"because --dry-run specified.\n"; "because --dry-run specified.\n";
} else { } else {
($opt{verbose} > 0) ($opt{verbose} > 0)
and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; and print STDERR "$PROG: Calling '@command' in '$dir'.\n";
unless (system("script /root/slackLog -a -f -c @command") == 0) { unless (system("script /root/slackLog -a -f -c @command") == 0) {
Slack::check_system_exit(@command); Slack::check_system_exit(@command);
} }
} }
chdir('/') chdir('/')
or die "Could not chdir '/': $!\n" or die "Could not chdir '/': $!\n"
} }
exit 0; exit 0;

View File

@ -1,111 +1,111 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ # $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script is in charge of running scripts out of the local stage # This script is in charge of running scripts out of the local stage
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use File::Find; use File::Find;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
# Export these options to the environment of the script # Export these options to the environment of the script
my @export_options = qw(root stage hostname verbose); my @export_options = qw(root stage hostname verbose);
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir('/') chdir('/')
or die "Could not chdir '/': $!"; or die "Could not chdir '/': $!";
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <action> <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <action> <role> [<role>...]");
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
usage => $usage, usage => $usage,
required_options => \@export_options, required_options => \@export_options,
); );
my $action = shift || die "No script to run!\n\n$usage"; my $action = shift || die "No script to run!\n\n$usage";
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
# }}} # }}}
# Start with a clean environment # Start with a clean environment
%ENV = ( %ENV = (
PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
); );
# Export certain variables to the environment. These are guaranteed to # Export certain variables to the environment. These are guaranteed to
# be set because we require them in get_options above. # be set because we require them in get_options above.
for my $option (@export_options) { for my $option (@export_options) {
my $env_var = $option; my $env_var = $option;
$env_var =~ tr/a-z-/A-Z_/; $env_var =~ tr/a-z-/A-Z_/;
$ENV{$env_var} = $opt{$option}; $ENV{$env_var} = $opt{$option};
} }
# We want to decrement the verbose value for the child if it's set. # We want to decrement the verbose value for the child if it's set.
$ENV{VERBOSE}-- if $ENV{VERBOSE}; $ENV{VERBOSE}-- if $ENV{VERBOSE};
# Run the script for each role given, if it exists and is executable # Run the script for each role given, if it exists and is executable
for my $role (@ARGV) { for my $role (@ARGV) {
my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; my $script_to_run = "$opt{stage}/roles/$role/scripts/$action";
unless (-x $script_to_run) { unless (-x $script_to_run) {
if (-e _) { if (-e _) {
# A helpful warning # A helpful warning
warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n";
} elsif ($opt{verbose} > 0) { } elsif ($opt{verbose} > 0) {
print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n";
} }
next; next;
} }
my $dir; my $dir;
if ($action eq 'fixfiles') { if ($action eq 'fixfiles') {
$dir = "$opt{stage}/roles/$role/files"; $dir = "$opt{stage}/roles/$role/files";
} else { } else {
$dir = "$opt{stage}/roles/$role/scripts"; $dir = "$opt{stage}/roles/$role/scripts";
} }
my @command = ($script_to_run, $role); my @command = ($script_to_run, $role);
# It's OK to chdir even if we're not going to run the script. # It's OK to chdir even if we're not going to run the script.
# Might as well see if it works. # Might as well see if it works.
chdir($dir) chdir($dir)
or die "Could not chdir '$dir': $!\n"; or die "Could not chdir '$dir': $!\n";
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
($opt{verbose} > 0) ($opt{verbose} > 0)
and print STDERR "$PROG: Not calling '@command' in '$dir' ". and print STDERR "$PROG: Not calling '@command' in '$dir' ".
"because --dry-run specified.\n"; "because --dry-run specified.\n";
} else { } else {
($opt{verbose} > 0) ($opt{verbose} > 0)
and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; and print STDERR "$PROG: Calling '@command' in '$dir'.\n";
unless (system(@command) == 0) { unless (system(@command) == 0) {
Slack::check_system_exit(@command); Slack::check_system_exit(@command);
} }
} }
chdir('/') chdir('/')
or die "Could not chdir '/': $!\n" or die "Could not chdir '/': $!\n"
} }
exit 0; exit 0;

View File

@ -1,278 +1,278 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-stage 180 2008-01-19 08:26:19Z alan $ # $Id: slack-stage 180 2008-01-19 08:26:19Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script is in charge of copying files from the local cache # This script is in charge of copying files from the local cache
# directory to the local stage, building a unified single tree onstage # directory to the local stage, building a unified single tree onstage
# from the multiple trees that are the role + subroles in the cache # from the multiple trees that are the role + subroles in the cache
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use File::Find; use File::Find;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
my @rsync = ('rsync', my @rsync = ('rsync',
'--recursive', '--recursive',
'--times', '--times',
'--ignore-times', '--ignore-times',
'--perms', '--perms',
'--sparse', '--sparse',
); );
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
sub check_stage (); sub check_stage ();
sub sync_role ($$@); sub sync_role ($$@);
sub apply_default_perms_to_role ($$); sub apply_default_perms_to_role ($$);
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir("/") chdir("/")
or die "Could not chdir /: $!"; or die "Could not chdir /: $!";
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]");
$usage .= <<EOF; $usage .= <<EOF;
--subdir DIR --subdir DIR
Sync this subdir only. Possible values for DIR are 'files' and Sync this subdir only. Possible values for DIR are 'files' and
'scripts'. 'scripts'.
EOF EOF
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
command_line_options => [ command_line_options => [
'subdir=s', 'subdir=s',
], ],
usage => $usage, usage => $usage,
required_options => [ qw(cache stage) ], required_options => [ qw(cache stage) ],
); );
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
# We only allow certain values for this option # We only allow certain values for this option
if ($opt{subdir}) { if ($opt{subdir}) {
unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') {
die "--subdir option must be 'files' or 'scripts'\n\n$usage"; die "--subdir option must be 'files' or 'scripts'\n\n$usage";
} }
} else { } else {
$opt{subdir} = ''; $opt{subdir} = '';
} }
# Prepare for backups # Prepare for backups
if ($opt{backup} and $opt{'backup-dir'}) { if ($opt{backup} and $opt{'backup-dir'}) {
# Make sure backup directory exists # Make sure backup directory exists
unless (-d $opt{'backup-dir'}) { unless (-d $opt{'backup-dir'}) {
($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { mkpath($opt{'backup-dir'}); }; eval { mkpath($opt{'backup-dir'}); };
die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@;
} }
} }
push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}");
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
push @rsync, '--dry-run'; push @rsync, '--dry-run';
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'verbose'} > 1) { if ($opt{'verbose'} > 1) {
push @rsync, '--verbose'; push @rsync, '--verbose';
} }
# }}} # }}}
# copy over the new files # copy over the new files
for my $full_role (@ARGV) { for my $full_role (@ARGV) {
# Split the full role (e.g. google.foogle.woogle) into components # Split the full role (e.g. google.foogle.woogle) into components
my @role_parts = split(/\./, $full_role); my @role_parts = split(/\./, $full_role);
die "Internal error: Expect at least one role part" if not @role_parts; die "Internal error: Expect at least one role part" if not @role_parts;
# Reassemble parts one at a time onto @role and sync as we go, # Reassemble parts one at a time onto @role and sync as we go,
# so we do "google", then "google.foogle", then "google.foogle.woogle" # so we do "google", then "google.foogle", then "google.foogle.woogle"
my @role = (); my @role = ();
# Make sure we've got the right perms before we copy stuff down # Make sure we've got the right perms before we copy stuff down
check_stage(); check_stage();
# For the base role, do both files and scripts. # For the base role, do both files and scripts.
push @role, shift @role_parts; push @role, shift @role_parts;
for my $subdir(qw(files scripts)) { for my $subdir(qw(files scripts)) {
if (not $opt{subdir} or $opt{subdir} eq $subdir) { if (not $opt{subdir} or $opt{subdir} eq $subdir) {
($opt{verbose} > 1) ($opt{verbose} > 1)
and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; and print STDERR "$PROG: Calling sync_role for $full_role, @role\n";
# @role here will have one element, so sync_role will use --delete # @role here will have one element, so sync_role will use --delete
sync_role($full_role, $subdir, @role) sync_role($full_role, $subdir, @role)
} }
} }
# For all subroles, just do the files. # For all subroles, just do the files.
# (If we wanted script subroles to work like files, we'd get rid of this # (If we wanted script subroles to work like files, we'd get rid of this
# distinction and simplify the code.) # distinction and simplify the code.)
if (not $opt{subdir} or $opt{subdir} eq 'files') { if (not $opt{subdir} or $opt{subdir} eq 'files') {
while (@role_parts) { while (@role_parts) {
push @role, shift @role_parts; push @role, shift @role_parts;
($opt{verbose} > 1) ($opt{verbose} > 1)
and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; and print STDERR "$PROG: Calling sync_role for $full_role, @role\n";
sync_role($full_role, 'files', @role); sync_role($full_role, 'files', @role);
} }
} }
for my $subdir (qw(files scripts)) { for my $subdir (qw(files scripts)) {
apply_default_perms_to_role($full_role, $subdir) apply_default_perms_to_role($full_role, $subdir)
if (not $opt{subdir} or $opt{subdir} eq $subdir); if (not $opt{subdir} or $opt{subdir} eq $subdir);
} }
} }
exit 0; exit 0;
# Make sure the stage directory exists and is mode 0700, to protect files # Make sure the stage directory exists and is mode 0700, to protect files
# underneath in transit # underneath in transit
sub check_stage () { sub check_stage () {
my $stage = $opt{stage} . "/roles"; my $stage = $opt{stage} . "/roles";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
if (not -d $stage) { if (not -d $stage) {
($opt{verbose} > 0) and print STDERR "$PROG: Creating '$stage'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$stage'\n";
eval { mkpath($stage); }; eval { mkpath($stage); };
die "Could not mkpath cache dir '$stage': $@\n" if $@; die "Could not mkpath cache dir '$stage': $@\n" if $@;
} }
($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$stage'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$stage'\n";
if ($> != 0) { if ($> != 0) {
warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; warn "WARNING[$PROG]: Not superuser; unable to chown files\n";
} else { } else {
chown(0, 0, $stage) chown(0, 0, $stage)
or die "Could not chown 0:0 '$stage': $!\n"; or die "Could not chown 0:0 '$stage': $!\n";
} }
chmod(0700, $stage) chmod(0700, $stage)
or die "Could not chmod 0700 '$stage': $!\n"; or die "Could not chmod 0700 '$stage': $!\n";
} }
} }
# Copy the files for a role from CACHE to STAGE # Copy the files for a role from CACHE to STAGE
sub sync_role ($$@) { sub sync_role ($$@) {
my ($full_role, $subdir, @role) = @_; my ($full_role, $subdir, @role) = @_;
my @this_rsync = @rsync; my @this_rsync = @rsync;
# If we were only given one role part, we're in the base role # If we were only given one role part, we're in the base role
my $in_base_role = (scalar @role == 1); my $in_base_role = (scalar @role == 1);
# For the base role, delete any files that don't exist in the cache. # For the base role, delete any files that don't exist in the cache.
# Not for the subrole (otherwise we'll delete all files not in # Not for the subrole (otherwise we'll delete all files not in
# the subrole, which may be most of them!) # the subrole, which may be most of them!)
if ($in_base_role) { if ($in_base_role) {
push @this_rsync, "--delete"; push @this_rsync, "--delete";
} }
# (a) => a/files # (a) => a/files
# (a,b,c) => a/files.b.c # (a,b,c) => a/files.b.c
my $src_path = $role[0].'/'.join(".", $subdir, @role[1 .. $#role]); my $src_path = $role[0].'/'.join(".", $subdir, @role[1 .. $#role]);
# This one's a little simpler: # This one's a little simpler:
my $dst_path = $full_role.'/'.$subdir; my $dst_path = $full_role.'/'.$subdir;
# final / is important for rsync # final / is important for rsync
my $source = $opt{cache} . "/roles/" . $src_path . "/"; my $source = $opt{cache} . "/roles/" . $src_path . "/";
my $destination = $opt{stage} . "/roles/" . $dst_path . "/"; my $destination = $opt{stage} . "/roles/" . $dst_path . "/";
if (not -d $destination and -d $source) { if (not -d $destination and -d $source) {
($opt{verbose} > 0) and print STDERR "$PROG: Creating '$destination'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$destination'\n";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { mkpath($destination); }; eval { mkpath($destination); };
die "Could not mkpath stage dir '$destination': $@\n" if $@; die "Could not mkpath stage dir '$destination': $@\n" if $@;
} }
} }
# We no longer require the source to exist # We no longer require the source to exist
if (not -d $source) { if (not -d $source) {
# but we need to remove the destination if the source # but we need to remove the destination if the source
# doesn't exist and we're in the base role # doesn't exist and we're in the base role
if ($in_base_role) { if ($in_base_role) {
rmtree($destination); rmtree($destination);
# rmtree() doesn't throw exceptions or give a return value useful # rmtree() doesn't throw exceptions or give a return value useful
# for detecting failure, so we just check after the fact. # for detecting failure, so we just check after the fact.
die "Could not rmtree '$destination' when '$source' missing\n" die "Could not rmtree '$destination' when '$source' missing\n"
if -e $destination; if -e $destination;
} }
# if we continue, rsync will fail because source is missing, # if we continue, rsync will fail because source is missing,
# so we don't. # so we don't.
return; return;
} }
# All this to run an rsync command # All this to run an rsync command
my @command = (@this_rsync, $source, $destination); my @command = (@this_rsync, $source, $destination);
($opt{verbose} > 0) and print STDERR "$PROG: Syncing $src_path with '@command'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Syncing $src_path with '@command'\n";
Slack::wrap_rsync(@command); Slack::wrap_rsync(@command);
} }
# This just takes the base role, and chowns/chmods everything under it to # This just takes the base role, and chowns/chmods everything under it to
# give it some sensible permissions. Basically, the only thing we preserve # give it some sensible permissions. Basically, the only thing we preserve
# about the original permissions is the executable bit, since that's the # about the original permissions is the executable bit, since that's the
# only thing source code controls systems like CVS, RCS, Perforce seem to # only thing source code controls systems like CVS, RCS, Perforce seem to
# preserve. # preserve.
sub apply_default_perms_to_role ($$) { sub apply_default_perms_to_role ($$) {
my ($role, $subdir) = @_; my ($role, $subdir) = @_;
my $destination = $opt{stage} . "/roles/" . $role; my $destination = $opt{stage} . "/roles/" . $role;
if ($subdir) { if ($subdir) {
$destination .= '/' . $subdir; $destination .= '/' . $subdir;
} }
# If the destination doesn't exist, it's probably because the source didn't # If the destination doesn't exist, it's probably because the source didn't
return if not -d $destination; return if not -d $destination;
($opt{verbose} > 0) and print STDERR "$PROG: Setting default perms on $destination\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Setting default perms on $destination\n";
if ($> != 0) { if ($> != 0) {
warn "WARNING[$PROG]: Not superuser; won't be able to chown files\n"; warn "WARNING[$PROG]: Not superuser; won't be able to chown files\n";
} }
# Use File::Find to recurse the directory # Use File::Find to recurse the directory
find({ find({
# The "wanted" subroutine is called for every directory entry # The "wanted" subroutine is called for every directory entry
wanted => sub { wanted => sub {
return if $opt{'dry-run'}; return if $opt{'dry-run'};
($opt{verbose} > 2) and print STDERR "$File::Find::name\n"; ($opt{verbose} > 2) and print STDERR "$File::Find::name\n";
if (-l) { if (-l) {
# symlinks shouldn't be in here, # symlinks shouldn't be in here,
# since we dereference when copying # since we dereference when copying
warn "WARNING[$PROG]: Skipping symlink at $File::Find::name: $!\n"; warn "WARNING[$PROG]: Skipping symlink at $File::Find::name: $!\n";
return; return;
} elsif (-f _) { # results of last stat saved in the "_" } elsif (-f _) { # results of last stat saved in the "_"
if (-x _) { if (-x _) {
chmod 0555, $_ chmod 0555, $_
or die "Could not chmod 0555 $File::Find::name: $!"; or die "Could not chmod 0555 $File::Find::name: $!";
} else { } else {
chmod 0444, $_ chmod 0444, $_
or die "Could not chmod 0444 $File::Find::name: $!"; or die "Could not chmod 0444 $File::Find::name: $!";
} }
} elsif (-d _) { } elsif (-d _) {
chmod 0755, $_ chmod 0755, $_
or die "Could not chmod 0755 $File::Find::name: $!"; or die "Could not chmod 0755 $File::Find::name: $!";
} else { } else {
warn "WARNING[$PROG]: Unknown file type at $File::Find::name: $!\n"; warn "WARNING[$PROG]: Unknown file type at $File::Find::name: $!\n";
} }
return if $> != 0; # skip chowning if not superuser return if $> != 0; # skip chowning if not superuser
chown 0, 0, $_ chown 0, 0, $_
or die "Could not chown 0:0 $File::Find::name: $!"; or die "Could not chown 0:0 $File::Find::name: $!";
}, },
# end of wanted function # end of wanted function
}, },
# way down here, we have the directory to traverse with File::Find # way down here, we have the directory to traverse with File::Find
$destination, $destination,
); );
} }

View File

@ -1,169 +1,169 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
# $Id: slack-sync 180 2008-01-19 08:26:19Z alan $ # $Id: slack-sync 180 2008-01-19 08:26:19Z alan $
# vim:sw=2 # vim:sw=2
# vim600:fdm=marker # vim600:fdm=marker
# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> # Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net>
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
# See the file COPYING for details. # See the file COPYING for details.
# #
# This script is in charge of copying files from the (possibly remote) # This script is in charge of copying files from the (possibly remote)
# master directory to a local cache, using rsync # master directory to a local cache, using rsync
require 5.006; require 5.006;
use warnings FATAL => qw(all); use warnings FATAL => qw(all);
use strict; use strict;
use sigtrap qw(die untrapped normal-signals use sigtrap qw(die untrapped normal-signals
stack-trace any error-signals); stack-trace any error-signals);
use File::Path; use File::Path;
use constant LIB_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack';
use lib LIB_DIR; use lib LIB_DIR;
use Slack; use Slack;
my @rsync = ('rsync', my @rsync = ('rsync',
'--cvs-exclude', '--cvs-exclude',
'--recursive', '--recursive',
'--links', '--links',
'--copy-links', '--copy-links',
'--times', '--times',
'--perms', '--perms',
'--sparse', '--sparse',
'--delete', '--delete',
'--files-from=-', '--files-from=-',
'--from0', '--from0',
); );
(my $PROG = $0) =~ s#.*/##; (my $PROG = $0) =~ s#.*/##;
sub check_cache ($); sub check_cache ($);
sub rsync_source ($$@); sub rsync_source ($$@);
######################################## ########################################
# Environment # Environment
# Helpful prefix to die messages # Helpful prefix to die messages
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
# Set a reasonable umask # Set a reasonable umask
umask 077; umask 077;
# Get out of wherever (possibly NFS-mounted) we were # Get out of wherever (possibly NFS-mounted) we were
chdir("/") chdir("/")
or die "Could not chdir /: $!"; or die "Could not chdir /: $!";
# Autoflush on STDERR # Autoflush on STDERR
select((select(STDERR), $|=1)[0]); select((select(STDERR), $|=1)[0]);
######################################## ########################################
# Config and option parsing {{{ # Config and option parsing {{{
my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]"); my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]");
# Option defaults # Option defaults
my %opt = (); my %opt = ();
Slack::get_options( Slack::get_options(
opthash => \%opt, opthash => \%opt,
usage => $usage, usage => $usage,
required_options => [ qw(source cache) ], required_options => [ qw(source cache) ],
); );
# Arguments are required # Arguments are required
die "No roles given!\n\n$usage" unless @ARGV; die "No roles given!\n\n$usage" unless @ARGV;
# Prepare for backups # Prepare for backups
if ($opt{backup} and $opt{'backup-dir'}) { if ($opt{backup} and $opt{'backup-dir'}) {
# Make sure backup directory exists # Make sure backup directory exists
unless (-d $opt{'backup-dir'}) { unless (-d $opt{'backup-dir'}) {
($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n";
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
eval { mkpath($opt{'backup-dir'}); }; eval { mkpath($opt{'backup-dir'}); };
die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@;
} }
} }
push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}");
} }
# Look at source type, and add options if necessary # Look at source type, and add options if necessary
if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) { if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) {
# This is tunnelled rsync, and so needs an extra option # This is tunnelled rsync, and so needs an extra option
if ($opt{'rsh'}) { if ($opt{'rsh'}) {
push @rsync, '-e', $opt{'rsh'}; push @rsync, '-e', $opt{'rsh'};
} else { } else {
push @rsync, '-e', 'ssh'; push @rsync, '-e', 'ssh';
} }
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'dry-run'}) { if ($opt{'dry-run'}) {
push @rsync, '--dry-run'; push @rsync, '--dry-run';
} }
# Pass options along to rsync # Pass options along to rsync
if ($opt{'verbose'} > 1) { if ($opt{'verbose'} > 1) {
push @rsync, '--verbose'; push @rsync, '--verbose';
} }
# }}} # }}}
my @roles = (); my @roles = ();
{ {
# This hash is just to avoid calling rsync twice if two subroles are # This hash is just to avoid calling rsync twice if two subroles are
# installed. We only care since it's remote, and therefore slow. # installed. We only care since it's remote, and therefore slow.
my %roles_to_sync = (); my %roles_to_sync = ();
# copy over the new files # copy over the new files
for my $full_role (@ARGV) { for my $full_role (@ARGV) {
# Get the first element of the role name (the base role) # Get the first element of the role name (the base role)
# e.g., from "google.foogle.woogle", get "google" # e.g., from "google.foogle.woogle", get "google"
my $base_role = (split /\./, $full_role, 2)[0]; my $base_role = (split /\./, $full_role, 2)[0];
$roles_to_sync{$base_role} = 1; $roles_to_sync{$base_role} = 1;
} }
@roles = keys %roles_to_sync; @roles = keys %roles_to_sync;
} }
my $cache = $opt{cache} . "/roles/"; my $cache = $opt{cache} . "/roles/";
# Make sure we've got the right perms before we copy stuff down # Make sure we've got the right perms before we copy stuff down
check_cache($cache); check_cache($cache);
rsync_source( rsync_source(
$opt{source} . '/roles/', $opt{source} . '/roles/',
$cache, $cache,
@roles, @roles,
); );
exit 0; exit 0;
# Make sure the cache directory exists and is mode 0700, to protect files # Make sure the cache directory exists and is mode 0700, to protect files
# underneath in transit # underneath in transit
sub check_cache ($) { sub check_cache ($) {
my ($cache) = @_; my ($cache) = @_;
if (not $opt{'dry-run'}) { if (not $opt{'dry-run'}) {
if (not -d $cache) { if (not -d $cache) {
($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n";
eval { mkpath($cache); }; eval { mkpath($cache); };
die "Could not mkpath cache dir '$cache': $@\n" if $@; die "Could not mkpath cache dir '$cache': $@\n" if $@;
} }
($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n"; ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n";
if ($> != 0) { if ($> != 0) {
warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; warn "WARNING[$PROG]: Not superuser; unable to chown files\n";
} else { } else {
chown(0, 0, $cache) chown(0, 0, $cache)
or die "Could not chown 0:0 '$cache': $!\n"; or die "Could not chown 0:0 '$cache': $!\n";
} }
chmod(0700, $cache) chmod(0700, $cache)
or die "Could not chmod 0700 '$cache': $!\n"; or die "Could not chmod 0700 '$cache': $!\n";
} }
} }
# Pull down roles from an rsync source # Pull down roles from an rsync source
sub rsync_source($$@) { sub rsync_source($$@) {
my ($source, $destination, @roles) = @_; my ($source, $destination, @roles) = @_;
my @command = (@rsync, $source, $destination); my @command = (@rsync, $source, $destination);
($opt{verbose} > 0) ($opt{verbose} > 0)
and print STDERR "$PROG: Syncing cache with '@command'\n"; and print STDERR "$PROG: Syncing cache with '@command'\n";
my ($fh) = Slack::wrap_rsync_fh(@command); my ($fh) = Slack::wrap_rsync_fh(@command);
# Shove the roles down its throat # Shove the roles down its throat
print $fh join("\0", @roles), "\0"; print $fh join("\0", @roles), "\0";
# Close fh, waitpid, and check return value # Close fh, waitpid, and check return value
unless (close($fh)) { unless (close($fh)) {
Slack::check_system_exit(@command); Slack::check_system_exit(@command);
} }
} }