On Fri, Jan 28, 2011 at 10:27:18AM -0500, Mike Svoboda wrote:
I?m using a SVN hook on post-commit to send a email to a bunch of important 
folks with the SVN diff / files changed when I make a merge into my production 
Cfengine branch.

What cool tricks are other folks using on pre-commit / post-commit.  Anyone 
checking / creating JIRA tickets, validating the syntax of the promises, or 
other cool stuff?  I?d be curious to see what other folks are doing.

We have a few pre/post commit hooks, and make use of svnlog.  Nothing
really fancy, but it could be expanded upon without too much trouble.
I'd like to add basic syntax checking at some point.

I've attached a cleaned copy of our post-commit script (the pre-commit
amounts to "root can't commit to SVN").

The post-commit script calls the 'svnlog' program that is available
from the subversion project to generate and send emails.  We use v1.14,
although that shouldn't matter much.  (For those that don't know, it
basically makes a pretty email that includes a 'diff' for the commit
in question.)

I'm sure there's room for improvement, but it works for us at the
moment.

--
Jesse Becker
NHGRI Linux support (Digicon Contractor)
#!/usr/bin/perl -w

# A simple post-commit script to do arbitrary things for arbitrary commits.

use strict;
use Carp qw(croak carp);
use List::MoreUtils qw(uniq);
use Data::Dumper;

# Set this to a non-zero value to get debugging info
my $verbose=0;


# POST-COMMIT HOOK
#
# The post-commit hook is invoked after a commit.  Subversion runs
# this hook by invoking a program (script, executable, binary, etc.)
# named 'post-commit' (for which this file is a template) with the
# following ordered arguments:
#
#   [1] REPOS-PATH   (the path to this repository)
#   [2] REV          (the number of the revision just committed)

# The REPOS-PATH is "usually" "/var/svn/repos"


# handlers are matched as *FULL* LINES from the svnlook command
# so to match the line below, you would want to enter a REGEX
# that matches this file name.  Multiple entries can match.
#
# The ^ and $ anchors will be added at the start and end of the
# regex listed below.  So given this entry:
#
# U   bob/path1/program.c
#
# Thus, the two entries:
#   bob/path1/.*
#   bob/path1/program.c
#
# would *BOTH* match.  Whereas the entry:
#   bob/.*/header.h
#
# would not match.
#
# HANDLER "API"
#
# Each handler is called with 4 arguments:
#  $regex, $status, $REPOS, $REV
#
# $regex  -- The regex that was used to match and call the handler
# $status -- The SVN status code (e.g. "U" for updates, "A" for Add, etc)
# $REPOS  -- the path to this repository (e.g. "/var/svn/cfengine_repo")
# $REV    -- numerical version that was just committed (e.g. "4321")

# Beyond that, a handler can do whatever you want, given sufficient permissions 


my %file_handlers = (
    # Automatically install a new crontab when committed
    'project/lib/crontab' => {
         'handlers' => [ \&Install_Crontab ],
    },

    # Send emails for this project
    'project/.*' => {
         'handlers' => [ \&Send_Commit_Email, ],
         'email'    => [ 'd...@example.com', 'man...@example.com', 
'comm...@example.com' ],
    },
    
    # Emails for a different project
    'other_project/.*' => {
        'handlers' => [ \&Send_Commit_Email, ],
        'email'    => [ 'b...@example.com', 'al...@example.com' ],
    },
    
    # admins want to see everything
        '.*' => {
        'handlers'  => [ \&Send_Commit_Email,],
        'email'     => [ 'root-hum...@example.com'],
    },
);


my ($REPOS,$REV) = @ARGV;

# Who's getting emails?
# Key is the handler path (see %file_handlers, above), value is an 
# array of email addresses to whom email has *already been sent*
my %emails;

if (!defined $REV) {
    croak "Pass a REPO directory and REV, in that order";
}

my $svn='/usr/bin/svn';
my $svnlook='/usr/bin/svnlook';

