eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
& eval 'exec perl -S $0 $argv:q'
if 0;
# THE PRECEEDING STUFF EXECS perl via $PATH
# -*-Fundamental-*-
# This is work in progress, but might serve as a good example of how a
# process can "tail" the Perforce journal to learn of interesting
# events.
#
# This script now allows for multiple entries in an Actions table (see
# below) which allows you to configure it to trigger different actions
# when particular journal entries occur.
#
use POSIX 'setsid';
use POSIX ':sys_wait_h';
my $p4;
if (-x "/a/tools/bin/p4")
{ $p4 = "/a/tools/bin/p4"; }
elsif (-x "/usr/local/bin/p4")
{ $p4 = "/usr/local/bin/p4"; }
else
{ die "no p4!"; }
my $p4port;
my $h = `/bin/hostname`;
if ($h eq "chinacat.foxcove.com\n")
{ $p4port = "chinacat.foxcove.com:1666 -u rmg"; }
else
{ $p4port = "perforce:1666 -u p4"; }
my $P4 = "$p4 -p $p4port";
$ENV{"P4CONFIG"} = "P4ENV";
# This dispatch table talls us what to run when we see particular
# entries in the journal. The default one below has been left in
# mainly as an example; it can be overridden with the "-a
# <actionsfile>" option. The special name "ddfab_*" causes p4jd to
# add entries to the actions table based on information in the Branch
# specs.
#
my $Actions_file;
my @Actions;
my @Branch_Actions;
# Note to self: We no longer use the def_actions table below at Data
# Domain; Rather, this ground in covered by /etc/sysconfig/p4jd.conf
# on the p4jd server host. This the table below can be considered a
# fossil example.
#
# On the other hand, the kludgey we we handle sync_module events that
# have to run on other hosts (see the code in sync_module) still
# mandates changes to the code in sync_module. Have I confused you
# yet? I've sure confused me!
#
sub def_actions
{
@rawActions = split(/\n/, <<EOA);
#
#name type db.file path chdir action
#
sync_tools pv db.rev //prod/main/test/bin/ /auto/tools/bin sync_module
sync_tools pv db.rev //prod/main/tools/ddfab/ /auto/tools/bin sync_module
sync_tools pv db.rev //prod/main/tools/ddr_dist/ /auto/tools/bin sync_module
sync_tools pv db.rev //tools/main/p4_tools/ /auto/tools/bin sync_module
sync_tools pv db.rev //sweb/doc.php /auto/tools/bin sync_module
#
sync_iweb pv db.rev //iweb/ /a/web/docs sync_module
sync_iweb pv db.rev //sweb/ /a/web/docs sync_module
sync_iweb pv db.rev //prod/main/doc/pdf/ /a/web/docs sync_module
sync_iweb pv db.rev //prod/main/app/ddr/help/ /a/web/docs sync_module
#
sync_support pv db.rev //sweb/ - sync_module
sync_support pv db.rev //tools/main/p4_tools/asup_index - sync_module
#
upd_fixes rv db.change - - upd_fixes
#
ddfab_*
EOA
}
#ddfab_app pv db.rev //prod/main/app - ddfab_module
#ddfab_os pv db.rev //prod/main/os - ddfab_module
#ddfab_app pv db.rev //prod/p1.cifs/app - ddfab_module
#ddfab_os pv db.rev //prod/p1.cifs/os - ddfab_module
#ddfab_release pv db.rev //prod/beta7 - ddfab_module
#ddfab_release pv db.rev //prod/1.0.7 - ddfab_module
my $Use_branch_actions = 0;
# load the Actions table:
#
#
sub loadactions
{
if (! $Actions_file)
{ &def_actions(); }
else
{
if (! open(A, "<$Actions_file"))
{
print STDERR "$Myname: can't open \"$Actions_file\": $!\n";
exit 1;
}
while (<A>)
{ chomp; push(@rawActions, $_); }
close A;
}
foreach my $action (@rawActions)
{
if ($action =~ /^ddfab_\*/)
{
$Use_branch_actions = 1;
&load_branch_actions();
}
else
{
push (@Actions, $action);
&log("loadactions(): $action");
}
}
}
sub load_branch_actions
{
if (! open(BR, "$P4 branches |"))
{ print "$Myname: can't open \"$P4 branches\": $!\n"; exit 1; }
@Branch_Actions = ();
while (<BR>)
{
if (/^Branch ([^\s]+) [0-9\/]+ '\*([^;]+);/)
{
my $branch = $1;
my $attrs = $2;
if ($attrs =~ /\Winactive\W/) { next; }
foreach my $attr (split(/\s+/, $attrs))
{
if ($attr eq "inactive") { last; }
if ($attr =~ /^build(:.*)?$/)
{
my $bld_type = $1;
$bld_type =~ s/^://;
if ($bld_type eq "daily") { next; }
if ($bld_type eq "") { $bld_type = $branch; }
$ent = "ddfab_$bld_type\tpv\tdb.rev\t//prod/$branch";
if ($bld_type eq "app" || $bld_type eq "os") { $ent .= "/$bld_type"; }
$ent .= "\t-\tddfab_module";
push(@Branch_Actions, $ent);
&log("load_branch_actions(): $ent");
}
}
}
}
close (BR);
}
sub daemonize # courtesy of "man perlipc":
{
chdir '/' or die "Can't chdir to /: $!";
open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
open STDOUT, '>/dev/null'
or die "Can't write to /dev/null: $!";
defined(my $pid = fork) or die "Can't fork: $!";
exit if $pid;
setsid or die "Can't start a new session: $!";
open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
}
# TBD: configurability & async signalling
use Carp;
use strict;
use Fcntl ':flock'; # import LOCK_* constants
$| = 1;
my $Myname;
($Myname = $0) =~ s%^.*/%%;
my $Usage = <<LIT;
$Myname: usage: $Myname [-a <actions-file>] [-J <journal>]
LIT
sub usage
{
print STDERR $Usage;
exit 1;
}
sub help
{
print STDERR <<LIT;
$Usage
$Myname is an embryonic daemon that watches the tail of the Perforce
journal, and triggers actions when certain activities are seen there.
LIT
exit 1;
}
# option switch variables get defaults here...
my @Args;
my $Args;
my $LOGFILE = "/a/web/docs/logs/$Myname";
my $Journal = "/a/share/p4root/journal";
my $Ddfab_dir = "/auto/tools/bin";
# DEBUG:
#$LOGFILE = "/a/home/rmg/src/p4jd/LOGLOG";
#$Journal = "FAKE";
while ($#ARGV >= 0)
{
if ($ARGV[0] eq "-J")
{
shift; if ($ARGV[0] < 0) { &usage; }
$Journal = $ARGV[0]; shift; next;
}
elsif ($ARGV[0] eq "-L")
{
shift; if ($ARGV[0] < 0) { &usage; }
$LOGFILE = $ARGV[0]; shift; next;
}
elsif ($ARGV[0] eq "-a")
{
shift; if ($ARGV[0] < 0) { &usage; }
$Actions_file = $ARGV[0]; shift; next;
}
elsif ($ARGV[0] eq "-help")
{ &help; }
elsif ($ARGV[0] =~ /^-/) { &usage; }
if ($Args ne "") { $Args .= " "; }
push(@Args, $ARGV[0]);
shift;
}
&log("$Myname: starting...\n");
&loadactions();
# First, open the journal file
#
my $J_size;
#$J_size = 999999999; # DEBUG: fake a truncation
my $split_journal_inquot = 0;
my $split_journal_value;
my @j;
sub split_journal
{
my ($l, $func) = @_;
if (! $split_journal_inquot)
{
# We've got a new line, and we're not in quote, so reset the journal
# field values array
#
@j = ();
}
while ($l)
{
if ($split_journal_inquot)
{
while ($l)
{
if ($l =~ /^@@(.*\n)/)
{
$l = $1;
$split_journal_value .= "@";
next;
}
if ($l =~ /^@(.*\n)/)
{
$l = $1;
push(@j, $split_journal_value);
$split_journal_inquot = 0;
last;
}
if ($l =~ /^([^@]*\n)/)
{
$split_journal_value .= $1;
$l = "";
last;
}
if ($l =~ /^([^@]+)(@.*\n)/)
{
$split_journal_value .= $1;
$l = $2;
next;
}
}
}
else
{
if ($l eq "\n") { last; }
if ($l =~ /^(\s+)(.*\n)/) { $l = $2; next; }
if ($l =~ /^@(.*\n)/)
{
$l = $1;
$split_journal_inquot = 1;
$split_journal_value = "";
}
else
{
$l =~ /([^\s]+)(.*\n)/;
push(@j, $1);
$l = $2;
}
}
}
# We've emptied the line, and we're not in a quoted field, so
# we've got the entire journal entry; process it.
#
if (! $split_journal_inquot)
{
no strict 'refs';
&$func(@j);
use strict 'refs';
}
}
sub log
{
my ($m) = @_;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
open LOG, ">>$LOGFILE" || die "Can't open \"$LOGFILE\" for append";
my $ts = sprintf("%02d-%02d-%04d %02d:%02d:%02d",
$mon+1, $mday, $year+1900, $hour, $min, $sec);
my @m = split(/\n/, $m);
foreach my $l (@m)
{
printf LOG "$ts: [$$] $l\n";
$ts =~ s/./ /g;
}
close LOG
}
sub tail_check
{
my ($func) = @_;
my @s = stat J;
if ($#s < 0) { &log("fstat \"$Journal\" failed: $!\n"); exit 1; }
my $C_size = $s[7];
if ($C_size == $J_size) { return; }
if ($C_size < $J_size)
{
# We were truncated; rewind:
if (! seek(J, 0, 0))
{ &log("Could not rewind \"$Journal\": $!\n"); exit 1; }
if ($C_size == 0)
{
$J_size = 0;
return 0;
}
}
# If here, either size grew, or we truncated and there's
# stuff to read.
#
if (! flock(J, LOCK_EX))
{ &log("couldn't flock LOCK_EX \"$Journal\": $!\n"); exit 1; }
# Just stash stuff in here, so we can promptly relinquish the lock.
#
my @J;
while (<J>) { push(@J, $_); }
if (! flock(J, LOCK_UN))
{ &log("couldn't flock LOCK_UN \"$Journal\": $!\n"); exit 1; }
# Now process what we read...
#
foreach $_ (@J) { split_journal($_, $func); }
@s = stat J;
if ($#s < 0) { &log("fstat \"$Journal\" failed: $!\n"); exit 1; }
$J_size = $s[7];
return 1;
}
sub tail
{
my($Journal, $func) = @_;
while (1)
{
if (open(J, "<$Journal")) { last; }
if ($! =~ /^No such file or directory/i)
{
&log("Could not open journal \"$Journal\": $!\n");
exit 1;
}
sleep 1;
}
&log("opened $Journal\n");
if (! seek(J, 0, 2))
{ &log("Could not seek to end of \"$Journal\": $!\n"); exit 1; }
my @s = stat J;
if ($#s < 0) { &log("fstat \"$Journal\" failed: $!\n"); exit 1; }
$J_size = $s[7];
while (1)
{
&tail_check($func);
sleep 1;
# Recieve the souls of lost zombies...
#
waitpid(-1,&WNOHANG);
}
}
my $have_change = 0;
sub action
{
my ($name, $cmd) = @_;
&log("$name> $cmd\n");
my $cmdout = `$cmd`; my $sts = $?;
my $schr = ($sts ? "!" : "=");
&log("$name$schr $cmdout\n");
return $sts;
}
sub waitfor
{
my ($name, $change) = @_;
# To be safe, wait until "p4 counter change" reflects this change.
# We won't wait forever, however; if we don't see the expected
# change within a reasonable time, we just give up.
#
my $i;
for ($i = 20; $i; $i--)
{
my $cur_change = `$P4 counter change 2>&1`;
chop $cur_change;
if ($cur_change >= $change) { return 1; }
if ($i > 10) { sleep 1; } else { sleep 3; }
}
&log("$name: *** timed out waiting for the change counter!\n");
return 0;
}
my %Sync_last;
sub sync_module
{
my($name, $dir, @j) = @_;
my $change = $j[7];
# Only one per customer at a given change level
#
if (! ($change > $Sync_last{$name})) { return; }
# Having this up here means that we'll only try triggering for this
# $mod/$change one time. But relying on subsequent records (if the
# change contained multiple matching files, for instance) for
# retries after failures isn't right. Our contract is to bump the
# handler action once for each applicable change set.
#
$Sync_last{$name} = $change;
my $have = `$P4 counter $name 2>&1`;
if ($change > $have)
{
&log("$name: $P4 sync -f \@$change,$change\n");
my $pid;
if (! ($pid = fork()))
{
# In the child
if ($dir ne "-" && (! chdir $dir))
{
&log("name: *** could not cd to $dir: $!\n");
exit 1;
}
if (&waitfor($name, $change))
{
my $sts;
# Look for sync_* for clients on other hosts...
# (Yep, having this here is bush league... but for now...)
#
# Incestuousness follows... :-)
#
# Data Domain entries:
#
if ($name eq "sync_tools_fs1")
{
if (! ($sts = &action($name, "ssh fs1 /auto/tools/bin/syncit-fs1 $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_tools_morgan_a")
{
if (! ($sts = &action($name, "ssh morgan /a/tools/bin/syncit-morgan_a $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_support")
{
if (! ($sts = &action($name, "ssh support /var/www/html/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_salesedge")
{
if (! ($sts = &action($name, "ssh support /var/www/salesedge/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_ddfab_confs")
{
if (! ($sts = &action($name, "ssh build /auto/builds/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
# rmg personal entries:
#
if ($name eq "sync_foxcove_wheel")
{
if (! ($sts = &action($name, "ssh -l rmg wheel.foxcove.com /usr/rmg/foxcove/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_foxcove_chinacat")
{
if (! ($sts = &action($name, "ssh -l rmg chinacat.foxcove.com /home/rmg/web/foxcove/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
if ($name eq "sync_foxcove_touchofgrey")
{
if (! ($sts = &action($name, "ssh -l rmg touchofgrey.foxcove.com /Users/rmg/Sites/foxcove/syncit $change")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
# Note: we rely on the $P4CONFIG stuff here!
#
delete $ENV{"PWD"};
delete $ENV{"USER"};
delete $ENV{"USERNAME"};
if (! ($sts = &action($name, "$P4 sync -f \@$change,$change 2>&1")))
{ &action($name, "$P4 counter $name $change 2>&1"); }
exit $sts;
}
&log("$name: *** timed out waiting for the change counter update\n");
exit 1;
}
}
}
my %Ddfab_last;
sub ddfab_module
{
my($name, $dir, @j) = @_;
my $change = $j[7];
# Only one per customer at a given change level
#
if (! ($change > $Ddfab_last{$name})) { return; }
# Having this up here means that we'll only try triggering for this
# $mod/$change one time. But relying on subsequent records (if the
# change contained multiple matching files, for instance) for
# retries after failures isn't right. Our contract is to bump the
# handler action once for applicable change set.
#
$Ddfab_last{$name} = $change;
my $mod = $name;
$mod =~ s/^.*_//;
my $have = `$P4 counter $name 2>&1`;
if ($change > $have)
{
&log("$name: ddfab_run_$mod apache 0002\n");
my $pid;
if (! ($pid = fork()))
{
# In the child
if (&waitfor($name, $change))
{
# For the build daemon stuff, we just bump the
# ddfab_run_$mod script; _it_ will decide when to update
# the counter.
#
my $branch = "";
if ($mod !~ /^(app|os|dev|release|daily)$/) { $branch = "branch "; }
my $sts = &action($name, "/usr/bin/rsh build $Ddfab_dir/ddfab_run $branch$mod apache 0002 2>&1");
exit $sts;
}
&log("name: *** timed out waiting for the change counter update\n");
exit 1;
}
}
}
sub handle_entry
{
my (@j) = @_;
my @Perform_Actions;
# Reload the branch actions if we see any branch spec changed
#
if ($Use_branch_actions)
{
if ($j[2] eq "db.domain" && $j[4] eq "98") { &load_branch_actions(); }
@Perform_Actions = (@Actions, @Branch_Actions);
}
else
{ @Perform_Actions = @Actions; }
foreach my $a (@Perform_Actions)
{
if ($a =~ /^\s*\#/ || $a =~ /^\s*$/) { next; }
my($name, $op, $table, $path, $chdir, $action) = split(/\s+/, $a);
# The check for !~ $path/(test|doc)/ is a bit of a kludge, but
# makes life easier for the doc & testfolk...
#
if ($j[0] eq $op && $j[2] eq $table && $j[3] =~ /$path/ && $j[3] !~ /\/\/prod\/[^\/]+\/(test|doc)\//)
{
no strict 'refs';
&$action($name, $chdir, @j);
use strict 'refs';
}
}
}
daemonize;
&tail($Journal, "handle_entry");
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #55 | 5102 | Richard Geiger | Life is full of special cases... | ||
| #54 | 4974 | Richard Geiger |
mainly comments and one simple code structure cleanup. |
||
| #53 | 4882 | Richard Geiger |
Typo - grrr. Monday Monday. |
||
| #52 | 4881 | Richard Geiger | Use the per-host syncit scripts. | ||
| #51 | 4880 | Richard Geiger |
Musical hosts. (like musical chairs). |
||
| #50 | 4828 | Richard Geiger | Will I never learn? | ||
| #49 | 4827 | Richard Geiger | This is getting Silly. | ||
| #48 | 4628 | Richard Geiger |
Handle sync_salesedge correctly. reviewer: wade |
||
| #47 | 4289 | Richard Geiger | Don't load actions for inactive branches. | ||
| #46 | 4288 | Richard Geiger |
Oops. Need the extra "branch" keyword to ddfab_run for per-branch builds. |
||
| #45 | 4287 | Richard Geiger | Handle the new "build" (per-branch builds) type. | ||
| #44 | 4285 | Richard Geiger | Add a comment. | ||
| #43 | 4192 | Richard Geiger | Exempt prod/<branch>/doc from triggering automatic builds. | ||
| #42 | 4013 | Richard Geiger | Adjust config for asup_index. | ||
| #41 | 3935 | Richard Geiger | Pass the entire journal entry to the handlers. | ||
| #40 | 3934 | Richard Geiger | Tweak to fix the auto-branch actions load stuff. | ||
| #39 | 3933 | Richard Geiger | A tweak, so that the log will show the branch_action_reloads. | ||
| #38 | 3932 | Richard Geiger |
Mainly, this change makes p4jd more dynamically configurable when a "ddfab_*" entry is in effect: in this case, the config entries derived from the branches info is regenerated whenever any branch domain record is seen. |
||
| #37 | 3915 | Richard Geiger | Pass the change number to the remote "syncit" command. | ||
| #36 | 3914 | Richard Geiger |
Only sync the files from the changelist being done. (Need this now, with the advent of -f, or the whole blooming site get's refreshed!). |
||
| #35 | 3913 | Richard Geiger |
Use -f for auto-syncs (in case there's an old pre-Perfortification version already in the live tree). |
||
| #34 | 3699 | Richard Geiger |
Immunize all //prod/<branch>/test/ paths against auto build tiggering. |
||
| #33 | 3696 | Richard Geiger | Spiff up the log entries, and add s "starting" message. | ||
| #32 | 3695 | Richard Geiger | Log the configuration loaded. | ||
| #31 | 3694 | Richard Geiger |
These changes finally decouple the p4jd configuration from needing to be hardwired in the script. It can now get its configuration via a combination of a static configuration file plus the contents of Perforce branch specs. Once less edit to make when adding a new branch (but we still have to remember to at least restart p4jd!) |
||
| #30 | 3664 | Richard Geiger | enable 1.0.7 builds. | ||
| #29 | 3657 | Richard Geiger | Use the new grp * umask parms to ddfab_run | ||
| #28 | 3653 | Richard Geiger |
We keep the latest //sweb/doc.php here, for use by the multiboot Makefile. |
||
| #27 | 3627 | Richard Geiger | Do p1.cifs | ||
| #26 | 3617 | Richard Geiger | beta6 -> beta7 | ||
| #25 | 3555 | Richard Geiger | progress... | ||
| #24 | 3504 | Richard Geiger |
Always use -u p4. (Allows us to lock the live workspaces) |
||
| #23 | 3461 | Richard Geiger | ddr_dist now lives under //prod/<branch>/tools/, like ddfab. | ||
| #22 | 3435 | Richard Geiger | beta5 tweakage. | ||
| #21 | 3394 | Richard Geiger | Fix ssh args for sync_support. | ||
| #20 | 3357 | Richard Geiger | Set up for beta4 auto-builds. | ||
| #19 | 3313 | Richard Geiger |
Config for trigger a sync_module on the support server, via ssh. |
||
| #18 | 3304 | Richard Geiger |
Add auto-sync of //prod/main/test/bin/ Switch release branch to beta3. |
||
| #17 | 3303 | Richard Geiger |
Tweak the config table, which really should be a spearate file, and not controlled on the Public Depot! |
||
| #16 | 3198 | Richard Geiger |
Reconfig... (and whitespace) |
||
| #15 | 3195 | Richard Geiger | reconfig | ||
| #14 | 3179 | Richard Geiger | This config info really should be in a separate file! | ||
| #13 | 3175 | Richard Geiger |
Just some config changes. These really should be in a separate config file! |
||
| #12 | 3058 | Richard Geiger |
Tweak to how ddfab_run... is invoked. |
||
| #11 | 3056 | Richard Geiger | Update the general comments at the top and in the help message. | ||
| #10 | 3055 | Richard Geiger |
A change intended to free the souls of lost zombies. I'm checking this in with no further testing than a perl syntax check. Am I damned? Will I myself be zombied for offenses such as this? And... what about Naomi? |
||
| #9 | 3024 | Richard Geiger | Add -a <actions> | ||
| #8 | 3003 | Richard Geiger | Fixes to the mult-ent support. | ||
| #7 | 3000 | Richard Geiger |
Really only do a trigger only once per change, even if there are multiple matching revisions in the change. |
||
| #6 | 2997 | Richard Geiger |
Clean up ddfab_* handling so we don't get multiple triggers form the same change (as when multiple files in the changes are from the module) |
||
| #5 | 2996 | Richard Geiger | Let ddfab_run_*'s do the ddfab_* counter updates. | ||
| #4 | 2995 | Richard Geiger |
Teach it to be configurable so that it can monitor for N events. Slick, eh? |
||
| #3 | 2765 | Richard Geiger |
log timeouts when waiting for the change counter to update. My first submit with p4v! |
||
| #2 | 2762 | Richard Geiger | daemonize it. | ||
| #1 | 2761 | Richard Geiger | initial p4jd submit. |