[Zope-CVS] CVS: CVSROOT - postcommit_actions:1.151

Ken Manheimer cvs-admin at zope.org
Thu Jun 17 17:43:51 EDT 2004


Update of /cvs-repository/CVSROOT
In directory cvs.zope.org:/tmp/cvs-serv30424

Modified Files:
	postcommit_actions 
Log Message:
Track different LDAP server IP number - and convert to new
postcommit-actions packaging.


=== CVSROOT/postcommit_actions 1.150 => 1.151 ===
--- CVSROOT/postcommit_actions:1.150	Tue Dec  9 13:53:06 2003
+++ CVSROOT/postcommit_actions	Thu Jun 17 17:40:12 2004
@@ -1,621 +1,25 @@
-#!/usr/bin/env python
+#!/bin/env python
 
-"""Apply checkin actions dictated by traffic_table.py, as checkins occur.
+"""Dispatch checkin notifications and manage symlinks, after checkins happen.
 
-Special provisions to fork into the background, disconnected from tty, so
-the user and (more critically) CVS locks aren't held just pending completion 
-of notification and other potentially meandering actions.
+Meant to be used on 'loginfo' CVS bookkeeping hook, eg:
 
-Options:
+  DEFAULT ${CVSROOT}/CVSROOT/postcommit_actions %{sVv}
 
- --verbose - operate in verbose mode, with output to OUTPUT_LOG in the
-             detached copy, and to stdout before the detach (and forever if 
-             --wait specified)
+Notifications are routed according to traffic_table.py in the bookkeeping
+directory, and symlink management is according to checkin of a file named
+'repolinks'.  The actual work happens in a separate, disconnected process to
+avoid CVS lock self-contention and also avoid delaying people's checkin
+activities.
 
