slack-0.15.2/0000755000076400001440000000000011002764210011523 5ustar alanusersslack-0.15.2/doc/0000755000076400001440000000000011002764205012274 5ustar alanusersslack-0.15.2/doc/fixfiles-and-dirs.txt0000644000076400001440000001324311002764205016350 0ustar alanusersTODO: clean up this email into a more general doc. In the meantime, though, here you go... Date: Mon, 30 Apr 2007 13:08:06 -0700 From: Alan Sundell To: Marc MERLIN Subject: Re: Slack fixfiles question On Fri, Apr 27, 2007 at 07:19:22PM -0700, Marc MERLIN wrote: > Howdy, > > I was reading > http://XXXX > in the hope of finding some details on whether fixfiles is allowed to > change directory perms in the hopes that slack-installfiles would them. > > In our case, we have this: > opt/httpd svn svn-team 2775 > opt/httpd/conf svn svn-team 2775 > opt/httpd/conf/ssl.crt svn svn-team 2775 > opt/httpd/conf/ssl.key root svn-team 2550 > > Our fixfiles does set all the perms right, as seen in > /var/lib/slack/stage/roles/svn/files/opt/httpd/conf > but /usr/lib/slack/slack-installfiles takes a list of files to sync on > stdin and apparently that list doesn't contain the directories > themselves > > It looks like for now we'll have to forget about fixfiles and just set > directory perms in postinstall > > Correct? Yep. fixfiles is mainly for files. Of course, it's not quite that simple. Queue long explanation... > If so, would you take an RFE to improve fixfiles to also sync directory > perms? Basically, the problem is that slack lets you install files by just putting them in a tree, rather than also having to describe them in some meta-data config file (like rpm or bundle would, or like your table above). You have to create all the parent directories in the tree, but slack has no idea what level of the parent directories you care about. In your case, for instance, you care about /opt/httpd, but not /opt -- to slack, however, they are all just directories. Installfiles does make kind of an educated guess -- if it has to create a directory because it doesn't exist on the target, it has to pick some permissions for the new dir, so it just copies the permissions from the stage. Otherwise, though, it leaves directories alone. Naturally, then, this only affects things the first time on missing directories, so if you want to be idempotent or change existing dirs, you've got to throw it in one of the other scripts. For more on why this is, consider the example of an admin who wants to install /usr/local/sbin/somescript ... Inside the stage, slack will make directories have the default perms of 0755 root:root. But you have to ask yourself: does the role author really want to change the perms on: / /usr /usr/local /usr/local/sbin to 755 root:root every time he installs this file? Probably not. Probably, he just wants to install the file, and doesn't care what some other person has done to those dirs. On Debian, for instance, /usr/local looks like this: drwxr-sr-x 11 root staff 1024 Jan 22 01:09 /usr/local Or, in some cases, a parent directory may be a symlink, and we wouldn't want to replace that with a directory (which is what rsync would do if we told it to sync that directory). And things get even worse if you have multiple roles fighting with each other. If one role cared about the perms on /usr/local and the other didn't, which one would win, and for how long? This type of conflict is more likely than a file conflict, since e.g. every role with files contains /. So, in general, the parent dirs on a machine may have all sorts of peculiar circumstanaces that you don't particularly care about, so one of the big features of slack is that it doesn't constantly muck with your parent dirs. That's why I rewrote my old system into slack in the first place after rsync introduced its --no-implied-dirs option. The manpage for rsync talk about similar things in the discussion of that option. Obviously, other systems don't have this problem, if they have you explicitly document everything you care about and what permissions it has (like you did above), they can set the perms on the directory every time. And, of course, they need to keep track of which roles care about which perms, so they can avoid conflicts, and... oh, no, we've re-invented a packaging system ;) slack, in exchange for your not having to have a metadata config in most cases, makes you handle this stuff in the pre- or postinstall. I suppose you can look at the chmod/chown statements as a form of metadata config, but at least you don't need them most of the time, which suits slack's lazy attitude. One last thing: why do we treat files specially? It's because they are special, in that they contain data. And chmodding becomes most important when you want to protect something, like your SSL key. You want to install that atomically with the right perms. If you fixed the perms in postinstall, people could read your key in the interval. Directories, on the other hand, don't contain data -- they contain things that contain data. Moreover, it's not really feasible to atomically install a whole directory plus its contents over an existing one (since you can't unlink dirs, you can't rename over them either). Even if you could do this, you'd have to deal with the stuff in the old dir you didn't want to mess with (so you'd have to use hard links, which probably means hard-linked subdirectories, which is another problem). So, the installation of an individual file we want to be atomic, so we provide a way of making sure that happens with the right perms, but the entire operation of installing the role can't be atomic, because the OS won't let us do that, so it's sufficient to use preinstall and postinstall to muck with directories, because we can't eliminate the race there. If the OS ever gives us some kind of multiple-operation atomic transaction support for filesystems, then it'd make sense to think about dirs in fixfiles, because it would buy us something. --Alan slack-0.15.2/doc/slack.conf.50000644000076400001440000000535111002764205014407 0ustar alanusers.\" $Header$ .\" vim:tw=72:filetype=nroff .\" .\" manpage for slack.conf .\" .TH slack.conf 5 2005-05-23 "File formats" .SH NAME slack.conf \- configuration file for slack .SH DESCRIPTION The file .I /etc/slack.conf contains configuration information for .BR slack (8) and its backends. It should contain one keyword-value pair per line, separated by an '=' sign. Keywords must consist solely of capital letters and underscores. Values may take any appropriate format, but must not begin with a space. Comments start with '#', and all text from the '#' to the end of a line is ignored. Trailing whitespace on lines is ignored. Empty lines or lines consisting of only whitespace and comments are ignored. Valid keywords are: .TP \fBSOURCE\fP The master source for slack roles. It can be in one of four forms: .IP .RS .IP \(bu \fB/path/to/dir\fP Use a local directory. .IP \(bu \fBsomehost:/path/to/dir\fP Use given directory on a remote host via rsync over SSH. .IP \(bu \fBrsync://somehost/module\fP Use module on a remote rsyncd server (directly over the network). .IP \(bu \fBsomehost::module\fP Use the rsync daemon protocol over SSH to the given host. See \(lqUSING RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION\(rq in .BR rsync (1) .RE .IP All forms of SOURCE are passed directly to rsync, so you can do things like add \(lquser@\(rq before the host on any remote forms. For more about what rsync can do, see its manual page, of course. .IP For the last form, however, we do a little magic. rsync treats the last two forms equivalently, so we overload the last form by automatically passing \(lq-e ssh\(rq to rsync when we see it. This hack lets us tell slack to use this nice feature of rsync just using the SOURCE config option. .RE .TP \fBROOT\fP The root filesystem into which to install slack roles. Usually .RI ' / '. .TP \fBROLE_LIST\fP The location of the role list, which lists the roles to be installed by default on each host. This can be a path relative to the source, or can be an entirely separate location if it starts with a slash or a hostname (optionally preceeded by user@). .TP \fBCACHE\fP A local cache directory, used as a local mirror of the SOURCE. .TP \fBSTAGE\fP A local staging directory, used as an intermediate stage when installing files. .TP \fBBACKUP_DIR\fP A directory in which to keep dated backups for rollbacks. .SH EXAMPLE A typical file might look like this: # slack.conf configuration file SOURCE=slack-master:/slack # source is on a remote # host named "slack-master" ROLE_LIST=slack-master:/roles.conf ROOT=/ CACHE=/var/cache/slack STAGE=/var/lib/slack/stage BACKUP_DIR=/var/lib/slack/backups .SH FILES .I /etc/slack.conf .SH SEE ALSO .BR slack (8), .BR rsync (1) slack-0.15.2/doc/slack-intro0000644000076400001440000001076411002764205014455 0ustar alanusersWhat slack does (overview): slack copies a set files down from a central repository onto your local machine, and it lets you write some scripts to run before and after doing installing the files. slack doesn't care about file conflicts, and it can't uninstall things. It places no limitations on your scripts (it just exec()s them, so technically they could be binaries), so it has absolutely no idea what they will do (I haven't invented the parallel universe simulator yet to see how the world will react to the actions of your scripts). Each set of files and scripts is called a "role". Several roles can be installed on one machine. You can also have "subroles" inside a role, which sort of get overlaid on top of the base role, but more about that later. The roles all live together in the "slack repository." Generally, you want to put this on some central server(s), backed by some sort of source control repository, but slack just needs a directory somewhere. You can configure which roles live on a particular machine using a simple text file (which can live in the repository, or not). Or you could write another backend and get your roles from DNS or LDAP or something (I've never felt the need). slack has a fair number of options to change its behavior, which can be specified in an optional configuration file, /etc/slack.conf, on the host running slack, or on the command line (which takes precedence over the config file). The location of the config file can also be specified on the command line. The software itself is split into a frontend (slack), and a whole mess of backends (slack-*). The job of the frontend is to interpret command line options and orchestrate the various backends. The backends are all separate executables, so if you wish you can replace them or invoke them in your own interesting ways. Options/config: Slack takes long options, like "--source". Sometimes they have abbreviations, like "-s". Each of the options can be specified in the config file by upcasing them and specifying KEY=value pairs. variables. For example the command line: slack --source /var/lib/slack-repository would become this line in slack.conf: SOURCE=/var/lib/slack-repository and then you could just type "slack" on the command line. Repository: You probably want to put your repository in some sort of source control system (e.g. CVS). slack doesn't actually require that, though. The repository is the thing specified by the SOURCE config option. slack tools in $SOURCE/roles/ for roles. For example, it looks for the role "nss" (and all its subroles) in the directory $SOURCE/roles/nss The path of the ROLE_LIST file is relative to the repository. So, if you do this: ROLE_LIST=etc/roles.conf SOURCE=/var/lib/slack-repository then slack-getroles will look at /var/lib/slack-repository/etc/roles.conf to get its role list. Role layout: Inside a role, you might have some files. These are laid out in the "files" subdirectory of the role, laid out in a tree just like they will be installed on your host. So, for example, if you have the file: $SOURCE/roles/nss/files/etc/nsswitch.conf then the nss role will replace /etc/nsswitch.conf on your host with that file. You can also have scripts. If you have a: $SOURCE/roles/nss/scripts/postinstall it will get called after the files are installed. You can also have a "preinstall." Both are optional. slack just tries to call exec() on them if they exist, so they can do whatever you want or be in any language you want (in other words, they don't need to be scripts, per se). There is a third script, "fixfiles", which will be called with a working directory of the files directory, so that you can chown or chmod or do other things to your files *before* they get installed. Note that paths need to be relative to the files directory, then (slack isn't going to translate them for you, since that would be impossible to do reliably, nor is it going to chroot you, because then we'd have to import all the things you could want to do into the chroot). So, for example, to chmod sudoers before installing: #!/bin/sh chmod 440 etc/sudoers Normally, though, you shouldn't need fixfiles. Before executing the script, slack cleans out the environment. It sets a default PATH and sets some env vars of its own (like SOURCE and ROOT and VERBOSE). It also sets the umask to 077. It does all this so that the operation of the script won't depend on the person running slack, so hopefully you won't have a case where something works for Alice but not for Bob. slack-0.15.2/doc/slack.80000644000076400001440000001131211002764205013460 0ustar alanusers.\" $Header$ .\" vim:tw=72:filetype=nroff .\" .\" manpage for slack.conf .\" .TH slack 8 2004-10-22 "Administrative commands" .SH NAME slack \- Sysadmin's lazy autoconfiguration kit .SH SYNOPSIS \fBslack\fR [\fIoption ...\fR] [\fIrole ...\fR] .SH DESCRIPTION slack is a master command which coordinates the activities of its backends, which variously: .IP \(bu determine the list of roles to be installed on this server .IP \(bu create a local cached copy of the role files from the central repository .IP \(bu merge file trees from subroles into a single, unified tree .IP \(bu install files onto the local filesystem .IP \(bu run scripts before and after installation .PP Options you give to slack will be generally passed along to the backends where relevant. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR Print a usage statement. .TP \fB\-\-version\fR Print the version and exit. .TP \fB\-v\fR, \fB\-\-verbose\fR Increase verbosity. Can be specified multiple times. .TP \fB\-\-quiet\fR Don't be verbose (Overrides previous uses of --verbose). .TP \fB\-C\fR, \fB\-\-config FILE\fR Use the specfied FILE for configuration instead of the default, .IR /etc/slack.conf . .TP \fB\-s\fR, \fB\-\-source DIR\fR Source directory for slack files .TP \fB\-e\fR, \fB\-\-rsh COMMAND\fR Remote shell for rsync .TP \fB\-c\fR, \fB\-\-cache DIR\fR Local cache directory for slack files .TP \fB\-t\fR, \fB\-\-stage DIR\fR Local staging directory for slack files .TP \fB\-r\fR, \fB\-\-root DIR\fR Root destination for slack files .TP \fB\-\-no\-sync\fR Skip the slack-sync step (useful if you're pushing stuff into the CACHE outside slack). .TP \fB\-\-no\-files\fR Don't install any files in ROOT, but tell rsync to print what it would do. .TP \fB\-\-no\-scripts\fR Don't run scripts .TP \fB\-n\fR, \fB\-\-dry\-run\fR Same as \-\-no\-files \-\-no\-scripts (CACHE, STAGE will still be updated) .TP \fB\-\-role\-list\fR Role list for .BR slack-getroles (8). .TP \fB\-b\fR, \fB\-\-backup\fR Make backups of existing files in ROOT that are overwritten. This option defaults to on if it is not set to 0 in a config file or disabled with --nobackup on the command line. .TP \fB\-\-backup\-dir\fR Put backups from the .B \-\-backup option into this directory. .TP \fB\-H\fR, \fB\-\-hostname HOST\fR Pretend to be running on HOST, instead of the name given by gethostname(2). .TP \fB\-\-preview MODE\fR Do a diff of scripts and files before running them. MODE can be one of 'simple' or 'prompt' (See PREVIEW MODES, below). .TP \fB\-\-diff PROG\fR Use this diff program for previews. .TP \fB\-\-sleep TIME\fR Randomly sleep between 1 and TIME seconds before starting operations. Useful in crontabs. .SH PREVIEW MODES .PP Preview functionality is new in slack 0.14.0. I haven't quite worked out how things will work, so this usage is somewhat subject to change in future versions. I thought I would try it this way and see how people like it. .PP In 'simple' mode, after syncing and staging the files directory, slack will present a diff of the files and scripts. In this mode, slack will not run the preinstall or fixfiles scripts, and because of this, it may provide some false output about permissions changes to files. .PP In 'prompt' mode, after syncing and staging the files directory, slack will diff the script directory. If there are differences, slack will present them to you and ask you if you want to continue. If you say no, it will exit. If you say yes, it will stage the scripts directory, run the preinstall and fixfiles scripts, and then diff the files in the stage with those in the root. If there are differences, slack will present them to you and ask you if you want to continue. If you say no, it will exit. If you say yes, it will install the files and run the postinstall script. .PP So, the 'simple' mode is easy to use, and will be accurate if you don't use fixfiles. The 'prompt' mode will be accurate if you use fixfiles, but requires some interaction. .PP Why can't we just have one mode that works with fixfiles and requires no interaction? Well, that would require slack to understand what your free-form fixfiles executable was going to do, which would either require some kind of universe simulator or would require you to write your fixfiles in a less free-form way, which would make slack less like slack. .SH EXAMPLES .PP To install all the roles configured in the role list for a server: .RS slack .RE .PP To install a specific role: .RS slack .I rolename .RE .PP To test a new role before checking in the changes: .RS slack --source .IR user @ workstation :/home/ user /.../slack .I rolename .RE .PP To avoid killing your master server when calling from cron: .RS slack --sleep 3600 .RE .SH FILES .I /etc/slack.conf .SH SEE ALSO .BR slack.conf (5), .BR rsync (1) slack-0.15.2/doc/slack-diff.10000644000076400001440000000331311002764205014361 0ustar alanusers.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.27. .TH DIFF "1" "April 2002" "diffutils 2.8.1" "User Commands" .SH NAME slack-diff \- compare file contents, modes, etc .SH SYNOPSIS .B slack-diff [\fIOPTION\fR]... \fIFILE1\fR \fIFILE2\fR .SH DESCRIPTION A wrapper for diff that displays file modes and other metadata changes. .TP \fB\-u\fR \fB\-U\fR NUM \fB\-\-unified\fR[=\fINUM\fR] Tell .BR diff (1) to use unified output format. .TP \fB\-\-diff\fR \fIPROG\fR Use this program for diffing, instead of diff. .TP \fB\-\-fakediff\fR Make a fake diff for file modes and other things that are not file contents. Default is on, can be disabled with \-\-nofakediff. .TP \fB\-r\fR \fB\-\-recursive\fR Recursively compare any subdirectories found. .TP \fB\-N\fR \fB\-\-new\-file\fR Treat missing files as empty. Default is on, can be disabled with \-\-nonew\-file. .TP \fB\-\-unidirectional\-new\-file\fR Treat only missing files in the first directory as empty. .TP \fB\-\-from\-file\fR Treat arguments as a list of files from which to read filenames to compare, two lines at a time. .TP \fB\-0\fR \fB\-\-null\fR Use NULLs instead of newlines as the separator in \-\-from\-file mode. .TP \fB\-\-devnullhack\fR You have a version of diff that can't deal with \-N when not in recursive mode, so we need to feed it /dev/null instead of the missing file. Default is on, can be disabled with \-\-nodevnullhack. .TP \fB\-\-version\fR Output version info. .TP \fB\-\-help\fR Output this help. .PP FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'. If \fB\-\-from\-file\fR or \fB\-\-to\-file\fR is given, there are no restrictions on FILES. If a FILE is `-', read standard input. .SH "SEE ALSO" .BR diff (1) slack-0.15.2/doc/Makefile0000644000076400001440000000075111002764205013737 0ustar alanusers# Makefile for slack/doc # $Id: Makefile 96 2006-03-21 07:18:13Z alan $ include ../Makefile.common all: install: install-man install-man: all $(MKDIR) $(DESTDIR)$(mandir)/man1 $(INSTALL) slack-diff.1 $(DESTDIR)$(mandir)/man1 $(MKDIR) $(DESTDIR)$(mandir)/man5 $(INSTALL) slack.conf.5 $(DESTDIR)$(mandir)/man5 $(MKDIR) $(DESTDIR)$(mandir)/man8 $(INSTALL) slack.8 $(DESTDIR)$(mandir)/man8 install-bin install-conf install-lib: all clean: realclean: clean distclean: clean test: slack-0.15.2/FAQ0000644000076400001440000000523311002764207012066 0ustar alanusersWhat's so great about slack, then? Nothing, really. The code is kind of a mess, and I'm sure you can come up with better :) People seem to like it, though, and if pressed, I'd say that it's pretty simple to use, and probably close to what you want to be doing, and that it doesn't tend to force some weird strict philosophy on you. Also it probably gets major points for existing. That's it? Pretty much. Are you associated with the Church of the Subgenius? I don't *think* so... What does the name stand for? "Sysadmins' Lazy Auto-Configuration Kit". Yes, this was a "backronym" -- I named it "slack" on a dare (see CREDITS). What is slack written in? Perl Why isn't it written in [insert language]? I wanted to use an interpreted language that I didn't have to convince sysadmins to install on their machines, and shell would have ended up looking even worse than this Perl. Sometimes I do regret it, though. :/ Shouldn't people be using (cfengine/conf packages/whatever)? Probably. Feel free to try to convince them, but I'd like to stay out of any config-management wars. Well, then how does slack compare to other config management systems? slack is both less capable and less complicated than most other config management systems out there. Most people can figure out how to use it with little documentation (not that we shouldn't write more...), but all it can do is move files around and run some scripts. However, that may turn out to be all you need. Why did you write slack when all these other systems existed? slack was designed for environments with no pre-existing config management, and in which there was resistance among the admins to get over the up-front costs of learning and implementing other config management systems. We saw it as kind of a gateway to get lazy people into config management and automated builds, so that they could later "trade up" to more sophisticated systems. Now, however, it seems that a lot of people don't need anything more complicated than slack. That's fine by me; people should use what works best for them. How do I set up slack to use rsyncd over ssh? 1) Generate a public/private keypair for SSH 2) Put the private key on clients in /root/.ssh/slack-key 3) Add a stanza like this on clients to /root/.ssh/config: Host slack-master User slack IdentityFile /root/.ssh/slack-key 4) Pick a master server, make a CNAME "slack-master" pointed at it 5) Install the slack-master package on that server 6) Put the public key on that server in ~slack/.ssh/authorized_keys (see the example in /usr/share/doc/slack-master) slack-0.15.2/ChangeLog0000644000076400001440000002372111002764207013310 0ustar alanusers2008-04-20 Alan Sundell * Version 0.15.2 2008-04-18 Alan Sundell * Makefile: fix 'dist' target to work with svn 2008-03-02 Alan Sundell * Makefile: remove debian directory from tarball, to make Andrew Pollock happy; make a separate tarball for the (suggested) debian directory, to make me happy until his debian patch becomes available * src/Slack.pm, test/01_module.t: apply Steve Atwell's get_system_exit patch. This uses the POSIX macros for exit statues, makes the tests work on OpenBSD, and removes the bit that checks core dumps, since it was kind of silly and awkward to test, anyway. * src/Makefile: another OpenBSD patch from Steve Atwell -- don't specify -p with -d when using install (doesn't make sense, anyway) 2008-01-19 Alan Sundell * Version 0.15.1 2008-01-18 Alan Sundell * Makefile: minor changes to 'dist' target * src/slack-sync: use --files-from=- to do the whole sync at once. On my systems for a 15-role sync this cuts the time from 4-5 sec to 0.4-0.5 sec. * test/*: unittest updates, change handling of temp dir * Slack.pm: wrap_rsync() and wrap_rsync_fh() replace some duplicated code in the backends. 2008-01-14 Alan Sundell * Version 0.15.0 * src/Makefile: install slack.conf without the execute bit (thanks to Andrew Pollock) * Makefile, debian/*: pull in a bunch of changes from Andrew Pollock's debian patch (perhaps to his frustration), with a few changes to preserve permissions on /var/lib/slack and /var/cache/slack; and a few of my own changes to the way debs are built. * src/slack: Propagate --rsh to backends so the option actually works 2008-01-12 Alan Sundell * Slack.pm, slack-sync, slack-getroles: patch to add --rsh/-e option from David Lowry. This allows us to explicitly enable the behavior implied by a double colon in SOURCE. * slack-stage: fix from Brian Brazil for a bug that occurs with files in a subrole that have the same size and timestamp as those in a parent role, such as can occur when the two files are checked into a repository at the same time. * slack: add a --sleep option for a random sleep. Useful in crontabs, where doing 'sleep $(($RANDOM % 3600)) && slack' can run you afoul of cron's treatment of '%'. * Slack.pm: add a --version option * slack.spec: integrate some minor changes from Gordon Messmer's patch (much already done by David Lowry's patch), most significantly adding username to buildroot 2007-06-15 Alan Sundell * doc/slack.conf.5: describe the four forms of SOURCE 2007-01-25 Alan Sundell * src/Makefile: mkdir /usr/bin to fix slack-diff install (thanks to Robert Flemming and Chris Heiser) * slack.spec: new, much-improved spec file from David Lowry 2006-11-05 Alan Sundell * Version 0.14.1 * slack-getroles: remove --delete from rsync options (incompatible with newer versions [2.6.8] of rsync without -r); call check_system_exit() properly 2006-10-12 Alan Sundell * Version 0.14.0 * slack.8: added missing and new options, and a section on preview modes * slack: made --preview require a mode to avoid confusion of preview modes and role names; made 'prompt' preview mode run the preinstall/fixfiles scripts before showing file diff 2006-09-25 Alan Sundell * slack-rolediff: renamed from slack-preview to avoid name conflict * 08_diff.t: tests for slack-diff 2006-09-21 Alan Sundell * COPYING, GPL, CREDITS: add license, attribution * README: lots of new text about slack * slack-installfiles: when creating ROOT, make it mode 0755 2006-03-21 Alan Sundell * slack-diff: new diffing tool * slack-preview: new slack backend for diffing tool * slack-stage: allow specifying a subdir (files/script) to stage 2005-10-30 Alan Sundell * slack.spec: expect slack.conf in sysconfdir (since that's where we install it) * slack-installfiles: use program name when logging "Creating backup directory" message (like all other messages) 2005-02-09 Alan Sundell * Version 0.13.2 * slack-installfiles: fail gracefully if no files to install 2005-01-08 Alan Sundell * Version 0.13.1 * test: added tests for each of the backends * testsource: mockup roles for testing * test_util.pm: common functions, variables for testing * gen_config_file: generate config files with full paths for testing * test: added tests for the frontend * slack: call check_system_exit with full name (fixes undefined subroutine error when a backend failed) * Slack.pm: say commands "exited" with their exit code, rather than "returned" * slack-stage: in the base role, delete the destination if the source doesn't exist * slack-sync: remove unused variable 2005-01-05 Alan Sundell * slack-getroles: add a --remote-role-list option to force local sync * slack-sync: we don't need File::Find anymore * slack-runscript: mention when we skip a non-existant script if verbose is on. 2005-01-04 Alan Sundell * added support for "make dist" * slack-getroles: don't prepend source for paths to role-list beginning with './' (important for testing) * Slack.pm: get_options(): generate default usage if no usage given get_options(): don't pass back the --quiet handler * added support for "make test" * test: added Slack.pm tests * [Makefiles]: support a common set of install-* targets 2004-12-22 Alan Sundell * Version 0.13 * slack,Slack.pm,slack-getroles: add a --hostname option, use that to select roles when provided. * slack-runscript: export these options to the environment: root, stage, hostname, verbose * slack: add --no-sync option to skip syncing add --libexec-dir option for testing reclaim run_command() and rename it run_backend(), handle libexec dir in there. * Slack.pm: fix handling of config file parsing verbosity back to previous behavior (broken in 0.12.2) * src/Makefile: install Slack.pm mode 644 2004-12-21 Alan Sundell * Version 0.12.2 * Slack.pm: A new common library * [all]: Use new Slack.pm functions * slack: allow --noscripts, --nofiles as --no-scripts, --no-files * slack-getroles: fix broken handling of cache, source with respect to role-list 2004-12-21 Alan Sundell * Version 0.12.1 * slack: refer to installfiles backend flags consistently -- fixes bug introduced in 0.11 that broke backups 2004-12-03 Alan Sundell * Version 0.12 * slack: run fixfiles just before installfiles and after preinstall so that preinstall can, e.g, create users 2004-11-11 Alan Sundell * Version 0.11 * slack: store backend flags separately for each backend * slack: support --no-scripts, --no-files, change -n to just mean both of those, rather than the pretty useless "don't do anything, not even refreshing the cache" * [all]: Try to get some sensible output at verbosity level 2 2004-10-29 Alan Sundell * Version 0.10.2 * [all]: use full role names in the stage directory, so there aren't collisions when multiple subroles are installed * slack: call slack-sync once with all the roles * slack-sync: only sync each base role once * slack-stage: fix copy/paste variable misnomer 2004-10-29 Alan Sundell * Version 0.10.1 * slack-stage: don't need --cvs-exclude * [all]: get rid of $[ -- it just confuses people 2004-10-22 Alan Sundell * Version 0.10 * version bump to deal with downstream version increments * slack-getroles: allow roles list to be relative to source * [all]: Add a new "staging" step * slack-sync, slack-getroles: allow digits in hostnames everywhere this time * Makefile.common: add a /var/cache/slack for CACHE * doc/slack.8: new manpage * slack,slack-getroles: allow a --role-list argument 2004-08-13 Alan Sundell * Version 0.7 * slack-getroles: allow digits in hostnames 2004-07-18 Alan Sundell * Version 0.6 * slack-sync: reduce number of calls to rsync, made some exception handling more graceful and informative 2004-07-17 Alan Sundell * Version 0.5 * require rsync 2.6.0 * slack-sync: dereference symlinks (--copy-links) 2004-06-03 Alan Sundell * Version 0.4 * Changes to the way backups are handled * slack: choose a backup directory based on date * slack-sync,slack-getroles,slack-installfiles: use a --backup and a --backup-dir option, like rsync * slack.conf: set BACKUP_DIR The current scheme is a little fragile and will probably change. * Changes to verbosity: * slack: chop off one level of verbosity to modules * [modules]: bump verbosity thresholds down one * slack-sync: transfer files with --perms, so when people make old files (non-)executable in the repository, the change will propagate. * slack-installfiles: mkdir $opt{root} in an eval 2004-05-31 Alan Sundell * Version 0.3 * [all]: use sigtrap * [modules]: be silent with just one -v redirect rsync's STDOUT to STDERR * slack: propagate verbosity down to modules make level-1 verbosity less noisy, easier to read fix error-checking after system() * slack-sync: check perms on $opt{cache}/roles (root:root 0700) 2004-05-23 Alan Sundell * Version 0.2 * slack,slack-runscript: Renamed "fixperms" to "fixfiles". * slack-sync: Fixed bug that would make installing a subrole install files from the subrole only, not its parent. * slack.conf: Changed default master to rsyncd-over-ssh. 2004-05-23 Alan Sundell * Initial Version (0.1) slack-0.15.2/GPL0000644000076400001440000004310511002764207012101 0ustar alanusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. slack-0.15.2/CREDITS0000644000076400001440000000114411002764210012543 0ustar alanusers(Note: A lot of this is from memory, so if I left out or mischaracterized something you contributed, let me know!) Roman Marxer: lots of help in intial design helped come up with concept of subroles. Frank Cusack: lots of strongly-worded early criticism :) the one who said someone should name a project "slack". Mikal Still: nagged me near-daily to license the damn thing and put it up on the web David Lowry: fixed my hacky spec file Googlers: gave me feature requests, bug reports, many of which I dutifully filed in the TODO file and haven't fixed yet :/ slack-0.15.2/TODO0000644000076400001440000001134311002764207012223 0ustar alanusers# $Id: TODO 184 2008-03-03 01:54:09Z alan $ reorganize TODO file :) ################# Version 0.16: Break a bunch of stuff: * change behavior of symlinks in SOURCE -- nobody likes the current behavior, and rsyncd won't let it work as intended anyway. Just copy them down as symlinks, instead of trying to dereference them within the repository. * don't test for -x on scripts, just -e (making non-executable scripts a fatal error, rather than skipping silently by what is usually a mistake) * get rid of implicit '-e ssh' with a double-colon in SOURCE. Make people use --rsh explicitly (maybe add a warning first?) * replace --cvs-exclude in slack-sync with a better exclude list [so people can, against my advice :), install .so files and such] ################# logging! add --explicit-roles option that enables specifying roles on the command line but defaults to on. but you can disable this in slack.conf if you want to avoid admin errors. make slack-getroles use slack-sync, so only slack-sync needs to know about how to get stuff from source, and we can more easily add support for other roles sources in rolediff, feed the files to slack-diff via stdin or something to speed things up. make slack.conf noreplace in slack.spec? in --preview=prompt, add a "skip" option to skip a particular role fully support alternate diff programs (temp files or fds for modes with filename and attribute in the text; honor DIFF env var or something) Slack.pm: in find_files_to_install(), probably better to hand the callback a relative path (sub out ^$source/). make run_backend() do the printing to stderr and dereferencing backend flags make slack-getroles work for non-superusers (by caching the role list in /tmp or something) write unit tests for slack-stage changes (--subdir support) more unit tests for slack-diff we can use fakeroot if avail and skip some tests if non-root manpage update: describe order of operations add functionality to check manually-supplied role list against list in slack-getroles, with option to override just skip syncing if source equals cache. use an "install" script, if present, instead of built-in file install routines export dry-run, backup, backup-dir if they exist support preview add a flag to run script in preview with a dry-run flag or env function library for shell scripts in RPM, deb, create subdirs of CACHE a push script to read roles.conf and push a role to all servers running it defaults for slack.conf in Slack.pm or somewhere? (so people don't have to keep patching as new variables are introduced...) ################# PRE-1.0: * Figure out some better way to handle backup options: - by default, we want admins to just specify the base backup dir in config, and we can add the time-based subdir to it - we generally want only installfiles to do backups (given the existence of fixfiles, it's pretty useless for slack-sync) - if we're doing backups in slack-sync, we need to append files(.subrole)?|scripts - rsync really hates whatever fixfiles does with symlinks in the accounts role. it's probably a bug. track it down. - before running scripts, install them in some other directory, so we can keep track of the last script we ran for backup and warning purposes. (just doing it in slack-sync wouldn't work for nagging or warning purposes). Probably we'll have to install them all at once, since we don't know what other files in the scripts/ directory they may use. * Nagging - we want to be able to nag admins about files they have not yet installed or scripts they have not yet run * separate slack-runscript, slack-runfixfiles * allow ./configure to set paths * manpages for slack, slack-backends * record what roles have been installed -------- LATER: * build in support for heterogenous underlying distros, like subsets have (maybe just using subroles) * Nagging email about pending changes * Use backup functionality to store original versions of files, so we can uninstall * Use backups to store intermediate versions of files for rollbacks * Make a helper that uses tripwire or AIDE to find files that have been modified and upload them to repository, so people can be *really* lazy. * Locking to prevent concurrent writes * something like rpm -ql, -qf to show/query files in a role (just from files dir) * use a tarball as a source (possibly fetched with wget) * support http urls (with wget), both as raw dir trees and as tar.gz files * use rsync+ssh to mean rsync -e ssh (like svn) * maybe split the getting stuff in sync/getroles from the syncing stuff in stage, so we can have a common backend for various URLs slack-0.15.2/COPYING0000644000076400001440000000132411002764210012556 0ustar alanusersSLACK - Sysadmins' Lazy Auto-Configuration Kit Copyright (C) 2004-2008 Alan Sundell This program is free software; you may redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (see the file GPL); if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. slack-0.15.2/README0000644000076400001440000000146611002764210012412 0ustar alanusersShort Description: slack is a configuration management system designed to appeal to lazy admins (like me). It's an evolution from the usual "put files in some central directory" that is faily common practice. It's descended from an earlier system I also wrote, called "subsets", and uses a multi-stage rsync to fix some of the problems I had there. Basically, it's a glorified wrapper around rsync. License: See the file COPYING. Getting slack: http://www.sundell.net/~alan/projects/slack/ Documentation: Not much, but there's some in doc/ Mailing list: Sign up at http://groups.google.com/group/slack-users Reporting problems: Send an email to . Probably want to put "slack" in the subject and be patient for replies. :) $Id: README 134 2006-10-13 23:46:06Z alan $ slack-0.15.2/slack.spec0000644000076400001440000001061411002764207013504 0ustar alanusersName: slack Version: 0.15.2 Release: 1 Summary: slack configuration management tool Group: System Environment/Libraries License: GPL URL: http://www.sundell.net/~alan/projects/slack/ Source0: http://www.sundell.net/~alan/projects/slack/%{name}-%{version}.tar.gz Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch Requires: rsync >= 2.6.0 %description configuration management program for lazy admins slack tries to allow centralized configuration management with a bare minimum of effort. Usually, just putting a file in the right place will cause the right thing to be done. It uses rsync to copy files around, so can use any sort of source (NFS directory, remote server over SSH, remote server over rsync) that rsync supports. %prep %setup -q %build make %install rm -rf %{buildroot} mkdir -p %{buildroot}/%{_bindir} %makeinstall libexecdir=%{buildroot}/%{_libdir} %clean rm -rf %{buildroot} %files %defattr(644,root,root) %config %{_sysconfdir}/slack.conf %doc ChangeLog CREDITS COPYING README FAQ TODO doc/slack-intro %{_mandir}/man1/slack-diff.1.gz %{_mandir}/man5/slack.conf.5.gz %{_mandir}/man8/slack.8.gz %defattr(755,root,root) %{_bindir}/slack-diff %{_sbindir}/slack %{_libdir}/slack %defattr(0700,root,root) %{_localstatedir}/lib/slack %{_localstatedir}/cache/slack %preun if [ $1 = 0 ] ; then . /etc/slack.conf rm -rf "$CACHE"/* rm -rf "$STAGE" fi %changelog * Sun Apr 20 2008 Alan Sundell 0.15.2-1 - New upstream source (see ChangeLog). packaging fixes * Sat Jan 19 2008 Alan Sundell 0.15.1-1 - New upstream source (see ChangeLog). performance improvement for slack-sync * Mon Jan 14 2008 Alan Sundell 0.15.0-1 - New upstream source (see ChangeLog). three new options: --sleep SECS (random sleep for crontabs) --rsh COMMAND (instead of default ssh; will replace :: syntax in future) --version (print version) numerous packaging and installation fixes * Thu Nov 14 2006 David Lowry 0.14.1-2 - Spec file changes * Sun Nov 05 2006 Alan Sundell 0.14.1-1 - New upstream source (see ChangeLog). fixes bugs in rsync invocation in slack-getroles * Thu Oct 12 2006 Alan Sundell 0.14.0-1 - New upstream source (see ChangeLog). new --preview option * Wed Feb 09 2005 Alan Sundell 0.13.2-1 - New upstream source (see ChangeLog). allows non-existent files dir * Sat Jan 08 2005 Alan Sundell 0.13.1-1 - New upstream source (see ChangeLog). adds unit tests slack-runscript mentions when it skips a non-executable script when --verbose is on fix bug causing undefined subroutine reference when a backend failed * Wed Dec 22 2004 Alan Sundell 0.13-1 - new upstream source (see ChangeLog) adds --hostname, --no-sync, --libexec-dir options exports root, stage, hostname, verbose to script environment minor fixes for bugs introduced in 0.12.2 * Tue Dec 21 2004 Alan Sundell 0.12.2-1 - new upstream source (see ChangeLog) moves functions into common library Slack.pm * Tue Dec 21 2004 Alan Sundell 0.12.1-1 - new upstream source (see ChangeLog) fixes bug introduced in 0.11-1 that broke backups * Fri Dec 03 2004 Alan Sundell 0.12-1 - new upstream source (see ChangeLog) swap preinstall and fixfiles in order of operations * Thu Nov 11 2004 Alan Sundell 0.11-1 - new upstream source (see ChangeLog) add --no-files and --no-scripts options * Fri Oct 29 2004 Alan Sundell 0.10.2-1 - new upstream source (see ChangeLog) use the full role name in the stage * Fri Oct 29 2004 Alan Sundell 0.10.1-1 - new upstream source (see ChangeLog) minor code cleanups * Fri Oct 22 2004 Alan Sundell 0.10-1 - new upstream source (see ChangeLog) adds a new "staging" step, which elimates the need for .keepme~ files * Fri Aug 13 2004 Alan Sundell 0.7-1 - new upstream source * Sun Jul 18 2004 Alan Sundell 0.6-1 - new upstream source * Sat Jul 17 2004 Alan Sundell 0.5-1 - new upstream source * Thu Jul 01 2004 Alan Sundell 0.4-1 - new upstream source * Mon May 24 2004 Alan Sundell 0.1-1 - initial version slack-0.15.2/Makefile.common0000644000076400001440000000115011002764207014455 0ustar alanusers# Common code included in every Makefile # $Id: Makefile.common 189 2008-04-21 00:52:56Z sundell $ PACKAGE=slack VERSION=0.15.2 DESTDIR = prefix = / exec_prefix = /usr sysconfdir = ${prefix}/etc mandir = ${exec_prefix}/share/man bindir = ${exec_prefix}/bin sbindir = ${exec_prefix}/sbin libdir = ${exec_prefix}/lib libexecdir = ${exec_prefix}/lib localstatedir = ${prefix}/var slack_libdir = ${libdir}/slack slack_libexecdir = ${libexecdir}/slack slack_localstatedir = ${localstatedir}/lib/slack slack_localcachedir = ${localstatedir}/cache/slack INSTALL = install MKDIR = mkdir -p PRIVDIRMODE = 0700 slack-0.15.2/src/0000755000076400001440000000000011002764207012320 5ustar alanusersslack-0.15.2/src/slack-sync0000755000076400001440000001055311002764206014320 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-sync 180 2008-01-19 08:26:19Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # This script is in charge of copying files from the (possibly remote) # master directory to a local cache, using rsync require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; my @rsync = ('rsync', '--cvs-exclude', '--recursive', '--copy-links', '--times', '--perms', '--sparse', '--delete', '--files-from=-', '--from0', ); (my $PROG = $0) =~ s#.*/##; sub check_cache ($); sub rsync_source ($$@); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); # Option defaults my %opt = (); Slack::get_options( opthash => \%opt, usage => $usage, required_options => [ qw(source cache) ], ); # Arguments are required die "No roles given!\n\n$usage" unless @ARGV; # Prepare for backups if ($opt{backup} and $opt{'backup-dir'}) { # Make sure backup directory exists unless (-d $opt{'backup-dir'}) { ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; if (not $opt{'dry-run'}) { eval { mkpath($opt{'backup-dir'}); }; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; } } push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); } # Look at source type, and add options if necessary if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) { # This is tunnelled rsync, and so needs an extra option if ($opt{'rsh'}) { push @rsync, '-e', $opt{'rsh'}; } else { push @rsync, '-e', 'ssh'; } } # Pass options along to rsync if ($opt{'dry-run'}) { push @rsync, '--dry-run'; } # Pass options along to rsync if ($opt{'verbose'} > 1) { push @rsync, '--verbose'; } # }}} my @roles = (); { # This hash is just to avoid calling rsync twice if two subroles are # installed. We only care since it's remote, and therefore slow. my %roles_to_sync = (); # copy over the new files for my $full_role (@ARGV) { # Get the first element of the role name (the base role) # e.g., from "google.foogle.woogle", get "google" my $base_role = (split /\./, $full_role, 2)[0]; $roles_to_sync{$base_role} = 1; } @roles = keys %roles_to_sync; } my $cache = $opt{cache} . "/roles/"; # Make sure we've got the right perms before we copy stuff down check_cache($cache); rsync_source( $opt{source} . '/roles/', $cache, @roles, ); exit 0; # Make sure the cache directory exists and is mode 0700, to protect files # underneath in transit sub check_cache ($) { my ($cache) = @_; if (not $opt{'dry-run'}) { if (not -d $cache) { ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n"; eval { mkpath($cache); }; die "Could not mkpath cache dir '$cache': $@\n" if $@; } ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n"; if ($> != 0) { warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; } else { chown(0, 0, $cache) or die "Could not chown 0:0 '$cache': $!\n"; } chmod(0700, $cache) or die "Could not chmod 0700 '$cache': $!\n"; } } # Pull down roles from an rsync source sub rsync_source($$@) { my ($source, $destination, @roles) = @_; my @command = (@rsync, $source, $destination); ($opt{verbose} > 0) and print STDERR "$PROG: Syncing cache with '@command'\n"; my ($fh) = Slack::wrap_rsync_fh(@command); # Shove the roles down its throat print $fh join("\0", @roles), "\0"; # Close fh, waitpid, and check return value unless (close($fh)) { Slack::check_system_exit(@command); } } slack-0.15.2/src/slack-runscript0000755000076400001440000000614611002764206015400 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2006 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # This script is in charge of running scripts out of the local stage require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use File::Find; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; # Export these options to the environment of the script my @export_options = qw(root stage hostname verbose); (my $PROG = $0) =~ s#.*/##; ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); # Get out of wherever (possibly NFS-mounted) we were chdir('/') or die "Could not chdir '/': $!"; ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); # Option defaults my %opt = (); Slack::get_options( opthash => \%opt, usage => $usage, required_options => \@export_options, ); my $action = shift || die "No script to run!\n\n$usage"; # Arguments are required die "No roles given!\n\n$usage" unless @ARGV; # }}} # Start with a clean environment %ENV = ( PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', ); # Export certain variables to the environment. These are guaranteed to # be set because we require them in get_options above. for my $option (@export_options) { my $env_var = $option; $env_var =~ tr/a-z-/A-Z_/; $ENV{$env_var} = $opt{$option}; } # We want to decrement the verbose value for the child if it's set. $ENV{VERBOSE}-- if $ENV{VERBOSE}; # Run the script for each role given, if it exists and is executable for my $role (@ARGV) { my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; unless (-x $script_to_run) { if (-e _) { # A helpful warning warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; } elsif ($opt{verbose} > 0) { print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; } next; } my $dir; if ($action eq 'fixfiles') { $dir = "$opt{stage}/roles/$role/files"; } else { $dir = "$opt{stage}/roles/$role/scripts"; } my @command = ($script_to_run, $role); # It's OK to chdir even if we're not going to run the script. # Might as well see if it works. chdir($dir) or die "Could not chdir '$dir': $!\n"; if ($opt{'dry-run'}) { ($opt{verbose} > 0) and print STDERR "$PROG: Not calling '@command' in '$dir' ". "because --dry-run specified.\n"; } else { ($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; unless (system(@command) == 0) { Slack::check_system_exit(@command); } } chdir('/') or die "Could not chdir '/': $!\n" } exit 0; slack-0.15.2/src/slack.conf0000644000076400001440000000035611002764206014267 0ustar alanusers# Example slack.conf file # $Id: slack.conf 48 2004-10-23 01:21:29Z alan $ # See slack.conf(5) ROLE_LIST=etc/roles.conf SOURCE=slack-master::slack CACHE=/var/cache/slack STAGE=/var/lib/slack/stage ROOT=/ BACKUP_DIR=/var/lib/slack/backups slack-0.15.2/src/Slack.pm0000644000076400001440000002272511002764206013722 0ustar alanusers# $Id: Slack.pm 189 2008-04-21 00:52:56Z sundell $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. package Slack; require 5.006; use strict; use Carp qw(cluck confess croak); use File::Find; use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); use base qw(Exporter); use vars qw($VERSION @EXPORT @EXPORT_OK $DEFAULT_CONFIG_FILE); $VERSION = '0.15.2'; @EXPORT = qw(); @EXPORT_OK = qw(); $DEFAULT_CONFIG_FILE = '/etc/slack.conf'; my $term; my @default_options = ( 'help|h|?', 'version', 'verbose|v+', 'quiet', 'config|C=s', 'source|s=s', 'rsh|e=s', 'cache|c=s', 'stage|t=s', 'root|r=s', 'dry-run|n', 'backup|b', 'backup-dir=s', 'hostname|H=s', ); sub default_usage ($) { my ($synopsis) = @_; return < config file to read # opthash => hashref in which to store the options # verbose => whether to be verbose sub read_config (%) { my %arg = @_; my ($config_fh); local $_; confess "Slack::read_config: no config file given" if not defined $arg{file}; $arg{opthash} = {} if not defined $arg{opthash}; open($config_fh, '<', $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 # for a particular option my %looking_for; if (ref $arg{options} eq 'ARRAY') { %looking_for = map { $_ => 1 } @{$arg{options}}; } while(<$config_fh>) { chomp; s/#.*//; # delete comments s/\s+$//; # delete trailing spaces next if m/^$/; # skip empty lines if (m/^[A-Z_]+=\S+/) { my ($key, $value) = split(/=/, $_, 2); $key =~ tr/A-Z_/a-z-/; # Only set options we're looking for next if (%looking_for and not $looking_for{$key}); # Don't set options that are already set next if defined $arg{opthash}->{$key}; $arg{verbose} and print STDERR "Slack::read_config: Setting '$key' to '$value'\n"; $arg{opthash}->{$key} = $value; } else { cluck "Slack::read_config: Garbage line '$_' in '$arg{file}' line $. ignored"; } } close($config_fh) or confess "Slack::read_config: Could not close config file: $!"; # The verbose option is treated specially in so many places that # we need to make sure it's defined. $arg{opthash}->{verbose} ||= 0; return $arg{opthash}; } # Just get the exit code from a command that failed. # croaks if anything weird happened. sub get_system_exit (@) { my @command = @_; if (WIFEXITED($?)) { my $exit = WEXITSTATUS($?); return $exit if $exit; } if (WIFSIGNALED($?)) { my $sig = WTERMSIG($?); croak "'@command' caught sig $sig"; } if ($!) { croak "Syserr on system '@command': $!"; } croak "Unknown error on '@command'"; } sub check_system_exit (@) { my @command = @_; my $exit = get_system_exit(@command); # Exit is non-zero if get_system_exit() didn't croak. croak "'@command' exited $exit"; } # get options from the command line and the config file # Arguments # opthash => hashref in which to store options # usage => usage statement # required_options => arrayref of options to require -- an exception # will be thrown if these options are not defined # command_line_hash => store options specified on the command line here sub get_options { my %arg = @_; use Getopt::Long; Getopt::Long::Configure('bundling'); if (not defined $arg{opthash}) { $arg{opthash} = {}; } if (not defined $arg{usage}) { $arg{usage} = default_usage($0); } my @extra_options = (); # extra arguments to getoptions if (defined $arg{command_line_options}) { @extra_options = @{$arg{command_line_options}}; } # Make a --quiet function that turns off verbosity $arg{opthash}->{quiet} = sub { $arg{opthash}->{verbose} = 0; }; unless (GetOptions($arg{opthash}, @default_options, @extra_options, )) { print STDERR $arg{usage}; exit 1; } if ($arg{opthash}->{help}) { print $arg{usage}; exit 0; } if ($arg{opthash}->{version}) { print "slack version $VERSION\n"; exit 0; } # Get rid of the quiet handler delete $arg{opthash}->{quiet}; # 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. # Unfortunately, perl has no .replace function, so we iterate. if (ref $arg{command_line_hash} eq 'HASH') { while (my ($k, $v) = each %{$arg{opthash}}) { $arg{command_line_hash}->{$k} = $v; } } # Use the default config file if (not defined $arg{opthash}->{config}) { $arg{opthash}->{config} = $DEFAULT_CONFIG_FILE; } # We need to decide whether to be verbose about reading the config file # Currently we just do it if global verbosity > 2 my $verbose_config = 0; if (defined $arg{opthash}->{verbose} and $arg{opthash}->{verbose} > 2) { $verbose_config = 1; } # Read options from the config file, passing along the options we've # gotten so far read_config( file => $arg{opthash}->{config}, opthash => $arg{opthash}, verbose => $verbose_config, ); # The "verbose" option gets compared a lot and needs to be defined $arg{opthash}->{verbose} ||= 0; # The "hostname" option is set specially if it's not defined if (not defined $arg{opthash}->{hostname}) { use Sys::Hostname; $arg{opthash}->{hostname} = hostname; } # We can require some options to be set if (ref $arg{required_options} eq 'ARRAY') { for my $option (@{$arg{required_options}}) { if (not defined $arg{opthash}->{$option}) { croak "Required option '$option' not given on command line or specified in config file!\n"; } } } return $arg{opthash}; } sub prompt ($) { my ($prompt) = @_; if (not defined $term) { require Term::ReadLine; $term = new Term::ReadLine 'slack' } $term->readline($prompt); } # 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 # directory (i.e. where $source/foo exists but $destination/foo does not). sub find_files_to_install ($$$) { my ($source, $destination, $callback) = @_; return find ({ wanted => sub { if (-l or not -d _) { # Copy all files, links, etc my $file = $File::Find::name; &$callback($file); } elsif (-d _) { # For directories, we only want to copy it if it doesn't # exist in the destination yet. my $dir = $File::Find::name; # We know the root directory will exist (we make it above), # so skip the base of the source (my $short_source = $source) =~ s#/$##; return if $dir eq $short_source; # Strip the $source from the path, # so we can build the destination dir from it. my $subdir = $dir; ($subdir =~ s#^$source##) or croak "sub failed: $source|$subdir"; if (not -d "$destination/$subdir") { &$callback($dir); } } } }, $source, ); } # Runs rsync with the necessary redirection to its filehandles sub wrap_rsync (@) { my @command = @_; my ($pid); if ($pid = fork) { # Parent } elsif (defined $pid) { # Child open(STDIN, "<", "/dev/null") or die "Could not redirect STDIN from /dev/null\n"; # This redirection is necessary because rsync sends # verbose output to STDOUT open(STDOUT, ">&STDERR") or die "Could not redirect STDOUT to STDERR\n"; exec(@command); die "Could not exec '@command': $!\n"; } else { die "Could not fork: $!\n"; } my $kid = waitpid($pid, 0); if ($kid != $pid) { die "waitpid returned $kid\n"; } elsif ($?) { Slack::check_system_exit(@command); } } # Runs rsync with the necessary redirection to its filehandles, but also # returns an FH to stdin and a PID. sub wrap_rsync_fh (@) { my @command = @_; my ($fh, $pid); if ($pid = open($fh, "|-")) { # Parent } elsif (defined $pid) { # Child # This redirection is necessary because rsync sends # verbose output to STDOUT open(STDOUT, ">&STDERR") or die "Could not redirect STDOUT to STDERR\n"; exec(@command); die "Could not exec '@command': $!\n"; } else { die "Could not fork: $!\n"; } return($fh, $pid); } 1; slack-0.15.2/src/slack-rolediff0000755000076400001440000000746311002764206015144 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-rolediff 125 2006-09-27 07:50:07Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2006 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # 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. require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use File::Find; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; my @diff = ('slack-diff', '-uN', ); # directories to compare my %subdir = ( files => 1, scripts => 1, ); (my $PROG = $0) =~ s#.*/##; sub diff ($$;@); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); $usage .= < \%opt, command_line_options => [ 'subdir=s', 'diff=s', ], usage => $usage, required_options => [ qw(cache stage root) ], ); # Arguments are required die "No roles given!\n\n$usage" unless @ARGV; # We only allow certain values for this option if ($opt{subdir}) { unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { die "--subdir option must be 'files' or 'scripts'\n\n$usage"; } # Only do this subdir %subdir = ( $opt{subdir} => 1 ); } # Let people override our diff. Split on spaces so they can pass args. if ($opt{diff}) { @diff = split(/\s+/, $opt{diff}); } # }}} my $exit = 0; # Do the diffs for my $full_role (@ARGV) { # Split the full role (e.g. google.foogle.woogle) into components my @role = split(/\./, $full_role); if ($subdir{scripts}) { # Then we compare the cache vs the stage my $old = $opt{stage} . "/roles/" . $full_role . "/scripts"; my $new = $opt{cache} . "/roles/" . $role[0] . "/scripts"; # For scripts, we don't care so much about mode and owner (since those are # inherited in the CACHE from the SOURCE), so --noperms. $exit |= diff($old, $new, '--noperms'); } if ($subdir{files}) { # Then we compare the stage vs the root my $old = $opt{root}; my $new = $opt{stage} . "/roles/" . $full_role . "/files"; # For files, we don't care about files that exist in $old but not $new $exit |= diff($old, $new, '--unidirectional-new-file'); } } exit $exit; sub diff ($$;@) { my ($old, $new, @options) = @_; my @command = (@diff, @options); # return if there's nothing to do return 0 if (not -d $old and not -d $new); ($opt{verbose} > 0) and print STDERR "$PROG: Previewing with '@command'\n"; my $return = 0; my $callback = sub { my ($new_file) = @_; my $old_file = $new_file; ($old_file =~ s#^$new#$old#) or die "sub failed: $new|$new_file"; if (system(@command, $old_file, $new_file) != 0) { $return |= Slack::get_system_exit(@command); } }; # 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 # that exist in $ROOT and therefore aren't being synced. Slack::find_files_to_install($new, $old, $callback); return $return; } slack-0.15.2/src/slack0000755000076400001440000002220511002764207013344 0ustar alanusers#!/usr/bin/perl -w # $Id: slack 180 2008-01-19 08:26:19Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # This script is in charge of copying files from the (possibly remote) # master directory to a local cache, using rsync require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use File::Find; use POSIX; # for strftime use constant LIBEXEC_DIR => '/usr/lib/slack'; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; sub run_backend(@); sub run_conditional_backend($@); (my $PROG = $0) =~ s#.*/##; # Arguments to pass to each backends (initialized to a hash of empty arrays) my %backend_flags = ( map { $_ => [] } qw(getroles sync stage preview preinstall fixfiles installfiles postinstall) ); my @roles; ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); $usage .= < \%opt, command_line_options => [ 'preview=s', 'role-list=s', 'no-scripts|noscripts', 'no-files|nofiles', 'no-sync|nosync', 'libexec-dir=s', 'diff=s', 'sleep=i', ], required_options => [ qw(source cache stage root) ], command_line_hash => \%command_line_opt, usage => $usage, ); # Special options if ($opt{'dry-run'}) { $opt{'no-scripts'} = 1; $opt{'no-files'} = 1; } if ($opt{'no-scripts'}) { for my $action (qw(fixfiles preinstall postinstall)) { push @{$backend_flags{$action}}, '--dry-run'; } } if ($opt{'no-files'}) { push @{$backend_flags{installfiles}}, '--dry-run'; } # propagate verbosity - 1 to all backends if (defined $command_line_opt{'verbose'} and $command_line_opt{'verbose'} > 1) { for my $action (keys %backend_flags) { push @{$backend_flags{$action}}, ('--verbose') x ($command_line_opt{'verbose'} - 1); } } # propagate these flags to all the backends for my $option (qw(config root cache stage source hostname rsh)) { if ($command_line_opt{$option}) { for my $action (keys %backend_flags) { push @{$backend_flags{$action}}, "--$option=$command_line_opt{$option}"; } } } # getroles also can take 'role-list' if ($command_line_opt{'role-list'}) { push @{$backend_flags{'getroles'}}, "--role-list=$command_line_opt{'role-list'}"; } # The libexec dir defaults to this if it wasn't specified # on the command line or in a config file. if (not defined $opt{'libexec-dir'}) { $opt{'libexec-dir'} = LIBEXEC_DIR; } # Pass diff option along to slack-rolediff if ($opt{'diff'}) { push @{$backend_flags{preview}}, "--diff=$opt{'diff'}"; } # Preview takes an optional argument. If no argument is given, # it gets "" from getopt. if (defined $opt{'preview'}) { if (not grep /^$opt{'preview'}$/, qw(simple prompt)) { die "Unknown preview mode '$opt{'preview'}'!"; } } # The backup option defaults to on if it wasn't specified # on the command line or in a config file if (not defined $opt{backup}) { $opt{backup} = 1; } # Figure out a place to put backups if ($opt{backup} and $opt{'backup-dir'}) { push @{$backend_flags{installfiles}}, '--backup', '--backup-dir='. $opt{'backup-dir'}. "/". strftime('%F-%T', localtime(time)) ; } # }}} # Random sleep, helpful when called from cron. if ($opt{sleep}) { my $secs = int(rand($opt{sleep})) + 1; $opt{verbose} and print STDERR "$PROG: sleep $secs\n"; sleep($secs); } # Get a list of roles to install from slack-getroles {{{ if (not @ARGV) { my @command = ($opt{'libexec-dir'}.'/slack-getroles', @{$backend_flags{'getroles'}}); $opt{verbose} and print STDERR "$PROG: getroles\n"; ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command' to get a list of roles for this host.\n"; my ($roles_pid, $roles_fh); if ($roles_pid = open($roles_fh, "-|")) { # Parent } elsif (defined $roles_pid) { # Child exec(@command); die "Could not exec '@command': $!\n"; } else { die "Could not fork to run '@command': $!\n"; } @roles = split(/\s+/, join(" ", <$roles_fh>)); unless (close($roles_fh)) { Slack::check_system_exit(@command); } } else { @roles = @ARGV; } # }}} # Check role name syntax {{{ for my $role (@roles) { # Roles MUST begin with a letter. All else is reserved. if ($role !~ m/^[a-zA-Z]/) { die "Role '$role' does not begin with a letter!"; } } # }}} $opt{verbose} and print STDERR "$PROG: installing roles: @roles\n"; unless ($opt{'no-sync'}) { # sync all the roles down at once $opt{verbose} and print STDERR "$PROG: sync @roles\n"; run_backend('slack-sync', @{$backend_flags{sync}}, @roles); } ROLE: for my $role (@roles) { # stage $opt{verbose} and print STDERR "$PROG: stage files $role\n"; run_backend('slack-stage', @{$backend_flags{stage}}, '--subdir=files', $role); if ($opt{preview}) { if ($opt{preview} eq 'simple') { $opt{verbose} and print STDERR "$PROG: preview $role\n"; # Here, we run the backend in no-prompt mode. run_conditional_backend(0, 'slack-rolediff', @{$backend_flags{preview}}, $role); # ...and we skip further action in the ROLE after showing the diff. next ROLE; } elsif ($opt{preview} eq 'prompt') { $opt{verbose} and print STDERR "$PROG: preview scripts $role\n"; # Here, we want to prompt and just do the scripts, since # we need to run preinstall and fixfiles before doing the files. run_conditional_backend(1, 'slack-rolediff', @{$backend_flags{preview}}, '--subdir=scripts', $role); } else { # Should get caught in option processing, above die "Unknown preview mode!\n"; } } $opt{verbose} and print STDERR "$PROG: stage scripts $role\n"; run_backend('slack-stage', @{$backend_flags{stage}}, '--subdir=scripts', $role); # preinstall $opt{verbose} and print STDERR "$PROG: preinstall $role\n"; run_backend('slack-runscript', @{$backend_flags{preinstall}}, 'preinstall', $role); # fixfiles $opt{verbose} and print STDERR "$PROG: fixfiles $role\n"; run_backend('slack-runscript', @{$backend_flags{fixfiles}}, 'fixfiles', $role); # preview files if ($opt{preview} and $opt{preview} eq 'prompt') { $opt{verbose} and print STDERR "$PROG: preview files $role\n"; run_conditional_backend(1, 'slack-rolediff', @{$backend_flags{preview}}, '--subdir=files', $role); } # installfiles $opt{verbose} and print STDERR "$PROG: install $role\n"; run_backend('slack-installfiles', @{$backend_flags{installfiles}}, $role); # postinstall $opt{verbose} and print STDERR "$PROG: postinstall $role\n"; run_backend('slack-runscript', @{$backend_flags{postinstall}}, 'postinstall', $role); } exit 0; sub run_backend (@) { my ($backend, @args) = @_; # If we weren't given an explicit path, prepend the libexec dir unless ($backend =~ m#^/#) { $backend = $opt{'libexec-dir'} . '/' . $backend; } # Assemble our command line my (@command) = ($backend, @args); ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; unless (system(@command) == 0) { Slack::check_system_exit(@command); } } sub run_conditional_backend ($@) { my ($prompt, $backend, @args) = @_; # If we weren't given an explicit path, prepend the libexec dir unless ($backend =~ m#^/#) { $backend = $opt{'libexec-dir'} . '/' . $backend; } # Assemble our command line my (@command) = ($backend, @args); ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; unless (system(@command) == 0) { my $exit = Slack::get_system_exit(@command); if ($exit == 1) { # exit 1 means a difference found or something normal that requires # a prompt before continuing. if ($prompt) { exit 1 unless Slack::prompt("Continue? [yN] ") eq 'y'; } } else { # any other non-successful exit is a serious error. die "'@command' exited $exit"; } } } slack-0.15.2/src/slack-getroles0000755000076400001440000001044011002764206015163 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-getroles 180 2008-01-19 08:26:19Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # This script is in charge of copying files from the (possibly remote) # master directory to a local cache, using rsync require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; my @rsync = ('rsync', '--links', '--times', ); (my $PROG = $0) =~ s#.*/##; sub sync_list (); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options]"); $usage .= < \%opt, command_line_options => [ 'role-list=s', 'remote-role-list', ], required_options => [ qw(role-list hostname) ], usage => $usage, ); # Prepare for backups if ($opt{backup} and $opt{'backup-dir'}) { # Make sure backup directory exists unless (-d $opt{'backup-dir'}) { ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; if (not $opt{'dry-run'}) { eval { mkpath($opt{'backup-dir'}); }; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; } } push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); } # Pass options along to rsync if ($opt{'dry-run'}) { push @rsync, '--dry-run'; } # Pass options along to rsync if ($opt{'verbose'} > 1) { push @rsync, '--verbose'; } # }}} # See if role-list is actually relative to source, and pre-pend source # if need be. unless ($opt{'role-list'} =~ m#^/# or $opt{'role-list'} =~ m#^\./# or $opt{'role-list'} =~ m#^[\w@\.-]+:#) { if (not defined $opt{source}) { die "Relative path to role-list given, but source not defined!\n\n$usage\n"; } $opt{'role-list'} = $opt{source} . '/' . $opt{'role-list'}; } # auto-detect remote role list if ($opt{'role-list'} =~ m#^[\w@\.-]+:#) { $opt{'remote-role-list'} = 1; } # Copy a remote list locally if ($opt{'remote-role-list'}) { # We need a cache directory if the role list is not local if (not defined $opt{cache}) { die "Remote path to role-list given, but cache not defined!\n\n$usage\n"; } # Look at source type, and add options if necessary if ($opt{'rsh'} or $opt{'role-list'} =~ m/^[\w@\.-]+::/) { # This is tunnelled rsync, and so needs an extra option if ($opt{'rsh'}) { push @rsync, '-e', $opt{'rsh'}; } else { push @rsync, '-e', 'ssh'; } } sync_list(); } # Read in the roles list my @roles = (); my $host_found = 0; ($opt{verbose} > 0) and print STDERR "$PROG: Reading '$opt{'role-list'}'\n"; open(ROLES, "<", $opt{'role-list'}) or die "Could not open '$opt{'role-list'}' for reading: $!\n"; while() { s/#.*//; # Strip comments chomp; if (s/^$opt{hostname}:\s*//) { $host_found++; push @roles, split(); } } close(ROLES) or die "Could not close '$opt{'role-list'}': $!\n"; if (not $host_found) { die "Host '$opt{hostname}' not found in '$opt{'role-list'}'!\n"; } print join("\n", @roles), "\n"; exit 0; sub sync_list () { my $source = $opt{'role-list'}; my $destination = $opt{cache} . "/_role_list"; unless (-d $opt{cache}) { eval { mkpath($opt{cache}); }; die "Could not mkpath '$opt{cache}': $@\n" if $@; } # All this to run an rsync command my @command = (@rsync, $source, $destination); ($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command'\n"; Slack::wrap_rsync(@command); $opt{'role-list'} = $destination; } slack-0.15.2/src/slack-stage0000755000076400001440000002130611002764207014446 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-stage 180 2008-01-19 08:26:19Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # This script is in charge of copying files from the local cache # directory to the local stage, building a unified single tree onstage # from the multiple trees that are the role + subroles in the cache require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use File::Find; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; my @rsync = ('rsync', '--recursive', '--times', '--ignore-times', '--perms', '--sparse', ); (my $PROG = $0) =~ s#.*/##; sub check_stage (); sub sync_role ($$@); sub apply_default_perms_to_role ($$); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); $usage .= < \%opt, command_line_options => [ 'subdir=s', ], usage => $usage, required_options => [ qw(cache stage) ], ); # Arguments are required die "No roles given!\n\n$usage" unless @ARGV; # We only allow certain values for this option if ($opt{subdir}) { unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { die "--subdir option must be 'files' or 'scripts'\n\n$usage"; } } else { $opt{subdir} = ''; } # Prepare for backups if ($opt{backup} and $opt{'backup-dir'}) { # Make sure backup directory exists unless (-d $opt{'backup-dir'}) { ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; if (not $opt{'dry-run'}) { eval { mkpath($opt{'backup-dir'}); }; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; } } push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); } # Pass options along to rsync if ($opt{'dry-run'}) { push @rsync, '--dry-run'; } # Pass options along to rsync if ($opt{'verbose'} > 1) { push @rsync, '--verbose'; } # }}} # copy over the new files for my $full_role (@ARGV) { # Split the full role (e.g. google.foogle.woogle) into components my @role_parts = split(/\./, $full_role); 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, # so we do "google", then "google.foogle", then "google.foogle.woogle" my @role = (); # Make sure we've got the right perms before we copy stuff down check_stage(); # For the base role, do both files and scripts. push @role, shift @role_parts; for my $subdir(qw(files scripts)) { if (not $opt{subdir} or $opt{subdir} eq $subdir) { ($opt{verbose} > 1) and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; # @role here will have one element, so sync_role will use --delete sync_role($full_role, $subdir, @role) } } # For all subroles, just do the files. # (If we wanted script subroles to work like files, we'd get rid of this # distinction and simplify the code.) if (not $opt{subdir} or $opt{subdir} eq 'files') { while (@role_parts) { push @role, shift @role_parts; ($opt{verbose} > 1) and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; sync_role($full_role, 'files', @role); } } for my $subdir (qw(files scripts)) { apply_default_perms_to_role($full_role, $subdir) if (not $opt{subdir} or $opt{subdir} eq $subdir); } } exit 0; # Make sure the stage directory exists and is mode 0700, to protect files # underneath in transit sub check_stage () { my $stage = $opt{stage} . "/roles"; if (not $opt{'dry-run'}) { if (not -d $stage) { ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$stage'\n"; eval { mkpath($stage); }; die "Could not mkpath cache dir '$stage': $@\n" if $@; } ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$stage'\n"; if ($> != 0) { warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; } else { chown(0, 0, $stage) or die "Could not chown 0:0 '$stage': $!\n"; } chmod(0700, $stage) or die "Could not chmod 0700 '$stage': $!\n"; } } # Copy the files for a role from CACHE to STAGE sub sync_role ($$@) { my ($full_role, $subdir, @role) = @_; my @this_rsync = @rsync; # If we were only given one role part, we're in the base role my $in_base_role = (scalar @role == 1); # 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 # the subrole, which may be most of them!) if ($in_base_role) { push @this_rsync, "--delete"; } # (a) => a/files # (a,b,c) => a/files.b.c my $src_path = $role[0].'/'.join(".", $subdir, @role[1 .. $#role]); # This one's a little simpler: my $dst_path = $full_role.'/'.$subdir; # final / is important for rsync my $source = $opt{cache} . "/roles/" . $src_path . "/"; my $destination = $opt{stage} . "/roles/" . $dst_path . "/"; if (not -d $destination and -d $source) { ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$destination'\n"; if (not $opt{'dry-run'}) { eval { mkpath($destination); }; die "Could not mkpath stage dir '$destination': $@\n" if $@; } } # We no longer require the source to exist if (not -d $source) { # but we need to remove the destination if the source # doesn't exist and we're in the base role if ($in_base_role) { rmtree($destination); # rmtree() doesn't throw exceptions or give a return value useful # for detecting failure, so we just check after the fact. die "Could not rmtree '$destination' when '$source' missing\n" if -e $destination; } # if we continue, rsync will fail because source is missing, # so we don't. return; } # All this to run an rsync command my @command = (@this_rsync, $source, $destination); ($opt{verbose} > 0) and print STDERR "$PROG: Syncing $src_path with '@command'\n"; Slack::wrap_rsync(@command); } # This just takes the base role, and chowns/chmods everything under it to # give it some sensible permissions. Basically, the only thing we preserve # about the original permissions is the executable bit, since that's the # only thing source code controls systems like CVS, RCS, Perforce seem to # preserve. sub apply_default_perms_to_role ($$) { my ($role, $subdir) = @_; my $destination = $opt{stage} . "/roles/" . $role; if ($subdir) { $destination .= '/' . $subdir; } # If the destination doesn't exist, it's probably because the source didn't return if not -d $destination; ($opt{verbose} > 0) and print STDERR "$PROG: Setting default perms on $destination\n"; if ($> != 0) { warn "WARNING[$PROG]: Not superuser; won't be able to chown files\n"; } # Use File::Find to recurse the directory find({ # The "wanted" subroutine is called for every directory entry wanted => sub { return if $opt{'dry-run'}; ($opt{verbose} > 2) and print STDERR "$File::Find::name\n"; if (-l) { # symlinks shouldn't be in here, # since we dereference when copying warn "WARNING[$PROG]: Skipping symlink at $File::Find::name: $!\n"; return; } elsif (-f _) { # results of last stat saved in the "_" if (-x _) { chmod 0555, $_ or die "Could not chmod 0555 $File::Find::name: $!"; } else { chmod 0444, $_ or die "Could not chmod 0444 $File::Find::name: $!"; } } elsif (-d _) { chmod 0755, $_ or die "Could not chmod 0755 $File::Find::name: $!"; } else { warn "WARNING[$PROG]: Unknown file type at $File::Find::name: $!\n"; } return if $> != 0; # skip chowning if not superuser chown 0, 0, $_ or die "Could not chown 0:0 $File::Find::name: $!"; }, # end of wanted function }, # way down here, we have the directory to traverse with File::Find $destination, ); } slack-0.15.2/src/Makefile0000644000076400001440000000172011002764207013760 0ustar alanusers# Makefile for slack/src # $Id: Makefile 187 2008-03-03 02:00:18Z alan $ include ../Makefile.common BACKENDS = slack-getroles slack-installfiles slack-runscript slack-sync slack-stage slack-rolediff all: install: install-bin install-conf install-lib install-man install-bin: all $(MKDIR) $(DESTDIR)$(sbindir) $(INSTALL) slack $(DESTDIR)$(sbindir) $(MKDIR) $(DESTDIR)$(bindir) $(INSTALL) slack-diff $(DESTDIR)$(bindir) $(MKDIR) $(DESTDIR)$(slack_libexecdir) @set -ex;\ for i in $(BACKENDS); do \ $(INSTALL) $$i $(DESTDIR)$(slack_libexecdir); done $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localstatedir) $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localcachedir) install-conf: all $(MKDIR) $(DESTDIR)$(sysconfdir) $(INSTALL) -m 0644 slack.conf $(DESTDIR)$(sysconfdir) install-lib: all $(MKDIR) $(DESTDIR)$(slack_libdir) $(INSTALL) -m 0644 Slack.pm $(DESTDIR)$(slack_libdir) install-man: all clean: realclean: clean distclean: clean test: slack-0.15.2/src/slack-installfiles0000755000076400001440000000761511002764206016042 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-installfiles 180 2008-01-19 08:26:19Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2008 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # This script is in charge of copying files from the local stage to the root # of the local filesystem require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use File::Path; use constant LIB_DIR => '/usr/lib/slack'; use lib LIB_DIR; use Slack; my @rsync = ('rsync', '--relative', '--times', '--perms', '--group', '--owner', '--links', '--devices', '--sparse', '--no-implied-dirs', # SO GOOD! '--files-from=-', '--from0', ); (my $PROG = $0) =~ s#.*/##; sub install_files ($); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Get out of wherever (possibly NFS-mounted) we were chdir("/") or die "Could not chdir /: $!"; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); ######################################## # Config and option parsing {{{ my $usage = Slack::default_usage("$PROG [options] [...]"); # Option defaults my %opt = (); Slack::get_options( opthash => \%opt, usage => $usage, required_options => [ qw(root stage) ], ); # }}} # Arguments are required die "No roles given!\n\n$usage" unless @ARGV; unless (-d $opt{root}) { if (not $opt{'dry-run'}) { eval { mkpath($opt{root}); # We have a tight umask, and a root of mode 0700 would be undesirable # in most cases. chmod(0755, $opt{root}); }; die "Could not mkpath destination directory '$opt{root}': $@\n" if $@; } warn "WARNING[$PROG]: Created destination directory '".$opt{root}."'\n"; } # Prepare for backups if ($opt{backup} and $opt{'backup-dir'}) { # Make sure backup directory exists unless (-d $opt{'backup-dir'}) { ($opt{verbose} > 0) and print STDERR "$PROG: Creating backup directory '$opt{'backup-dir'}'\n"; if (not $opt{'dry-run'}) { eval { mkpath($opt{'backup-dir'}); }; die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; } } push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); } # Pass options along to rsync if ($opt{'dry-run'}) { push @rsync, '--dry-run'; } if ($opt{'verbose'} > 1) { push @rsync, '--verbose'; } # copy over the new files for my $role (@ARGV) { install_files($role); } exit 0; # This subroutine takes care of actually installing the files for a role sub install_files ($) { my ($role) = @_; # final / is important for rsync my $source = $opt{stage} . "/roles/" . $role . "/files/"; my $destination = $opt{root} . "/"; my @command = (@rsync, $source, $destination); if (not -d $source) { ($opt{verbose} > 0) and print STDERR "$PROG: No files to install -- '$source' does not exist\n"; return; } # Try to give some sensible message here if ($opt{verbose} > 0) { if ($opt{'dry-run'}) { print STDERR "$PROG: Dry-run syncing '$source' to '$destination'\n"; } else { print STDERR "$PROG: Syncing '$source' to '$destination'\n"; } } my ($fh) = Slack::wrap_rsync_fh(@command); select((select($fh), $|=1)[0]); # Turn on autoflush my $callback = sub { my ($file) = @_; ($file =~ s#^$source##) or die "sub failed: $source|$file"; print $fh "$file\0"; }; # This will print files to be synced to the $fh Slack::find_files_to_install($source, $destination, $callback); # Close fh, waitpid, and check return value unless (close($fh)) { Slack::check_system_exit(@command); } } slack-0.15.2/src/slack-diff0000755000076400001440000003322711002764206014257 0ustar alanusers#!/usr/bin/perl -w # $Id: slack-diff 122 2006-09-27 07:34:32Z alan $ # vim:sw=2 # vim600:fdm=marker # Copyright (C) 2004-2006 Alan Sundell # All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. # See the file COPYING for details. # # This script is a wrapper for diff that gives output about special files # and file modes. (diff can only compare regular files) require 5.006; use warnings FATAL => qw(all); use strict; use sigtrap qw(die untrapped normal-signals stack-trace any error-signals); use Errno; use File::stat; use File::Basename; use File::Find; use Getopt::Long; use POSIX qw(SIGPIPE strftime); use Fcntl qw(:mode); # provides things like S_IFMT that POSIX does not my $VERSION = '0.1'; (my $PROG = $0) =~ s#.*/##; my @diff; # diff program to use my $exit = 0; # our exit code sub compare ($$); sub recursive_compare ($$); sub filetype_to_string ($;$); sub compare_files ($$); sub diff ($$); ######################################## # Environment # Helpful prefix to die messages $SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; # Set a reasonable umask umask 077; # Autoflush on STDOUT $|=1; # Autoflush on STDERR select((select(STDERR), $|=1)[0]); # Default options my %opt = ( fakediff => 1, perms => 1, 'new-file' => 1, diff => 'diff', ); # Config and option parsing my $usage = < $PROG -r Options: -u, -U NUM, --unified=NUM Tell diff to use unified output format. --diff PROG Use this program for diffing, instead of "$opt{diff}" --fakediff Make a fake diff for file modes and other things that are not file contents. Default is on, can be disabled with --nofakediff. --perms Care about owner, group, and permissions when doing fakediff. Default is on, can be disabled with --noperms. -r, --recursive Recursively compare directories. -N, --new-file Treat missing files as empty. Default is on, can be disabled with --nonew-file. --unidirectional-new-file Treat only missing files in the first directory as empty. --from-file Treat arguments as a list of files from which to read filenames to compare, two lines at a time. -0, --null Use NULLs instead of newlines as the separator in --from-file mode --devnullhack You have a version of diff that can't deal with -N when not in recursive mode, so we need to feed it /dev/null instead of the missing file. Default is on, can be disabled with --nodevnullhack. --version Output version info --help Output this help text Exit codes: 0 Found no differences 1 Found a difference 2 Had a serious error 3 Found a difference and had a serious error EOF { Getopt::Long::Configure ("bundling"); GetOptions(\%opt, 'help|h|?', 'version', 'null|0', 'devnullhack', 'new-file|N', 'u', 'unified|U=i', 'recursive|r', 'from-file', 'unidirectional-new-file', 'fakediff!', 'perms!', 'diff=s', ) or die $usage; if ($opt{help}) { print $usage; exit 0; } if ($opt{version}) { print "$PROG version $VERSION\n"; exit 0; } } if ($opt{diff}) { # We split on spaces here to be useful -- so that people can give # their diff options. @diff = split(/\s+/, $opt{diff}); } else { die "$PROG: No diff program!\n"; } if ($opt{'u'}) { push @diff, '-u'; } elsif ($opt{'unified'}) { $opt{'u'} = 1; # We use this value later push @diff, "--unified=$opt{'unified'}"; } if (not $opt{'devnullhack'}) { push @diff, '-N'; } # usually, sigpipe would be someone quitting their pager, so don't sweat it $SIG{PIPE} = sub { exit $exit }; if ($opt{'from-file'}) { local $/ = "\0" if $opt{'null'}; while (my $old = <>) { my $new = <>; die "Uneven number of lines in --from-file mode!\n" if not defined $new; chomp($old); chomp($new); $exit |= compare($old, $new); } } else { die $usage unless $#ARGV == 1; $exit |= compare($ARGV[0], $ARGV[1]); } exit $exit; ## # Subroutines sub compare ($$) { my ($old, $new) = @_; if ($opt{recursive}) { return recursive_compare($old, $new); } else { return compare_files($old, $new); } } # compare two directories. We do this by walking down the *new* # directory, and comparing everything that's there to the stuff in # the old directory sub recursive_compare ($$) { my ($olddir, $newdir) = @_; my ($retval, $basere, $wanted); my (%seen); $retval = 0; if (-d $newdir) { $basere = qr(^$newdir); $wanted = sub { my ($newfile) = $_; my $oldfile = $newfile; $oldfile =~ s#$basere#$olddir#; $seen{$oldfile} = 1; $retval |= compare_files($oldfile, $newfile); }; eval { find({ wanted => $wanted , no_chdir => 1}, $newdir) }; if ($@) { warn "$PROG: error during find: $@\n"; $retval |= 2; } } return $retval if $opt{'unidirectional-new-file'}; # If we're not unidirectional, we want to go through the old directory # and diff any files we didn't see in the newdir. if (-d $olddir) { $basere = qr(^$olddir); $wanted = sub { my ($oldfile) = $_; my $newfile; return if $seen{$oldfile}; $newfile = $oldfile; $newfile =~ s#$basere#$newdir#; $retval |= compare_files($oldfile, $newfile); }; eval { find({ wanted => $wanted , no_chdir => 1}, $olddir) }; if ($@) { warn "$PROG: error during find: $@\n"; $retval |= 2; } } return $retval; } # filetype_to_string(mode) # filetype_to_string(mode, plural) # # Takes a mode returned from stat(), returns a noune describing the filetype, # e.g. "directory", "symlink". # If the "plural" argument is provided and true, returns the plural form of # the noun, e.g. "directories", "symlinks". sub filetype_to_string ($;$) { my ($mode, $plural) = @_; if (S_ISREG($mode)) { return "regular file".($plural ? "s" : ""); } elsif (S_ISDIR($mode)) { return "director".($plural ? "ies" : "y"); } elsif (S_ISLNK($mode)) { return "symlink".($plural ? "s" : ""); } elsif (S_ISBLK($mode)) { return "block device".($plural ? "s" : ""); } elsif (S_ISCHR($mode)) { return "character device".($plural ? "s" : ""); } elsif (S_ISFIFO($mode)) { return "fifo".($plural ? "s" : ""); } elsif (S_ISSOCK($mode)) { return "socket".($plural ? "s" : ""); } else { return "unknown filetype".($plural ? "s" : ""); } } # compare_files(oldfile, newfile) # This is the actual diffing routine. It's quite long because we need to # deal with all sorts of special cases. It will print to STDOUT a # description of the differences between the two files. For regular files, # diff(1) will be run to show the differences. # # return codes: # 1 found a difference # 2 had an error # 3 found a difference and had an error sub compare_files ($$) { my ($oldname, $newname) = @_; my ($old, $new); # stat buffers my $return = 0; # Get rid of unsightly double slashes $oldname =~ s#//#/#g; $newname =~ s#//#/#g; eval { $old = lstat($oldname); }; if (not defined $old and not $!{ENOENT}) { warn "$PROG: Could not stat $oldname: $!\n"; return 2; } eval { $new = lstat($newname); }; if (not defined $new and not $!{ENOENT}) { warn "$PROG: Could not stat $newname: $!\n"; return 2; } # At this point, $old or $new should only be undefined if the # file does not exist. if (defined $old and defined $new) { if (S_IFMT($old->mode) != S_IFMT($new->mode)) { if ($opt{fakediff}) { fakediff('filetype', $oldname => filetype_to_string($old->mode), $newname => filetype_to_string($new->mode), ); } else { print "File types differ between ". filetype_to_string($old->mode)." $oldname and ". filetype_to_string($new->mode)." $newname\n"; } return 1; } if ($old->nlink != $new->nlink) { # In recursive mode, we don't care about link counts in directories, # as we'll pick that up with what files do and don't exist. unless ($opt{recursive} and S_ISDIR($old->mode)) { if ($opt{fakediff}) { fakediff('nlink', $oldname => $old->nlink, $newname => $new->nlink, ); } else { print "Link counts differ between ". filetype_to_string($old->mode, 1). " $oldname and $newname\n"; } $return = 1; } } if ($old->uid != $new->uid and $opt{perms}) { if ($opt{fakediff}) { fakediff('uid', $oldname => $old->uid, $newname => $new->uid, ); } else { print "Owner differs between ". filetype_to_string($old->mode, 1). " $oldname and $newname\n"; } $return = 1; } if ($old->gid != $new->gid and $opt{perms}) { if ($opt{fakediff}) { fakediff('gid', $oldname => $old->gid, $newname => $new->gid, ); } else { print "Group differs between ". filetype_to_string($old->mode, 1). " $oldname and $newname\n"; } $return = 1; } if (S_IMODE($old->mode) != S_IMODE($new->mode) and $opt{perms}) { if ($opt{fakediff}) { fakediff('mode', $oldname => sprintf('%04o', S_IMODE($old->mode)), $newname => sprintf('%04o', S_IMODE($new->mode)), ); } else { print "Modes differ between ". filetype_to_string($old->mode, 1). " $oldname and $newname\n"; } $return = 1; } # We don't want to compare anything more about sockets, fifos, or # directories, once we've checked the permissions and link counts if (S_ISSOCK($old->mode) or S_ISFIFO($old->mode) or S_ISDIR($old->mode)) { return $return; } # Check device file devs, and that's it for them if (S_ISCHR($old->mode) or S_ISBLK($old->mode)) { if ($old->rdev != $new->rdev) { if ($opt{fakediff}) { fakediff('rdev', $oldname => $old->rdev, $newname => $new->rdev, ); } else { print "Device numbers differ between ". filetype_to_string($old->mode, 1). " $oldname and $newname\n"; } $return = 1; } return $return; } # Compare the targets of symlinks if (S_ISLNK($old->mode)) { my $oldtarget = readlink $oldname or (warn("$PROG: Could not readlink($oldname): $!\n"), return $return | 2); my $newtarget = readlink $newname or (warn("$PROG: Could not readlink($newname): $!\n"), return $return | 2); if ($oldtarget ne $newtarget) { if ($opt{fakediff}) { fakediff('target', $oldname => $oldtarget, $newname => $newtarget, ); } else { print "Symlink targets differ between $oldname and $newname\n"; } $return = 1; } return $return; } if (not S_ISREG($old->mode)) { warn "$PROG: Don't know what to do with file mode $old->mode!\n"; return 2; } } elsif (not defined $old and not defined $new) { print "Neither $oldname nor $newname exists\n"; return $return; } elsif (not defined $old) { if (not S_ISREG($new->mode) or not $opt{'new-file'}) { print "Only in ".dirname($newname).": ". filetype_to_string($new->mode)." ".basename($newname)."\n"; return 1; } elsif ($opt{'devnullhack'}) { $oldname = '/dev/null'; } } elsif (not defined $new) { if (not S_ISREG($old->mode) or not $opt{'new-file'}) { print "Only in ".dirname($oldname).": ". filetype_to_string($old->mode)." ".basename($oldname)."\n"; return 1; } elsif ($opt{'devnullhack'}) { $newname = '/dev/null'; } } # They are regular files! We can actually run diff! return diff($oldname, $newname) | $return; } sub diff ($$) { my ($oldname, $newname) = @_; my @command = (@diff, $oldname, $newname); my $status; # If we're not specifying unified diff, we need to print a header # to indicate what's being diffed. (I'm not sure if this actually would # work for patch, but it does tell our user what's going on). # FIXME: We only need to specify this if the files are different print "@command\n" if not $opt{u}; { # There is a bug in perl with use warnings FATAL => qw(all) # that will cause the child process from system() to stick # around if there is a warning generated. # Shut off warnings -- we'll catch the error below. no warnings; $status = system(@command); } return 0 if ($status == 0); if ($? == -1) { die "$PROG: failed to execute '@command': $!\n"; } if ($? & 128) { die "$PROG: '@command' dumped core\n"; } if (my $sig = $? & 127) { die "$PROG: '@command' caught sig $sig\n" unless ($sig == SIGPIPE); } if (my $exit = $? >> 8) { if ($exit == 1) { return 1; } else { die "$PROG: '@command' returned $exit\n"; } } return 0; } sub fakediff ($$) { my ($type, $oldname, $oldvalue, $newname, $newvalue) = @_; return unless $opt{fakediff}; my $time = strftime('%F %T.000000000 %z', localtime(0)); # We add a suffix onto the filenames to show we're not actually looking # at file contents. There's no good way to indicate this that's compatible # with patch, and this is simple enough. $oldname .= '#~~' . $type; $newname .= '#~~' . $type; if ($opt{u}) { # fake up a unified diff print < $newvalue EOF } } slack-0.15.2/Makefile0000644000076400001440000000161511002764210013166 0ustar alanusers# Makefile for slack # $Id: Makefile 188 2008-04-21 00:42:43Z sundell $ include Makefile.common TARGETS = all test \ install install-bin install-conf install-lib install-man \ clean distclean realclean SUBDIRS = doc src test distdir = $(PACKAGE)-$(VERSION) $(TARGETS):: @set -e; \ for i in $(SUBDIRS); do $(MAKE) -C $$i $@ ; done deb: dpkg-buildpackage -b -uc -tc -rfakeroot ls -l ../slack_*_all.deb dist: distclean mkdir -p ../$(distdir) rsync -a --exclude=.svn --exclude='*.swp' --delete-excluded \ . ../$(distdir)/ chmod -R a+rX ../$(distdir) cd .. ; \ tar -cp --exclude=debian -f $(distdir).tar $(distdir) ; \ tar -cp -f $(distdir)-debian.tar $(distdir)/debian rm -rf ../$(distdir) gzip -9f ../$(distdir).tar ../$(distdir)-debian.tar chmod a+r ../$(distdir).tar.gz ../$(distdir)-debian.tar.gz @ls -l ../$(distdir).tar.gz ../$(distdir)-debian.tar.gz check: test slack-0.15.2/test/0000755000076400001440000000000011002764203012504 5ustar alanusersslack-0.15.2/test/roles.conf.template0000644000076400001440000000032011002764202016303 0ustar alanusers# template roles.conf file for testing # $Id: roles.conf.template 72 2005-01-09 01:40:49Z alan $ __HOSTNAME__.com: badrole __HOSTNAME__: __ROLES__ foo.__HOSTNAME__: badrole fixedhost.example.com: examplerole slack-0.15.2/test/06_installfiles.t0000644000076400001440000001115411002764202015670 0ustar alanusers#!/usr/bin/perl -w use strict; use warnings FATAL => qw(all); use Test::More tests => 13; use test_util; import test_util qw(gen_wanted); use File::Find; use File::Path; # For the scripts we will run $ENV{PERL5LIB} = '../src'; my @roles = qw(role1); # sync the role so we've got something known to work with (system("../src/slack-sync -C $test_config_file @roles 2> /dev/null") == 0) or die "Couldn't sync roles for testing"; # sync the role so we've got something known to work with (system("../src/slack-stage -C $test_config_file @roles 2> /dev/null") == 0) or die "Couldn't stage roles for testing"; # First, we're playing around with role1 { my $role = $roles[0]; my $stage = $test_config{stage}."/roles/$role/files"; my $root = $test_config{root}; # Make sure all the files are installed { rmtree($root); die "Could not remove root before testing" if -e $root; # pretend /etc is to be installed with revolutionary perms my $testdirperms = 01776; my $testdir = "etc"; chmod $testdirperms, "$stage/$testdir" or die "Could not chmod $testdirperms $stage/$testdir: $!"; # something less wacky for a conf file my $testfileperms = 0440; my $testfile = "etc/$role.conf"; chmod $testfileperms, "$stage/$testfile" or die "Could not chmod $testfileperms $stage/$testfile: $!"; # Now run the install my $return = system("../src/slack-installfiles -C $test_config_file $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role installfiles return"); ok((-d $root), "$role root dir created"); is(((stat "$root/$testdir")[2] & 07777), $testdirperms, "new dir mode preserved"); is(((stat "$root/$testfile")[2] & 07777), $testfileperms, "new file mode preserved"); # Compare the lists of files in the two directories { my $stage_files = {}; my $root_files = {}; find({wanted => gen_wanted($stage, $stage_files)}, $stage); find({wanted => gen_wanted($root, $root_files)}, $root); is_deeply($root_files, $stage_files, "$role file list compare"); } # role1 has no scripts } # Test that files are not deleted { my $testdir = "$root/etc"; my $testperms = 0715; rmtree($testdir); die "Could not rmtree $testdir" if (-e $testdir); mkdir $testdir or die "Could not mkdir $testdir: $!"; chmod $testperms, $testdir or die "Could not chmod $testdir: $!"; my $testfile = "$root/etc/existing_file"; open(FILE, ">", $testfile) or die "Could not create $testfile: $!"; close(FILE) or die "Could not close $testfile: $!"; my $return = system("../src/slack-installfiles -C $test_config_file $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role installfiles return"); is(((stat $testdir)[2] & 07777), $testperms, "existing dir perms preserved"); ok((-f $testfile), "existing file preserved"); } # Test that backups are made { rmtree($test_config{'backup-dir'}); die "Could not rmtree $test_config{'backup-dir'}" if (-e $test_config{'backup-dir'}); my $testfile = "etc/$role.conf"; chmod 0644, "$root/$testfile" or die "Could not chmod 0644 $root/$testfile: $!"; open(TESTFILE, ">", "$root/$testfile") or die "Could not open $root/$testfile for writing: $!"; print TESTFILE "Some edits an admin made locally\n"; close(TESTFILE) or die "Could not close $root/$testfile: $!"; my $return = system("../src/slack-installfiles -C $test_config_file --backup --backup-dir=$test_config{'backup-dir'} $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role installfiles return"); ok((-f $test_config{'backup-dir'}."/".$testfile), "backup file created when file changed"); # Now remove the file and make sure it's not re-created when we haven't # modified the file. rmtree($test_config{'backup-dir'}); die "Could not rmtree $test_config{'backup-dir'}" if (-e $test_config{'backup-dir'}); $return = system("../src/slack-installfiles -C $test_config_file --backup --backup-dir=$test_config{'backup-dir'} $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role installfiles return"); ok((not -f $test_config{'backup-dir'}."/".$testfile), "backup file not created when file not changed"); } # Test that we succeed when no files to install { rmtree($stage); die "Could not remove stage before testing" if -e $stage; # Now run the install my $return = system("../src/slack-installfiles -C $test_config_file $role 2> /dev/null"); ok(($return == 0 and $? == 0), "succeed on missing files dir"); } } slack-0.15.2/test/slack.conf.template0000644000076400001440000000043311002764203016262 0ustar alanusers# template slack.conf file for testing # $Id: slack.conf.template 173 2008-01-19 05:55:04Z alan $ SOURCE=__TEST_DIR__/testsource ROLE_LIST=__TEST_TMPDIR__/roles.conf CACHE=__TEST_TMPDIR__/cache STAGE=__TEST_TMPDIR__/stage ROOT=__TEST_TMPDIR__/root BACKUP_DIR=__TEST_TMPDIR__/backups slack-0.15.2/test/04_stage.t0000644000076400001440000001244511002764203014305 0ustar alanusers#!/usr/bin/perl -w use strict; use warnings FATAL => qw(all); use Test::More tests => 15; use test_util; import test_util qw(gen_wanted); use File::Find; use File::Path; # For the scripts we will run $ENV{PERL5LIB} = '../src'; # get rid of the stage rmtree($test_config{stage}); die "Could not remove stage for testing" if (-e $test_config{stage}); # First, we're playing around with role1 { my $role = 'role1'; my $cache = $test_config{cache}."/roles"; my $stage = $test_config{stage}."/roles"; my $test_time = 1200000000; my $overridden_file = "/etc/$role.conf"; my $src1 = "$cache/$role/files/$overridden_file"; my $src2 = "$cache/$role/files.sub/$overridden_file"; my $dst = "$stage/$role.sub/files/$overridden_file"; # sync the role so we've got something known to work with (system("../src/slack-sync -C $test_config_file $role 2> /dev/null") == 0) or die "Couldn't sync $role for testing"; # set up the source so the overridden files have the same timestamp utime($test_time, $test_time, $src1, $src2) or die "Couldn't touch $src1 and $src2 for testing"; { # Now run the stage my $return = system("../src/slack-stage -C $test_config_file $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role stage return"); ok((-d $stage), "$role stage dir created"); # Compare the lists of files in the two directories { my $cache_files = {}; my $stage_files = {}; find({wanted => gen_wanted("$cache/$role/files", $cache_files)}, "$cache/$role/files"); find({wanted => gen_wanted("$stage/$role/files", $stage_files)}, "$stage/$role/files"); is_deeply($stage_files, $cache_files, "$role file list compare"); } } { my $return = system("../src/slack-stage -C $test_config_file $role.sub 2> /dev/null"); ok(($return == 0 and $? == 0), "$role.sub stage return"); ok((-d $stage), "$role.sub stage dir created"); # Compare the lists of files in the two directories { my $cache_files = {}; my $stage_files = {}; # The stage should have a union of files + files.sub from the cache find({wanted => gen_wanted("$cache/$role/files", $cache_files)}, "$cache/$role/files"); find({wanted => gen_wanted("$cache/$role/files.sub", $cache_files)}, "$cache/$role/files.sub"); # The stage is in "role.sub" instead of "role" for subroles find({wanted => gen_wanted("$stage/$role.sub/files", $stage_files)}, "$stage/$role.sub/files"); is_deeply($stage_files, $cache_files, "$role.sub file list compare"); } # Check that the file in the subrole overrode the file in the base role { system("cmp $src2 $dst >/dev/null 2>&1"); is($?, 0, "files in subrole override files in base role"); } # Check that the time on the overridden file is copied from the cache { my $mtime = (stat $dst)[9]; is($mtime, $test_time, "timestamp copied from cache"); } } { # Make some junk in the stage my $testfile = "$stage/$role/files/should_not_be_here"; my $testdir = "$stage/$role/scripts"; open(TEST, ">", $testfile) or die "open $testfile: $!"; print TEST "This should be deleted\n"; close(TEST) or die "close $testfile: $!"; mkpath($testdir); # will throw exception on failure my $return = system("../src/slack-stage -C $test_config_file $role 2> /dev/null"); ok(($return == 0 and $? == 0), "$role stage return"); ok((not -e $testfile), "junk file deleted"); ok((not -e $testdir), "junk scripts dir deleted"); } } # Just make sure multiple subroles work as expected for role3 { my $role = 'role3'; my $cache = $test_config{cache}."/roles"; my $stage = $test_config{stage}."/roles"; # sync the role so we've got something known to work with (system("../src/slack-sync -C $test_config_file $role 2> /dev/null") == 0) or die "Couldn't sync $role for testing"; { my $return = system("../src/slack-stage -C $test_config_file $role.sub.sub 2> /dev/null"); ok(($return == 0 and $? == 0), "$role.sub stage return"); ok((-d $stage), "$role.sub cache dir created"); # Compare the lists of files in the two directories { my $cache_files = {}; my $stage_files = {}; # The stage should have a union of files + files.sub from the cache find({wanted => gen_wanted("$cache/$role/files", $cache_files)}, "$cache/$role/files"); find({wanted => gen_wanted("$cache/$role/files.sub", $cache_files)}, "$cache/$role/files.sub"); find({wanted => gen_wanted("$cache/$role/files.sub.sub", $cache_files)}, "$cache/$role/files.sub.sub"); # The stage is in "role.sub" instead of "role" for subroles find({wanted => gen_wanted("$stage/$role.sub.sub/files", $stage_files)}, "$stage/$role.sub.sub/files"); is_deeply($stage_files, $cache_files, "$role.sub.sub file list compare"); } # role3 has no scripts # Check that the file in the subrole overrode the file in the base role { my $overridden_file = "/etc/$role.conf"; my $src = "$cache/$role/files.sub.sub/$overridden_file"; my $dst = "$stage/$role.sub.sub/files/$overridden_file"; system("cmp $src $dst >/dev/null 2>&1"); is($?, 0, "files in double subrole override files in base role"); } } } slack-0.15.2/test/08_diff.t0000644000076400001440000001406211002764202014112 0ustar alanusers#!/usr/bin/perl -w use strict; use POSIX qw(strftime); #use Test::More qw(no_plan); # tests => 12; use Test::More tests => 34; use test_util qw(write_to_file); my $TMPDIR = $test_util::TEST_TMPDIR; # time depends on timezone, so set TZ here $ENV{TZ} = 'UTC'; my $zerotime = '1970-01-01 00:00:00.000000000 +0000'; sub unified_fakediff ($$$$$) { my ($type, $file1, $file2, $val1, $val2) = @_; my $expected =< $val2 EOF # see FIXME in diff() in slack-diff if ($type ne 'filetype' and $type ne 'target') { $expected .= "diff -N $file1 $file2\n"; } return $expected; } { my $output = `../src/slack-diff --version`; is($?, 0, 'version exit code'); like($output, qr/^slack-diff version [\d\.]+$/, 'version') } { my $file1 = "$TMPDIR/file1"; my $file2 = "$TMPDIR/file2"; my $text1 = "foo\n"; # this depends on the config inside slack-diff my $diff_cmd_expected = "diff -N"; my ($output, $expected); unlink $file1, $file2; write_to_file($file1, $text1); write_to_file($file2, $text1); # see FIXME in diff() in slack-diff $expected = "$diff_cmd_expected $file1 $file2\n"; $output = `../src/slack-diff $file1 $file2`; is($?, 0, 'simple no diff exit code'); is($output, $expected, 'simple no diff'); $expected = ''; $output = `../src/slack-diff -u $file1 $file2`; is($?, 0, 'unified no diff exit code'); is($output, $expected, 'unified no diff'); my $text2 = "bar\n"; write_to_file($file2, $text2); $output = `../src/slack-diff $file1 $file2`; is($?, 1 << 8, 'simple diff exit code'); $expected = "$diff_cmd_expected $file1 $file2\n" . "1c1\n< $text1---\n> $text2"; is($output, $expected, 'simple diff'); my @output = `../src/slack-diff -u $file1 $file2`; is($?, 1 << 8, 'unified diff exit code'); # strip the times so we're not testing diff's time formatting :) $output[0] =~ s/\t.*//; $output[1] =~ s/\t.*//; $expected =< "$TEST_DIR/testsource", 'role-list' => "$TEST_TMPDIR/roles.conf", 'cache' => "$TEST_TMPDIR/cache", 'stage' => "$TEST_TMPDIR/stage", 'root' => "$TEST_TMPDIR/root", 'backup-dir' => "$TEST_TMPDIR/backups", 'verbose' => 0, ); @test_roles = sort qw(role1 role2.sub role3.sub.sub); sub gen_config_file ($$) { my ($template_file, $file) = @_; open(TEMPLATE, "<", "$template_file") or die "Could not open template file $template_file: $!"; open(FILE, ">", $file) or die "Could not open output file $file: $!"; while(