| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

In This Package:

dbsvn.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 """
00003 
00004 Usage examples
00005 ~~~~~~~~~~~~~~
00006 
00007 ::
00008 
00009    ./dbsvn.py --help
00010        ## full list of options and this help text 
00011 
00012    ./dbsvn.py ~/catdir -M
00013        ## check catalog and skip commit message test    
00014 
00015    ./dbsvn.py ~/catdir -m "test commit message  dybsvn:source:dybgaudi/trunk/CalibWritingPkg/DBUPDATE.txt@12000  "
00016        ## check catalog and commit message 
00017 
00018 This script performs basic validations of SVN commits intended to lead to DB updates, 
00019 it is used in two situations:
00020 
00021 #. On the SVN server as part of the pre-commit hook that allows/denies the commit
00022 #. On the client, to allow testing of an intended commit before actually attempting the commit as shown above
00023 
00024 NB this script DOES NOT perform commits, it only verifies them
00025 
00026 
00027 How this script fits into the workflow
00028 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00029 
00030 ::
00031 
00032    cd ; svn co http://dayabay.ihep.ac.cn/svn/dybaux/catalog/tmp_offline_db 
00033          ## TEMORARILY DYBAUX NEEDS SOME CLEAN UP ... SO USE THE BELOW URL
00034 
00035    cd ; svn co http://dayabay.phys.ntu.edu.tw/repos/newtest/catalog/tmp_offline_db/
00036          ## check out catalog as left by the last updater
00037 
00038    ./db.py offline_db rdumpcat ~/tmp_offline_db     
00039          ## rdumpcat current offline_db on top of the SVN checkout and look for diffs
00040 
00041    svn diff ~/tmp_offline_db
00042          ## COMPLAIN LOUDLY IF YOU SEE DIFFS HERE BEFORE YOU MAKE ANY UPDATES     
00043 
00044    ./db.py tmp_joe_offline_db rdumpcat ~/tmp_offline_db       ## NB name switch
00045          ## write DBI catalog on top of working copy ~/tmp_offline_db 
00046    
00047    svn diff ~/tmp_offline_db     
00048          ## see if changed files are as you expect
00049 
00050    ./dbsvn.py ~/tmp_offline_db 
00051          ## use this script to check the "svn diff" to see if looks like a valid DBI update
00052 
00053    ./dbsvn.py ~/tmp_offline_db -m "Updating dybsvn:source:dybgaudi/trunk/CalibWritingPkg/DBUPDATE.txt@12000 " 
00054          ## fails as annotation link refers to dummy path, no such package and no change to that file at that revision 
00055 
00056    ./dbsvn.py ~/tmp_offline_db -m "Annotation link  dybsvn:source:dybgaudi/trunk/Database/DybDbiTest/tests/README "
00057          ## check the "svn diff" and intended commit message, fails as no revision  
00058 
00059    ./dbsvn.py ~/tmp_offline_db -m "Annotation link  dybsvn:source:dybgaudi/trunk/Database/DybDbiTest/tests/README@10000 "
00060          ## fails as no change to that file at that revision 
00061 
00062    ./dbsvn.py ~/tmp_offline_db -m "Annotation link  dybsvn:source:dybgaudi/trunk/Database/DybDbiTest/tests/README@9716 " 
00063          ## succeeds 
00064 
00065    svn ci ~/tmp/offline_db -m  "Updating dybsvn:source:dybgaudi/trunk/CalibWritingPkg/DBUPDATE.txt@12000 " 
00066          ## attempt the actual commit 
00067  
00068 
00069 What is validated by :file:`dbsvn.py`
00070 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00071 
00072 #. The commit message, eg "Updating dybsvn:source:dybgaudi/trunk/CalibWritingPkg/DBUPDATE.txt@12000 "
00073 
00074    #. must provide valid dybsvn reference which includes dybgaudi/trunk package path and revision number  
00075 
00076 #. Which files (which represent tables) are changed
00077 
00078    #.  author must have permission for these files/tables 
00079    #.  change must effect DBI file/tablepairs (payload, validity)
00080 
00081 #. What changes are made:
00082  
00083    #. must be additions/subtractions only  (allowing subtractions is for revertions) 
00084    #. note that LOCALSEQNO (a DBI bookkeeping table) is a special case 
00085 
00086     
00087 Rationale behind these validations
00088 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00089 
00090 #. valid DBI updates
00091 #. establish provenance and purpose
00092    
00093     #. what purpose for the update
00094     #. where it comes from (which revision of which code was used) 
00095     #. precise link to producing code and documentation 
00096 
00097 
00098 Commit denial
00099 ~~~~~~~~~~~~~~
00100 
00101 This script is invoked on the SVN server by the pre-commit hook (shown below)
00102 if any directories changed by the commit start with "catalog/". 
00103 If this script exits normally with zero return code, the commit is 
00104 allowed to proceed.
00105 
00106 On the other hand, if this script returns a non-zero exit code, 
00107 for example if an assert is tickled, then the commit is denied and stderr 
00108 is returned to the failed committer.
00109 
00110 
00111 OVERRIDE commits
00112 ~~~~~~~~~~~~~~~~~
00113 
00114 Administrators (configured using -X option on the server) can 
00115 use the string "OVERRIDE" in commit messages to short circuit validation.
00116 This is needed for non-standard operations, currently:
00117 
00118 #. adding/removing tables 
00119 
00120 A commit like the below from inside catalog will fail, assuming that the **dayabay** svn identity 
00121 is not on the admin list::
00122 
00123    svn --username dayabay ci -m "can dayabay use newtest OVERRIDE "
00124 
00125 
00126 
00127 Deployment of pre-commit hook on SVN server 
00128 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00129 
00130 Only SVN repository administrators need to understand this section.
00131 
00132 The below commands are an example of creating a bash pre-commit wrapper. 
00133 After changing the TARGET and apache user identity, the commands can be
00134 used to prepare the hook.
00135 Note that the pre-commit script is invoked by the server in a bare environment, 
00136 so any customizations must be propagated in, deploy the code::
00137 
00138     cd ; svn co http://dayabay.ihep.ac.cn/svn/dybsvn/dybgaudi/trunk/DybPython/python/DybPython
00139     export TARGET=/var/scm/repos/newtest/hooks/pre-commit
00140     sudo bash -c "cp $HOME/DybPython/{dbsvn,svndiff}.py $(dirname $TARGET)/  && chown nobody.nobody  $(dirname $TARGET)/{dbsvn,svndiff}.py "
00141 
00142     DBSVN_XREF=/var/scm/svn/dybsvn python dbsvn.py HOOK    ## check the hook is customized as desired
00143     DBSVN_XREF=/var/scm/svn/dybsvn python dbsvn.py HOOK  | sudo bash -c "cat - > $TARGET && chmod ugo+x $TARGET && chown nobody.nobody $TARGET " 
00144 
00145 
00146 A pre-commit hook testing harness is available in bash functions :env:`trunk/svn/svnprecommit.bash`
00147 
00148 
00149 Typical Problems with the Hook 
00150 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00151 
00152 **Mainly for admins**
00153 
00154 If the precommit hook is mis-configured the likely result is that attempts to 
00155 commit will hang. For example the :file:`dbsvn.py` invokation in the hook script needs to have:
00156 
00157 #. a valid admin user (SVN identity) 
00158 #. local filesystem repository path for the cross reference `-r` option
00159 
00160 The default cross reference path is the dybsvn URL which might hang on the server 
00161 as the user(root/nobody/...) that runs the SVN repository normally does not 
00162 have user permissions to access sibling repository dybsvn. (have switched to non-interactive now) 
00163 
00164 
00165 
00166 """
00167 
00168 
00169 import sys, os, re
00170 from svndiff import Diff, SVN, SVNLook
00171 
00172 log = lambda msg:sys.stderr.write(msg+"\n")
00173 cmd = lambda _:os.popen(_).read().rstrip()
00174 
00175 class Hook(dict):
00176     tmpl = """#!/bin/bash
00177 export LD_LIBRARY_PATH=%(LD_LIBRARY_PATH)s
00178 export SVNLOOK=%(SVNLOOK)s
00179     
00180 dbi=0
00181 dirs=$(%(SVNLOOK)s dirs-changed $1 --transaction $2 )
00182 for dir in $dirs ; do
00183     case $dir in 
00184         catalog*) dbi=1 ;; 
00185     esac
00186     echo "dir $dir dbi $dbi " >&2    
00187 done 
00188 
00189 # skip DBI validation if none of the dirs-changed start with "catalog"
00190 [ "$dbi" == "0" ] && exit 0 
00191 
00192 %(PYTHON)s $(dirname $0)/dbsvn.py $* -X %(USER)s -r %(XREF)s
00193 exit $?
00194 """
00195     def __init__(self, *args, **kwargs):
00196          dict.__init__(self, *args, **kwargs)
00197          self.update( LD_LIBRARY_PATH=os.environ['LD_LIBRARY_PATH'], SVNLOOK=cmd("which svnlook"),  PYTHON=cmd("which python"), USER=os.getlogin(), XREF=os.environ.get('DBSVN_XREF','http://dayabay.ihep.ac.cn/svn/dybsvn') ) 
00198     __str__ = lambda self:self.tmpl % self
00199 
00200 
00201 
00202 
00203 
00204 class Msg(dict):
00205     """
00206     A valid DBI update commit message contains a link of the form::
00207 
00208         dybsvn:source:dybgaudi/trunk/Database/DybDbiTest/tests/README@9716 
00209 
00210     The link provides crucial association between a DB update 
00211     and the package and revision of the code used in the preparation 
00212     and invokation of the update.
00213 
00214     Dummy links will be denied, a real path that was modified at the specified 
00215     revision is required.
00216 
00217     """
00218     baseurl="http://dayabay.ihep.ac.cn/svn/dybsvn"
00219     tracurl="http://dayabay.ihep.ac.cn/tracs/dybsvn"
00220     _ptn = re.compile("(?P<link>dybsvn:source:(?P<path>dybgaudi/trunk/(?P<relpath>\S*))\@(?P<rev>\d*))\s*")
00221     logurl = property(lambda self:os.path.join(self.tracurl,"log", self['path'] ))
00222 
00223     def validate_annotation_xref( self, refpath ):
00224         """ 
00225         Check that the annotation link points to a valid changed annotation file in xref repository  
00226         """
00227         revision = self.get('rev', None)
00228         path     = self.get('path', None)
00229         assert revision and path , "failed to find annotation link path and revision in commit message "
00230 
00231         if refpath.startswith("http:"):
00232             xmd = SVN( path=refpath , revision=revision )         
00233         elif os.path.exists(refpath):
00234             xmd = SVNLook( repo_path=refpath , revision=revision )
00235         else:
00236             assert 0, "refpath is invalid %s " % refpath
00237         changed = xmd.changed
00238 
00239         errmsg = lambda:"\n".join([
00240                     "INVALID COMMIT MESSAGE",
00241                     "referenced repository path \"%s\" was not touched by revision \"%s\"" % ( path, revision) , 
00242                     "the below paths were changed in this revision... ",] + changed + 
00243                     ["", "check trac url %s to find valid links/revisions " % self.logurl ,"" ]  )
00244         assert path in changed, errmsg()
00245 
00246     def __init__(self, msg ):
00247         dict.__init__(self)
00248         self.msg = msg
00249         m = self._ptn.search( msg )
00250         assert m, "INVALID COMMIT MESSAGE %s " % self.msg + self.__doc__
00251         self.update( m.groupdict() )
00252 
00253     def __repr__(self):
00254         return "Msg %s %r " % ( self.msg, dict.__repr__(self) ) 
00255 
00256 
00257 class DBIValidate(list):
00258     """
00259     Basic validation of commit that represents an intended DB update
00260     """
00261     def __init__(self, diff, msg , author ):
00262          self.diff = diff
00263          self.msg = msg
00264          self.author = author
00265          self.tabledict = dict([(c.name,c.smry) for c in self.diff.children])
00266 
00267     tables = property(lambda self:[c.name for c in self.diff.children])
00268     exts   = property(lambda self:[c.ext  for c in self.diff.children])
00269 
00270     def validate_msg(self):
00271         pass
00272 
00273     def validate_update( self):
00274         """
00275         Current checks do not verify tail addition  
00276         """
00277         tdict = self.tabledict 
00278         tabs = tdict.keys()
00279         print tabs
00280         print tdict
00281 
00282         assert 'LOCALSEQNO' in tabs, "No LOCALSEQNO in %s " % tabs
00283         assert tdict['LOCALSEQNO'] == "-+", "Unexpected LOCALSEQNO change %r " % tdict 
00284         tabs = filter(lambda t:t != 'LOCALSEQNO', tabs )
00285         assert len(tabs) % 2 == 0 , "An even number of changed tables is required %r " % tabs
00286         vlds = filter( lambda t:t[-3:] == 'Vld' , tabs )
00287         assert len(vlds) == len(tabs)/2 , "Need equal number of payload and validity table changes "
00288         for vld in vlds:
00289             pay = vld[:-3]
00290             assert pay in tabs, "Vld table %s is not paired " % vld 
00291 
00292     def __call__(self):
00293         self.validate_msg()
00294         self.validate_update()
00295 
00296 
00297 def main():
00298     from optparse import OptionParser
00299     op = OptionParser(usage=__doc__ )
00300     op.add_option("-v", "--verbose", action="store_true" )
00301     op.add_option("-m", "--message", help="Commit message to be validated, client side only. Default %default "  )
00302     op.add_option("-r", "--refpath", help="URL or filesystem path of cross reference checking repository. Default %default "  )
00303     op.add_option("-M", "--no-message-chk",  action="store_true",  help="Skip Commit message check, client side only. Default %default "  )
00304     op.add_option("-a", "--author",  help="Author identity, client side only. Default %default "  )
00305     op.add_option("-X", "--admins",  help="Comma separated list of SVN identity names of admins, server side. Default %default "  )
00306     op.set_defaults( verbose=False, message="no-message", author="unknown", admins="", no_message_chk=False , refpath="http://dayabay.ihep.ac.cn/svn/dybsvn" )
00307     (opts_ , args) = op.parse_args()
00308     opts = vars(opts_)
00309     if len(args)==0:args = [os.getcwd()]    ## default to current working directory 
00310 
00311     if len(args) == 1:
00312         if args[0] == "HOOK":
00313             print Hook()
00314             sys.exit(0)
00315         cmd = SVN(path=args[0], msg=opts['message'], author=opts['author'] ) 
00316     elif len(args) == 2:
00317         cmd = SVNLook(repo_path=args[0], txn_name=args[1], )
00318     else:
00319         print __doc__
00320         sys.exit(1) 
00321     
00322     admins = opts['admins'].split(",")
00323 
00324 
00325     author = cmd.author
00326     msg_ = cmd.msg 
00327 
00328     ## commit messages containing OVERRIDE by authors in the administrators list 
00329     ## short circuit the validations ... this is needed for non-standard operations such
00330     ## as adding or removing tables from the catalog 
00331     if msg_.find("OVERRIDE") > -1:
00332          if author in admins:
00333              sys.exit(0) 
00334          else:
00335              log("your identity %r is not in the admin users list  %r so you cannot use the OVERRIDE control  " % (author, admins) ) 
00336              sys.exit(1)
00337 
00338 
00339     lines = cmd.diff.split("\n")
00340 
00341     if not opts['no_message_chk']:
00342         msg = Msg( msg_ )
00343         log("commit msg: %r author: %r admins: %r  " % (msg,author,admins) )
00344         msg.validate_annotation_xref( opts['refpath'] )
00345     else:
00346         msg = None 
00347 
00348 
00349 
00350     maxl = 1000
00351     for i,line in enumerate(lines[0:maxl]):
00352         log("[%2d] %s " % (i+1, line))
00353     if len(lines) > maxl:
00354         log("TOTAL of %d lines truncated to max %d " %( len(lines), maxl) )  
00355 
00356 
00357     diff = Diff(lines)
00358     diff.dump()
00359 
00360     dbiv = DBIValidate( diff, msg, author )
00361     dbiv()
00362 
00363 if __name__=='__main__':
00364     main()
00365 
00366 
| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

Generated on Mon Apr 11 20:13:00 2011 for DybPython by doxygen 1.4.7