- --dryrun - do not actually do any of the activities
+See RepoUtils.postcommit and .adjustlinks for more details (generally
+installed in the python site-packages directory)."""
 
- --wait - do not fork into the background (and leave output in stdout/stderr)
-          This is for internal use by the script when forking.
+from RepoUtils.postcommit import cvs_postcommit 
+import sys
 
- --msgfile MSGFILENM - internal - get log message from MSGFILENM (and delete)
-
-We expect to be invoked from loginfo with %{CVSROOT} %{sVv}: CVS expands
-the loginfo %{sVv} into a single command-line token (bizarrely enough)
-containing multiple, space-delimited parts:
-
-  - The first part is the path of repository directory
-
-  - Then comes a comma-concatenated string for each file being checked in,
-    consisting of:
-
-    - The file name,
-    - The old version number, then
-    - the new one, like so:
-
-      filename,1.52,1.53
-
-\(See the CVS loginfo docs for more format-string details.)
-
-The actions are configured in traffic_table.py.  The only remaining relevant
-one is:
-
- - email checkin messages to designated recipients
-
-\(Versions of the script that supported automatic mirroring was removed
-beginning at version 1.123.)"""
-
-import sys, os, tempfile, time, pwd
-SCRIPT_DIR = os.path.abspath(os.path.split(sys.argv[0])[0])
-os.chdir(SCRIPT_DIR)                # Valid dir, having other local scripts.
-import string
-import getopt
-import smtplib
-import socket, whrandom, getpass
-
-CVSROOT = os.path.split(SCRIPT_DIR)[0]
-CVSROOT_ABS = os.path.abspath('..')
-USE_LDAP = 1
-LDAP_PORT = 389
-LDAP_HOST = "10.0.11.6"
-OFFICIAL_SENDER = "cvs-admin at zope.org"
-
-# Ceiling on number of lines per file report (diff or "added"), at which point
-# we go to excerpts from the beginning and end:
-FILE_LINES_LIMIT = 2000
-# Number of lines in beginning and end excerpts when FILE_LINES_LIMIT is hit:
-FILE_EXCERPTS_LINES = 200
-
-CUSTOM_TRAFFIC_TABLE = "%s/custom_traffic_table.py" % CVSROOT
-
-import re
-
-import traffic_table
-import adjustlinks
-
-# Hook up with an outboard file.
-if os.path.exists(CUSTOM_TRAFFIC_TABLE):
-    execfile(CUSTOM_TRAFFIC_TABLE)
-
-# OUTPUT_LOG must be writable by anyone whose checkins will invoke this
-# script in order for their logging to happen.
-OUTPUT_LOG = "%s/cvs-postcommit_actions.log" % CVSROOT
-
-# Set CVSMASTER to the email address of someone managing your CVS mirroring.
-# Notices about any caught malfunctions will be sent to this address.
-CVSMASTER = "cvs-admin at zope.org"
-
-# You have to plug in the recipients email address via '%' string formatting.
-MAIL_CMD = "/usr/lib/sendmail -t -f %s"
-
-WAIT = 0                                # Fork unless --wait is requested.
-DRYRUN = 0                              # '--dryrun' option
-VERBOSE = 0                             # '--verbose' option default value
-SCRIPT = "postcommit_actions"           # Will b set to argv[0], when obtained.
-
-sVv_re = re.compile("(.*),([^,]+),([^,]+)")
-
-def main(args):
-    """Grok the args and the traffic_table and process accordingly."""
-
-    global SCRIPT, DRYRUN, VERBOSE, WAIT
-
-    orig_args = args[:]
-
-    SCRIPT = args[0]
-    try:
-        opts, args = getopt.getopt(args[1:], "", ["verbose", "dryrun",
-                                                  "wait", "msgfile=",
-                                                  ])
-    except getopt.error, err:
-        complain("%s\n", err)
-        raise SystemExit, 1
-
-    msgfilenm = ""
-
-    for opt, val in opts:
-        if opt == "--verbose":
-            VERBOSE = 1
-            complain("%s: VERBOSE\n", SCRIPT)
-        elif opt == "--dryrun":
-            DRYRUN = 1
-            complain("%s: DRYRUN\n", SCRIPT)
-        elif opt == "--wait":
-            WAIT = 1
-            complain("%s: WAIT/Foreground\n", SCRIPT)
-        elif opt == "--msgfile":
-            # This is internal, for the script to pass itself a file.
-            msgfilenm = val
-        else:
-            complain("Unrecognized option '%s'\n", opt)
-            raise SystemExit, 1
-    if VERBOSE:
-        complain("Initial args: %s '%s'\n",
-                 string.join(orig_args[:-1], " "),
-                 orig_args[-1])
-        complain("Working dir: %s\n" % safe_getcwd('/tmp'))
-
-    if len(args) != 1:
-        usage(); raise SystemExit, 1
-
-    doing_directory = 0
-    no_files = 1
-
-    # The %{sVv} args are passed in as a single token - have to split them.
-    split_args = string.split(args[0])
-    repo = split_args[0]
-
-    if len(split_args) == 0:
-        subjs = ["<no files>", "", ""]
-    elif len(split_args) == 1:
-        subjs = split_args + ["", ""]
-    elif split_args[1] == "-":
-        subjs = split_args
-        doing_directory = 1
-    else:
-        no_files = 0
-        subjs = map(grok_file, split_args[1:])
-
-    if VERBOSE:
-        complain("CWD: %s, Repo: %s, ",
-                 safe_getcwd('/tmp'), repo)
-        complain("Subjects: %s\n", subjs)
-
-    if not WAIT:
-        detach(orig_args)
-
-    entries = find_entries(repo)
-
-    for entry in entries:
-        wasVerbose = VERBOSE
-        VERBOSE = entry.get('verbose', VERBOSE)
-        selector_path = entry['path']
-        addrs = entry.get('addrs')
-        specials = entry.get('specials')
-
-        if addrs:
-            do_mail(repo, addrs, subjs, msgfilenm=msgfilenm,
-                    doing_directory=doing_directory, no_files=no_files)
-
-        if specials:
-            subj_names = map(lambda x: x[0], subjs)
-            for trigger, action in specials:
-                if trigger in subj_names:
-                    do_special(trigger, action, addrs)
-
-        VERBOSE = wasVerbose
-
-    if VERBOSE:
-        complain("** Done **\n\n")
-
-    if failures:
-        handle_failures(orig_args)
-
-    if msgfilenm:
-        os.unlink(msgfilenm)
-
-def detach(args):
-    """Fork us into the background, with stdout and stderr to OUTPUT_LOG.
-
-    We have to disconnect std io (in, out, and err) and run the program in
-    the background - which we do using some shell wizardry."""
-
-    doctored_args = [args[0]]
-    doctored_args.append("--wait")
-    tempfile.mktemp()           # Throw one away, to get initial template.
-    tempfile.tempdir = "/tmp"
-##    template = (tempfile.template or
-##                "@%s.%d" % (os.getpid(), whrandom.randint(1000000, 9999999)))
-##    tempfile.template = "cvs-log" + template
-    msgfile = open(tempfile.mktemp(suffix='.cvslog'), 'w')
-    msgfile.write(sys.stdin.read())
-    msgfile.close()
-    msgfilenm = msgfile.name
-    doctored_args.append("--msgfile")
-    doctored_args.append(msgfilenm)
-    for i in args[1:]:
-        doctored_args.append('"%s"' % i)
-    cmd = (("( exec 1>>%s 2>&1; "
-            + ("export CVSROOT=%s; " % CVSROOT)
-            + string.join(doctored_args, " ") +  " &)")
-           % OUTPUT_LOG)
-    
-    if VERBOSE:
-        complain("Re-executing detached in %s, cmd:\n\t%s\n",
-                 safe_getcwd('/tmp'), cmd)
-
-    os.system(cmd)
-    loosen_file(OUTPUT_LOG)
-
-    os._exit(0)
-
-def find_entries(repo=None):
-    """Return dictionary of traffic_table entries qualified by repo regexp.
-
-    Iff no entries match, we return the catchall entry - the (last) one with
-    path == None."""
-    
-    entries = []
-    catchall = None
-    linkmgr = adjustlinks.LinkManager()
-    gotaddrs = []
-
-    containers = linkmgr.all_containers(repo)
-    for it in traffic_table.get_table():
-        if it['path'] == None:
-            # Retain the catchall entry in case no regular ones qualify.
-            catchall = it
-        else:
-            # Obtain qualifying candidates:
-            for candidate in containers:
-
-                if (re.match(it['path'], candidate)
-                    and it.get('addrs') not in gotaddrs):
-                    entries.append(it)
-                    gotaddrs.append(it.get('addrs'))
-
-    if entries:
-        if VERBOSE > 1:
-            complain("find_entries: repo: %s, containers: %s\n entries: %s\n",
-                     repo, containers, entries)
-    elif catchall:
-        entries.append(catchall)
-        if VERBOSE > 1:
-            complain("No matches, so using catchall:\n  %s\n", entries)
-    elif VERBOSE > 1:
-        complain("No matches, no catchall - no actions\n")
-
-    for e in entries:
-        if e.has_key('addrs') and (type(e['addrs']) == type("")):
-            # Be lenient - listify string args.
-            e['addrs'] = [e['addrs']]
-
-    return entries
-
-def do_mail(repo, addrs, subjs,
-            msgfilenm, doing_directory=0, no_files=0):
-    """Send notice about checkin to addresses dictated by traffic table.
-
-    We include a diff."""
-    if VERBOSE:
-        complain("Notice to %s\n for %s / %s\n", addrs, repo, subjs)
-    # The message contents are on stdin, just _yearning_ to be sent...-)
-    subject = "CVS: %s " % repo
-
-    diff_msg = ''
-
-    if doing_directory or no_files:
-        subject = subject + string.join(subjs, " ")
-        if subjs[2] == 'New':
-            new_msg = ('=== Added directory %s ===\n' % repo)
-            diff_msg = diff_msg + new_msg + '\n'
-    else:
-        subject = subject + "-"
-        for fn, old, new in subjs:
-            subject = subject + " %s:%s" % (fn, new)
-            if new == 'NONE':
-                new_msg = ('=== Removed File %s/%s ===\n'
-                           % (repo, fn))
-            else:
-                try:
-                    new_msg = "\n" + create_diff(repo, fn, old, new)
-                except IOError:
-                    if DRYRUN:
-                        text = "[Contrived diff]"
-                    else:
-                        raise
-                    
-
-            diff_msg = diff_msg + new_msg + '\n'
-
-    try:
-        # Prepend the Subject and From lines, and append the diff:
-        mf = open(msgfilenm, 'r')
-        text = mf.read() + diff_msg
-        mf.close()
-    except IOError:
-        if DRYRUN and WAIT:
-            text = "[Contrived content]\n" + diff_msg
-        else:
-            raise
-
-    send_mail(addrs, text, subject)
-
-def create_diff(repo, file, old, new):
-    """ Create a diff comparing old and new versions """
-    if old == 'NONE':   # A new file was added
-        # We have to change to a neutral dir, or cvs will complain about
-        # doing checkouts into the repository - even though the checkout
-        # is to stdout, sigh.
-        origdir = os.getcwd()
-        try:
-            os.chdir('/tmp')
-
-            revclause = ''
-            if new and new != 'NONE':
-                revclause = '-r ' + new
-
-            # "Checkout" to stdout, so we can collect the lines to return.
-            co_stdout_cmd = 'cvs -fn co -p %s %s/%s' % (revclause, repo, file)
-            handle = os.popen(co_stdout_cmd)
-            lines = handle.readlines()
-            handle.close()
-            header = ("=== Added File %s/%s ==="
-                      % (repo, file))
-
-        finally:
-            os.chdir(origdir)
-
-    else:                # A "normal" update happened
-        diff_cmd = ('cvs -d %s -f rdiff -r %s -r %s -kk -u %s/%s'
-                    % (CVSROOT_ABS, old, new, repo, file))
-        file_handle = os.popen(diff_cmd)
-        lines = file_handle.readlines()[2:]
-        file_handle.close()
-        header = ("=== %s/%s %s => %s ==="
-                  % (str(repo), str(file), old, new))
-
-    # Provide for initial checkins on branch - in which case, the ,v files
-    # exist only in the Attic.
-    if (old == 'NONE') and (len(new.split('.')) > 2):
-        template = "%s/%s/Attic/%s,v"
-    else:
-        template = "%s/%s/%s,v"
-    commav = template % (CVSROOT_ABS, repo, file)
-    if os.path.exists(commav):
-        isbinary = expands_as_binary(commav)
-    else:
-        complain("Couldn't check binary-ness of missing comma-v"
-                 " for %s%s v %s, should be at:\n  %s"
-                 % (((old == 'NONE') and "new ") or '',
-                    file, new, commav))
-        # Let the diff go ahead:
-        isbinary = None
-    
-    if isbinary:
-        return "%s\n  <Binary-ish file>" % header
-    else:
-        total = len(lines)
-        if total >= FILE_LINES_LIMIT:
-            omitted = total - (2 * FILE_EXCERPTS_LINES)
-            # Limit exceeded, show only exercpts from beginning and end.
-            lines = (lines[:FILE_EXCERPTS_LINES]
-                     + ['\n',
-                        ('[-=- -=- -=- %s lines omitted -=- -=- -=-]\n'
-                         % (total - (2 * FILE_EXCERPTS_LINES))),
-                        '\n']
-                     + lines[-FILE_EXCERPTS_LINES:])
-            header = header + " (%s/%s lines abridged)" % (omitted, total)
-        lines.insert(0, header + "\n")
-        return string.join(lines, '')
-
-def do_special(trigger, action, addrs):
-    """Do special action - a script, to be invoked from the CVSROOT dir.
-
-    Run it with the version info and send the result to indicated addrs."""
-
-    action_cmd = "%s/CVSROOT/%s %s" % (CVSROOT, action, trigger)
-    file_handle = os.popen(action_cmd)
-
-    output = file_handle.read()
-    result = file_handle.close()
-
-    if result:
-        note_failure("*** Special command failed with error %s:\n"
-                     "** %s **\n%s\n",
-                     result, action_cmd, output)
-    else:
-        subject = "CVS: %s run for %s changes" % (action, trigger)
-        send_mail(addrs, output, subject)
-        complain("%s\n%s\n", subject, output)
-
-def send_mail(addrs, text, subject):
-    user = getuser()
-    fullname = email = ''
-    if user:
-        fullname, email = get_user_info(user)
-        if '.' in fullname or ',' in fullname:
-            fullname = '"' + fullname + '"'
-    else:
-        user = "*unknown*"
-    if not fullname:
-        fullname = user or "The Unidentified User"
-    if not email:
-        email = OFFICIAL_SENDER
-
-    cmd_info = {'verbose1': (VERBOSE and "set -x; ") or "",
-                'dryrun': (DRYRUN and "echo Would do: ") or "",
-                'mailcmd': MAIL_CMD % email,
-                'verbose2': (VERBOSE and "-v") or ""}
-    cmd = ("%(verbose1)s%(dryrun)s%(mailcmd)s %(verbose2)s" % cmd_info)
-
-    if VERBOSE:
-        complain("%sDoing mail cmd for user %s:\n\t%s\n",
-                 ((DRYRUN and "NOT ") or ""), user, cmd)
-
-    envelope_info = {'subject': subject,
-                     'to': string.join(addrs, ", "),
-                     'from': "%s <%s>" % (fullname, email),
-                     'sender': OFFICIAL_SENDER,
-                     'user': user}
-    header = ("Subject: %(subject)s\n"
-              "To: %(to)s\n"
-              "From: %(from)s\n"
-              "Sender: %(sender)s\n"
-              % envelope_info)
-
-    notice = header + '\n' + text
-
-    if not DRYRUN:
-        cmd_in, cmd_out = os.popen2(cmd, 'rw')
-        cmd_in.write(notice)
-        cmd_in.close()
-        output = cmd_out.read()
-        result = cmd_out.close()
-    else:
-        result = None
-
-    if VERBOSE:
-        complain(string.join(map(lambda x: '= ' + x,
-                                 string.split(header, '\n')),
-                             '\n'))
-    if result:
-        note_failure("*** Mail cmd yielded unexpected result %s:\n%s\n",
-                     result, output)
-
-def loosen_file(fname):
-    """Relax permissions on (newly created) file so others can use it too."""
-    try:
-        os.chmod(fname, 0777)
-    except os.error, err:
-        pass
-
-def grok_file(s):
-    """Separate "file,old-version,new-version"."""
-    m = sVv_re.match(s)
-    if not m:
-        raise ValueError, "'%s' not in file,old-vers,new-vers format" % s
-    return m.groups()
-
-failures = 0
-def note_failure(msg, *args):
-    """Register a failure for handle_failures to announce at the end."""
-    global failures
-    failures = 1
-    apply(complain, (msg,) + args)
-
-def getuser():
-    """Try to get the user's login name."""
-    try: return getpass.getuser()
-    except: return None
-
-def get_user_info(user):
-    """Obtain some aproximation to user's (fullname, email).
-
-    We prefer to get the info out of ldap.
-
-    Failing that, we use whatever we can get from the password file.
-
-    Failing that, we return an empty fullname and the value of OFFICIAL_SENDER.
-
-    Failling all, we return ('', '')."""
-    if not user:
-        return ('', '')
-    # Fallback values:
-    email = OFFICIAL_SENDER
-    try:
-        fullname = pwd.getpwnam(user)[4]
-    except KeyError:
-        fullname = ''
-
-    if USE_LDAP:
-        try:
-            import ldap; ldap_mod = ldap
-        except ImportError:
-            try:
-                import _ldap; ldap_mod = _ldap
-            except ImportError:
-                print "Failed to get any LDAP module, punting on ldap."
-                return (fullname, email)
-        try:
-            c = ldap_mod.open(LDAP_HOST, LDAP_PORT)
-            c.simple_bind_s('', '')
-            # ('sn' for "surname")
-            record = c.search_s('ou=people,dc=zope,dc=org',
-                                ldap_mod.SCOPE_SUBTREE,
-                                'cn=%s' % user, ['mail', 'sn', 'givenName'])
-            if record:
-                d = record[0][1]
-                email = d.get('mail', [email])[0]
-                first = d.get('givenName', [''])[0]
-                last = d.get('sn', [''])[0]
-                if first or last:
-                    if first: first = first + " "
-                    fullname = "%s%s" % (first, last)
-        except ldap_mod.LDAPError:
-            pass
-
-    return (fullname, email)
-
-def handle_failures(argstring):
-    """On serious failures, send the complaints log to CVSMASTER."""
-
-    if os.environ.has_key('HOSTNAME'):
-        host = os.environ['HOSTNAME']
-    else:
-        host = socket.gethostbyaddr(socket.gethostname())[0]
-        if not host:
-            host = "nohost.zope.com"
-
-    user = getuser() or "unidentified"
-
-    if os.path.isfile(OUTPUT_LOG):
-        log_file_expr = ("\n\tSee log file for application errors:\n\t\t%s"
-                         % OUTPUT_LOG)
-    else:
-        log_file_expr = ""
-
-    complain("Sending complaints log to CVSMASTER %s\n", CVSMASTER)
-    complain("Time stamp: %s\n", time.ctime(time.time()))
-    complain("Fatal failures in %s:%s"
-             "\n\tCheckin by: %s@%s\n\tInvocation: %s\n\n",
-             SCRIPT, log_file_expr, user, host, argstring)
-
-    cmd = ('%s %s -s "CVS errors in %s for %s" %s'
-           % (MAIL_CMD, ((VERBOSE and "-v") or ""), SCRIPT, user, CVSMASTER))
-    f = os.popen(cmd, 'w')
-    f.write("Serious errors encountered during CVS postcommit actions,\n")
-    f.write("the log is below with orienting details at the bottom.\n\n")
-    f.write(string.join(complaints, ""))
-    return f.close()
-
-def usage():
-    complain("Usage: %s [options] ", SCRIPT)
-    complain('"repopath file,oldv,newv [f2,o2,n2 ...]"\n')
-    complain("(Note that repo-path and files must be"
-             " quoted so they are a single token.)\n")
-
-def get_command(cmd, path):
-    """Get a valid exe for cmd on path."""
-    for d in path:
-        maybe = os.path.join(d, cmd)
-        if os.path.isfile(maybe):
-            return maybe
-    note_failure("No usable %s executable found.\n", `cmd`)
-    
-EXPANDS_RE = re.compile("expand.*@b@")
-def expands_as_binary(repository_path):
-    """Return true if the repository file is marked for binary keyword
-    expansion."""
-
-    f = open(repository_path, 'r')
-    while 1:
-        l = f.readline().strip()
-        if not l:
-            # Break out on first blank line or eof:
-            return 0
-        if EXPANDS_RE.match(l):
-            return 1
-
-complaints = []
-def complain(msg, *args):
-    global complaints
-
-    import time
-    t = time.strftime("%Y-%m-%d %T - ", time.localtime(time.time()))
-
-    complaints.append(t + (msg % args))
-    sys.stderr.write(msg % args)
-    sys.stderr.flush()
-
-def safe_getcwd(fallback):
-    try:
-        return os.getcwd()
-    except:
-        os.chdir(fallback)
-        try:
-            return os.getcwd()
-        except:
-            return "<unknown dir>"
-
-if __name__ == "__main__":
-    main(sys.argv)
+cvs_postcommit(sys.argv,
+               cvsmaster="cvs-admin at zope.org",
+               ldap_host="10.0.24.10",
+               ldap_search_base="ou=people,dc=zope,dc=org",
+               ldap_filter_list_template="cn=%s")




More information about the Zope-CVS mailing list