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. |