#!/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 expect it to before turning it loose on your live system since # some of the operation it performs can be destructive, deleting Perforce # jobs for example. # # 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 seconds 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. # # 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 (eg. 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. # # Please bear in mind that the one operation this script can have no control # over is unauthorised users deleting jobs from Perforce. This is because by # the time the daemon comes to check the job, there is no job to check!! # In this case the daemon will log this fact to the command line and the log # file and mark the head rev of the versioned job file as deleted. This way # you could manually reinstate the Perforce job from the versioned file if # you wanted to. # # # 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 write permission on the area of # the depot where the versioned job files will be stored, ie. the "depotpath". # # 4) The "authorised" variable contains a list of the names of users who are # authorised to alter jobs. To have multiple users authorised to alter jobs # then include them all in the "authorised" variable, like so: # 'authorised': ['steve','admin','perforce'] # # 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. # # # 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). # revert a job altered by an unauthorised user should be # reverted (revert=1) in the Perforce job or ignored (revert=0). # I the changes are ignored in the Perforce job then the versioned # file which contains the job details will NOT be updated to reflect # the new contents of the job. This will only happen when the job is # altered by an authorised user. 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, 'revert': 0} class P4Command: def __init__(self): self.command = config['p4'] + \ ' -u ' + config['user'] + \ ' -c ' + 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 stdout and 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 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 + '\n' 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() def DeleteJob(self, msg = None): if msg: msg = msg + '\n\n' for key in self.dict.keys: if key == 'code': continue msg = msg + key + ': ' + self.dict[key] + '\n' 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, "<enter description here>") > 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 ' + thisjob.name + \ ' created by unauthorised user!') continue thisjob.DeleteJob('Job ' + thisjob.name + ' created by' + \ ' unauthorised user.\n Deleting job from Perforce.') continue if config['revert']: thisjob.RevertJob('Job ' + thisjob.name + ' altered by an ' + \ 'unauthorised user. Changes reverted.') continue thisjob.HandleMessage('Job ' + thisjob.name + ' altered by an ' + \ 'unauthorised user. Changes ignored in job file.') 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()
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#10 | 1322 | Steve Borrett | Only submit if we need to, so check "p4 opened" before trying the submit. | ||
#9 | 1321 | Steve Borrett |
Altered the comments, since they were a bit excessive. If you really want to see a version with every line of code fully commented then you should look at the previous revision. |
||
#8 | 1269 | Steve Borrett |
Fixed a minor bug in the P4Logger class. There was an "if" with a capital "I". |
||
#7 | 1252 | Steve Borrett |
Moved the logger counter checking to the constructor of the P4Logger class so that there is less chance of more events occuring before the counters value is recorded and the logger reset in the CleanUp() method. Added the config variable 'alwaysprint'. This will mean that all messages are always written to STDOUT, even if a log file is specified. We now write the entire dictionary to the message in the DeleteJob() method of the P4Job class instead of ignoring the 'code' field. Because, why not... Finished commenting the code and config variables and did some minor rearranging of the code layout to make things more obvious and readable. |
||
#6 | 1202 | Steve Borrett |
Moved the code which decides what action to take with a job to a TakeAction() method of the P4Job class. Added a call to the above function to the MainLoop() function of the of the P4Versioner class Commented the code in the main program body and the P4Versioner class and it's methods. |
||
#5 | 1201 | Steve Borrett |
Added a "revert" config variable which allows users to either ignore alterations to jobs by unauthorised users, or revert thier changes to the previous state. Reformatted the code to fit in an 80 column screen. Altered the P4Job.DeleteJob() function to include the job details in the logged message before the job is deleted from Perforce. Added + '\n' to the end of the print statement in P4Job.HandleMessage() to ensure that logged messages always end with a LF. Made some changes and additions to the comments at the start of the script. |
||
#4 | 1082 | Steve Borrett |
Alter imports to only import required functions. Alter code to reflect above import changes. Add config variable "add" to determine the action to be taken when an unauthorised user creates a job. Altered code to reflect the above. |
||
#3 | 1077 | Steve Borrett |
Reorganisation of code to better reflect class hierarchy. No functional alterations. |
||
#2 | 1076 | Steve Borrett |
An update to ensure all the latest fixes are in the public depot version of the script, since I am mostly controling this on my local Perforce server. |
||
#1 | 1074 | Steve Borrett |
Delete original job versioning script and add in the new improved version 2 script. This script has vast differences from the original and is now totally class based. As far as I can tell all of the issues in the original script have been resolved in this incarnation, including such things as being able to delete jobs from Perforce. The job file will simply be "p4 delete"ed and the job itself will be deleted from Perforce. This means that although there is still no protections possible on being able to delete jobs, there is a record of what they were prior to their deletion. One thing to be wary of with this version of the script is that jobs created by an unauthorised user are currently simply removed from Perforce and no trace of them exists. This is possibly a little extreme but for now is the easist solution to implement. See the to do list at the bottom of the script for things which I am currently working on for this script. |