#!/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. import os, re, marshal 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'} 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 os.popen(self.command + args, mode) def P4Marshal(self, args): return marshal.load(os.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) = re.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 = re.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': 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() # To Do List: # # 1) Add email notification on the message handling rather then just outputing # to stdout. # 2) Resolve what should happen to jobs created by unauthorised users. Should # they be deleted? Should they be left as they are and an email notification # sent? Should they be flagged somehow in their description? # Currently my favorite option would be to create the job file, then immediately # delete it and send a mail with the job details to the relevent people.
# | 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. |