# Loop over files.  This command outputs one update per line.  For example
# U   bob/project/program.c

open(SVNLOOK,"$svnlook -r $REV changed $REPOS |") || croak "open failed:  $!";

# Loop over the svnlook output
while(my $line = <SVNLOOK>) {
    chomp $line;
    debug ("LINE: $line");
    my ($status,$file) = split(' ',$line);

    # Strip off some leading characters...
    #$file =~ s,/?elliott,,;
    debug("\nChecking [$file]");

    foreach my $handler_regex (keys %file_handlers) {
        my $regex = qr($handler_regex);

        # anchor the regex
        if ( $file =~ /^$regex$/) {

            debug("\n  File [$file] --> [$handler_regex]");
            my @handlers=@{$file_handlers{$handler_regex}{handlers}};

            # call the handlers for this file.
            foreach my $handler (@handlers) {
                my $rc = $handler->($handler_regex,$status,$file,$REPOS,$REV);

                if (!defined $rc) {
                    carp "The handler for file [$file] returned undef.";
                }
            }

        }
        else {
            debug("\n  File [$file] doesn't match [$handler_regex].  Moving 
on...");
        }
    }
}

close SVNLOOK;


exit $verbose;


#=============================================================================
#=============================================================================
#=============================================================================
#=============================================================================

# Debug messaging....
sub debug {
    my ($msg,$code, $supress_newline)=@_;
    $code=1 unless defined($code);

    if ( $verbose >= $code ) {
        if ( $supress_newline ) {
            print STDERR "$msg";
        } else {
            print STDERR "$msg\n";
        }
    }
}



# Send an email with to thems that cares.
sub Send_Commit_Email {
    my ($regex,$status,$target,$REPOS,$REV) = @_;

    debug("    Sending email");
    debug(Dumper(\%emails));
    
    my $svnlog_options = "--header='X-Comment: subversion commit r$REV'";


    # Build the address list from the handler entry.
    # Still need to handle duplicate emails though...
    my @addresses = @{$file_handlers{$regex}{email}};
    my @already_sent = uniq @{$emails{$regex}};
    
    my %tmp_mail;
    @tmp_mail{@addresses} = ();
    foreach (@already_sent) {
        delete $tmp_mail{$_};
    }
    @addresses = keys %tmp_mail;
    
    $svnlog_options .= ' '.join(' ', map { "-a $_" } @addresses);
    
    my $cmd = "$REPOS/hooks/svnlog -c $REPOS/hooks/svnlog.conf $svnlog_options 
$REPOS $REV";
    
    push @{$emails{$regex}}, @addresses;
    
    debug(Dumper(\%emails));
   
   
    debug ("    Command:  $cmd");

    my $rc = system ($cmd);
    $rc = $rc >> 8;
    debug("    svnlog exited with code $rc");


    return $rc;
}


# Specialized routine to install crontab for indicated user.
sub Install_Crontab {
    my ($regex,$status,$target,$REPOS,$REV) = @_;

    my $user = 'program_user';

    # only do something on updates
    if ($status ne 'U') {
        debug('    Status not "Updated".  No action');
        return 0;
    }

    my $file="file://$REPOS/$target";
    my $dest="/tmp/svn_install_crontab.$$";

    if (-e $dest) {
        carp "Destination file $dest already exists!?";
        return;
    }

    debug("  Exporting [$target]");
    debug("       from [$file]");
    debug("         to [$dest]");

    my $output = `$svn export -r $REV $file $dest`;

    debug("OUTPUT:  $output");

    # NOTE NOTE NOTE
    # This assumes /etc/sudoers is setup correctly, and that the
    # the user commiting the code can run sudo without a password
    # for $user.  This is left as an exercise for the admin.
    my $command = "/usr/bin/sudo -u $user /usr/bin/crontab $dest";

    debug($command);
    system($command);

    unlink $dest;
}

_______________________________________________
Help-cfengine mailing list
Help-cfengine@cfengine.org
https://cfengine.org/mailman/listinfo/help-cfengine

Reply via email to