#!/usr/local/bin/python # # This script is intended to give some degree of version control and user # permissions on jobs. # It is currently under development and is neither complete nor of "production" # quality. Please bear that in mind when using it. I would STRONGLY advise running # it against a test server initially, just to check that it will do what you want it # before turning it loose on your live system!! # # The script is intended to be run either from a scheduling app, like cron, # or by setting the "repeat" variable to "1" and the "runinterval" variable # to the number of second between runs. # # In order to use this script you must enable the "p4 logger". # To do this simply run: # # p4 counter logger 0 # # from a Perforce command line client. This means that # # Due to the use this script makes of the logger functionality, running it in # conjunction with P4DTI and possibly any other defect tracker will cause both # to misbehave and miss data. SO DON'T DO IT!! # # Since this script will create a file for each job with the same name as the # job you should be careful to check that all of the job names you have are valid # file names on your OS. If you are using the default job naming scheme of job00001, # job000002, job000003, etc. then this will be fine, if however you give your jobs # other, possibly more descriptive, names ( e.g. Semaphore? ) then this is something # you should be aware of. # If you do need to rename jobs for this reason then please do all of this BEFORE # enabling the Perforce logger mechanism, otherwise the script will try to version # jobs which no longer exist. # # # A few configuration tips: # # 1) The "localpath" variable needs the double slashes (\\) on Windows machines # otherwise Python interprets them differently. You do NOT need double forward # slashes (//) in a Unix environment. # This variable should also match the "root" value of the client used to run # the script. # NOTE: you will have to create this directory manually as the script won't do # it for you. Perhaps this is something which can bee added to the script # in a future version. # # 2) The "client" variable should be set to the name of a precreated client on # the machine which will run this script. # # 3) The "user" variable should be a user with permissions on the area of the # depot where the versioned job files will be stored, i.e. the "depotpath". # # 4) The "authorised" variable contains a list of the names of users who are # authorised to alter jobs. # # 5) The "userfield" variable should be the name of a field in your jobspec which # is always set to the name of the last user to edit the job. # e.g. 109 Altered word 32 always # and should have a "Presets:" value of $user, e.g. Altered $user # THIS PART OF THE CONFIGURATION IS VITAL. WITHOUT THIS SUPPORT IN THE JOBSPEC # THE AUTHORISATION OF JOBS SIMPLY WON'T WORK AND THE SCRIPT WILL FAIL. # # 6) To have multiple users authorised to alter jobs then include them all in # the "authorised" variable, like so: authorised = ['steve','admin','perforce'] # # Config Variables - What they do. # user Perforce user to run commands as # client Perforce client to sync files to # localpath local client files directory(same directory as client root) # jvpath depot path where versioned job files are stored # authorised list of users authorised to make changes to jobs # userfield the field to check for who last edited the job # repeat loop the script automatically # runinterval if so how long in seconds between runs # p4 the path to your p4 command # log a file to logg message in. Leave blank if you want them all sent to stdout. # add a job added by an unauthorised user should be added (add=1) or deleted (add=0). from marshal import load from os import popen from re import match from string import count from time import ctime, time, sleep config = {'user': 'steve', 'client': 'review-daemon', 'localpath': 'c:\\localwork\\vjobs\\', 'jvpath': '//depot/vjobs/', 'authorised': ['steve'], 'userfield': 'Altered', 'repeat': 1, 'runinterval': 30, 'p4': 'p4', 'log': 'c:\\localwork\\vjobs\\vjobs.log', 'add': 1} class P4Command: def __init__(self): self.command = '%s -u %s -c %s ' % (config['p4'], config['user'], config['client']) def P4Run(self, args, mode = 'r'): return popen(self.command + args, mode) def P4Marshal(self, args): return load(popen(self.command + ' -G ' + args, 'rb')) class P4Job(P4Command): def __init__(self, jobname): P4Command.__init__(self) self.name = '' self.dict = {} self.have = '' self.action = '' self.name = jobname self.have = self.P4Run(' have ' + config['localpath'] + self.name).readline() self.dict = self.P4Marshal('job -o ' + self.name) # The message handling mechanism is currently pretty crude and simply # prints out what ever text it is sent to either stdout or a log file # if one has been specified in the config variables. # The next step is to alter this to an email mechanism which will mail # the interested parties for each event of note and possibly log them # as well def HandleMessage(self, msg): if config['log']: fh = open(config['log'], 'a') fh.write(ctime(time()) + ' - ' + msg + '\n') fh.close() return print ctime(time()) + ' - ' + msg def GetAction(self): if not self.have and not self.dict: self.action = 'error' return if self.have and ((self.dict['code'] == 'error') or not self.dict): self.action = 'delete' return if not self.have and self.dict: self.action = 'add' del self.dict['code'] return del self.dict['code'] self.action = 'edit' def IsAuth(self): if self.dict[config['userfield']] in config['authorised']: return 1 return 0 def EditJob(self, msg = None): if msg: self.HandleMessage(msg) self.P4Run('edit ' + config['jvpath'] + self.name) self.WriteJobFile() # This solution to jobs created by unauthorised users is a little severe # but it will do for now. At the very least the authorised users should # be informed of what was contained in the job. def DeleteJob(self, msg = None): if msg: self.HandleMessage(msg) self.P4Run('job -d ' + self.name) def RevertJob(self, msg = None): if msg: self.HandleMessage(msg) fh = self.P4Run('job -i -f', 'w') for line in open(config['localpath'] + self.name, 'r').readlines(): if '\t' not in line: line = '\t' + line fh.write(line) fh.close() def DeleteJobFile(self, msg = None): if msg: self.HandleMessage(msg) self.P4Run('delete ' + config['localpath'] + self.name) def WriteJobFile(self): fh = open(config['localpath'] + self.name, 'w') for key in self.dict.keys(): fh.write(key + ':\t' + str(self.dict[key]) + '\n') fh.close() def AddJobFile(self, msg = None): if msg: self.HandleMessage(msg) self.WriteJobFile() self.P4Run('add ' + config['jvpath'] + self.name) class P4Logger(P4Command): def __init__(self): P4Command.__init__(self) self.joblist = [] for line in self.P4Run('logger').readlines(): (type, job) = match(r'^\d+ (\S+) (.+)', line).groups() if (type == 'change') or (job in self.joblist): continue self.joblist.append(job) def CleanUp(self): counter = self.P4Run('counter logger').readline() self.P4Run('logger -c ' + counter[:-1] + ' -t logger') class P4JobVersion(P4Command): def __init__(self): P4Command.__init__(self) def InitialAdd(self): if self.P4Run('have ' + config['jvpath'] + '...').readline(): return 0 for line in self.P4Run('jobs').readlines(): jobname = match(r'(\S+) on', line).group(1) thisjob = P4Job(jobname) thisjob.AddJobFile() self.SubmitFiles() return 1 def SubmitFiles(self): newchange = [] for changeline in self.P4Run('change -o').readlines(): if count(changeline, "") > 0: changeline = ' Jobs altered/created by Job Versioning Daemon' newchange.append(changeline) self.P4Run('submit -i', 'w').writelines(newchange) def MainLoop(self): logger = P4Logger() for job in logger.joblist: thisjob = P4Job(job) thisjob.GetAction() if thisjob.action == 'error': thisjob.HandleMessage('Error with job ' + thisjob.name + \ '.\n Job probably created and deleted between runs.') continue if thisjob.action == 'delete': thisjob.DeleteJobFile('Delete job ' + thisjob.name) continue if thisjob.IsAuth(): if thisjob.action == 'add': thisjob.AddJobFile('Add job ' + thisjob.name) continue if thisjob.action == 'edit': thisjob.EditJob('Edit job ' + thisjob.name) continue if thisjob.action == 'add': if config['add']: thisjob.AddJobFile('Add job %s, created by unauthorised user!' % thisjob.name) continue thisjob.DeleteJob('Job %s created by unauthorised user, deleting job from Perforce.' % thisjob.name) continue thisjob.RevertJob('Job %s altered by an unauthorised user. Changes reverted.' % thisjob.name) del thisjob self.SubmitFiles() logger.CleanUp() del logger ########## ## Main ## ########## while config['repeat']: versioner = P4JobVersion() if not versioner.InitialAdd(): versioner.MainLoop() del versioner print 'Sleeping for %d seconds' % config['runinterval'] sleep(config['runinterval']) else: versioner = P4JobVersion() if not versioner.InitialAdd(): versioner.MainLoop()