def check_bugzilla_directory(config): dir = config.bugzilla_directory if dir is None: config.bugmail_command = None return # strip any trailing / character if (len(dir) > 1 and (dir[-1:] == '/' or dir[-1:] == '\\')): dir = dir[:-1] if not os.path.isdir(dir): # "Configuration parameter 'bugzilla_directory' does not # name a directory." raise error, catalog.msg(303) config.bugzilla_directory = dir # Check processmail if os.name == 'posix': processmail = 'processmail' elif os.name == 'nt': # processmail name is different on Windows processmail = 'processmail.pl' if os.access(os.path.join(dir, processmail), os.X_OK): config.bugmail_command = processmail else: bugmail = os.path.join('contrib', 'sendbugmail.pl') if os.access(os.path.join(dir, bugmail), os.R_OK): config.bugmail_command = bugmail else: # "Configuration parameter 'bugzilla_directory' does not # name a directory containing a mail-processing script." raise error, catalog.msg(304)
def check_list_of(config, name, type, typename): check_list(config, name) param = getattr(config, name) for item in param: if not isinstance(item, type): # "Configuration parameter '%s' must be a list of %s." raise error, catalog.msg(206, (name, typename))
def extend_jobspec(self, description, force = 0): current_jobspec = self.get_jobspec() comment, field_list = current_jobspec _, new_fields = description new_fields.sort(self.compare_field_by_number) current_fields = self.jobspec_map(current_jobspec, 1) new_field_names = map(lambda x: x[1], new_fields) field_numbers = map(lambda x: x[0], field_list) # counters for finding a free field number. free_number_p4dti = 194 free_number = 106 for field_spec in new_fields: field = field_spec[1] if current_fields.has_key(field): current_spec = current_fields[field] if (current_spec[2] != field_spec[2] or current_spec[3] != field_spec[3] or current_spec[4] != field_spec[4] or current_spec[5] != field_spec[5] or current_spec[6] != field_spec[6]): if force: # "Forcing replacement of field '%s' in jobspec." self.log(727, field) current_fields[field] = ((current_spec[0],) + field_spec[1:7] + (None,None,)) else: # "Retaining field '%s' in jobspec despite change." self.log(728, field) else: # "No change to field '%s' in jobspec." self.log(733, field) else: if field_spec[0] in field_numbers: # Field numbering clashes; find a free field number. if field[0:6] == 'P4DTI-': while free_number_p4dti in field_numbers: free_number_p4dti = free_number_p4dti - 1 number = free_number_p4dti else: while free_number in field_numbers: free_number = free_number + 1 number = free_number if free_number >= free_number_p4dti: # "Too many fields in jobspec." raise error, catalog.msg(730) field_spec = (number, ) + field_spec[1:] # "Adding field '%s' to jobspec." self.log(729, field) current_fields[field] = field_spec field_numbers.append(field_spec[0]) # Also report jobspec names fields not touched. for field in current_fields.keys(): if field not in new_field_names: # "Retaining unknown field '%s' in jobspec." self.log(732, field) self.install_jobspec((comment, current_fields.values()))
def SvcDoRun(self): # "The P4DTI service has started." self.log(catalog.msg(1011)) try: self.run_logging_errors() finally: # "The P4DTI service has halted." self.log(catalog.msg(1012))
def check_function(config, name): param = getattr(config, name) if (type(param) not in [types.FunctionType, types.MethodType, types.BuiltinFunctionType, types.BuiltinMethodType] and not hasattr(param, '__call__')): # "Configuration parameter '%s' must be a function." raise error, catalog.msg(203, name)
def failure_context(self): if self.file is sys.stdout: # "An attempt to write a log message to standard output # failed." return catalog.msg(1017) else: destination = getattr(self.file, 'name', str(self.file)) # "An attempt to write a log message to %s failed." return catalog.msg(1018, destination)
def compare_field_by_number(self, x, y): if x[0] < y[0]: return -1 elif x[0] > y[0]: return 1 else: # "Jobspec fields '%s' and '%s' have the same # number %d." raise error, catalog.msg(710, (x[1], y[1], x[0]))
def check_identifier(config, name): param = getattr(config, name) check_string(config, name) if (len(param) < 1 or len(param) > 32 or not re.match('^[A-Za-z_][A-Za-z_0-9]*$', param)): # "Configuration parameter '%s' (value '%s') must be from 1 to # 32 characters long, start with a letter or number, and consist # of letters, numbers and underscores only." raise error, catalog.msg(209, (name, param))
def check_list_of_string_pairs(config, name): check_list(config, name) param = getattr(config, name) for item in param: if not (isinstance(item, types.TupleType) and len(item) == 2 and isinstance(item[0], types.StringType) and isinstance(item[1], types.StringType)): # "Configuration parameter '%s' must be a list of pairs of strings." raise error, catalog.msg(212, name)
def check_email(config, name): param = getattr(config, name) check_string(config, name) atom_re = "[!#$%&'*+\\-/0-9=?A-Z^_`a-z{|}~]+" email_re = "^%s(\\.%s)*@%s(\\.%s)*$" % (atom_re, atom_re, atom_re, atom_re) if not re.match(email_re, param): # "Configuration parameter '%s' (value '%s') is not a valid # e-mail address." raise error, catalog.msg(202, (name, param))
def popen_read_binary(command): if os.name == 'nt': mode = 'rb' elif os.name == 'posix': mode = 'r' else: # "The P4DTI does not support the operating # system '%s'." raise error, catalog.msg(1021, os.name) return os.popen(command, mode)
def make_state_pairs(states, closed_state): state_pairs = [] state_p4_to_dt = {} found_closed_state = 0 if closed_state != None: p4_closed_state = keyword_translator.translate_0_to_1( string.lower(closed_state)) # Perforce jobs can't have state "new" (this indicates a fresh job # and Perforce changes the state to "open"). Nor can they have # state "ignore", because that is used in the submit form to # indicate that a job shouldn't be fixed by the change. # # Unfortunately, "new" and "ignore" are common names for states in # defect trackers (the former is in the Bugzilla workflow and in # the default workflow in TeamTrack), so we don't disallow them, # but prefix them with 'bugzilla_'. Then we quit if two Bugzilla # states map to the same state in Perforce, ruling out the # unlikely situation that someone has a Bugzilla status of # 'BUGZILLA_CLOSED'. See job000141. prohibited_states = ['new', 'ignore'] prohibited_state_prefix = 'bugzilla_' for dt_state in states: p4_state = keyword_translator.translate_0_to_1( string.lower(dt_state)) if closed_state != None: if p4_state == p4_closed_state: p4_state = 'closed' found_closed_state = 1 elif p4_state == 'closed': p4_state = prohibited_state_prefix + p4_state if p4_state in prohibited_states: p4_state = prohibited_state_prefix + p4_state if (state_p4_to_dt.has_key(p4_state) and state_p4_to_dt[p4_state] != dt_state): # "Two Bugzilla states '%s' and '%s' map to the same # Perforce state '%s'." raise error, catalog.msg(300, (dt_state, state_p4_to_dt[p4_state], p4_state)) state_p4_to_dt[p4_state] = dt_state pair = (dt_state, p4_state) if pair not in state_pairs: state_pairs.append(pair) if closed_state != None and not found_closed_state: # "You specified the closed_state '%s', but there's no such # Bugzilla state." raise error, catalog.msg(301, closed_state) return state_pairs
def translate_enum(column, bz_type): if not bz_type['type'] == 'enum': # "The '%s' column of Bugzilla's 'bugs' table is not an enum # type." raise error, catalog.msg(302, column) values = bz_type['values'] values = map(enum_translator.translate_0_to_1, values) default = bz_type['default'] if default != None: default = enum_translator.translate_0_to_1(default) values = string.join(values,'/') return values, default
def get_user_name_length(config): # Get the types of the 'profiles' table from Bugzilla. In # particular we need to know the size of the 'login_name' column. profiles_types = config.bugzilla.get_types('profiles') if not profiles_types.has_key('login_name'): # "Bugzilla's table 'profiles' does not have a 'login_name' # column." raise error, catalog.msg(305) if profiles_types['login_name']['type'] != 'text': # "The 'login_name' column of Bugzilla's 'profiles' table does # not have a 'text' type." raise error, catalog.msg(306) return profiles_types['login_name']['length']
def check_date(config, name): param = getattr(config, name) check_string(config, name) date_re = "^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$" match = re.match(date_re, param) if match: year,month,date,hour,minute,second = map(int, match.groups()) if (1 <= month and month <= 12 and 1 <= date and date <= 31 and hour <= 23 and minute <= 59 and second <= 59): return # "Configuration parameter '%s' (value '%s') is not a valid date. # The right format is 'YYYY-MM-DD HH:MM:SS'." raise error, catalog.msg(201, (name, param))
def log_fatal_error(self, type, value): if value is not None: err = '%s: %s' % (type, value) else: err = str(type) # "Fatal error in P4DTI service: %s." self.log(catalog.msg(1010, err))
def check_changelevels(self): # client changelevel first. self.client_changelevel = 0 supported_client = 16895 (command, result, status) = self.run_p4_command('-V') match = re.search('Rev\\. [^/]+/[^/]+/[^/]+/([0-9]+)', result) if match: self.client_changelevel = int(match.group(1)) if self.client_changelevel < supported_client: # "Perforce client changelevel %d is not supported # by P4DTI. Client must be at changelevel %d or # above." raise error, catalog.msg(704, (self.client_changelevel, supported_client)) else: # "The command '%s' didn't report a recognizable version # number. Check your setting for the 'p4_client_executable' # parameter." raise error, catalog.msg(705, command) # now server changelevel. self.server_changelevel = 0 if self.port: command = '-p %s info' % self.port else: command = 'info' (command, result, status) = self.run_p4_command(command) if status: # "The Perforce client exited with error code %d. The # server might be down; the server address might be # incorrect; or your Perforce license might have expired." raise error, catalog.msg(707, status) match = re.search('Server version: ' '[^/]+/[^/]+/[^/]+/([0-9]+)', result) if match: self.server_changelevel = int(match.group(1)) else: # "The Perforce command 'p4 info' didn't report a # recognisable version." raise error, catalog.msg(835) if not self.supports('p4dti'): # "The Perforce server changelevel %d is not supported by # the P4DTI. See the P4DTI release notes for Perforce # server versions supported by the P4DTI." raise error, catalog.msg(834, self.server_changelevel)
def check_job_url(config, name): param = getattr(config, name) if param == None: return check_string(config, name) i = 0 found = 0 while i < len(param): if param[i] == '%': i = i + 1 if i >= len(param) or param[i] not in "%s": found = 0 break if param[i] == 's': found = found + 1 i = i + 1 if found != 1: # "Configuration parameter '%s' (value '%s') must contain # exactly one %%s format specifier, any number of doubled # percents, but no other format specifiers." raise error, catalog.msg(211, (name, param))
def check_jobs(): p4i = p4.p4(port = config.p4_port, client_executable = config.p4_client_executable, user = config.p4_user, password = config.p4_password) jobs = p4i.run('jobs') failures = 0 for j in jobs: try: p4i.run('job -o %s' % j['Job']) except p4.error, msg: # "Job '%s' doesn't match the jobspec:" print str(catalog.msg(1008, j['Job'])) print str(msg) failures = failures + 1
def get_fields_bz_to_p4(config, bugs_types): fields_bz_to_p4 = {} fields_p4_to_bz = {} # particular requested mappings field_names_bz_to_p4 = {} bz_fields = default_fields + config.replicated_fields for f in config.omitted_fields: bz_fields.remove(f) for (bz_field, p4_field) in config.field_names: field_names_bz_to_p4[bz_field] = p4_field for bz_field in bz_fields: if not bugs_types.has_key(bz_field): # "Bugzilla's table 'bugs' does not have a '%s' column." raise error, catalog.msg(307, bz_field) if field_names_bz_to_p4.has_key(bz_field): p4_field = field_names_bz_to_p4[bz_field] elif bz_field_map.has_key(bz_field): p4_field = bz_field_map[bz_field][0] else: desc = config.bugzilla.field_description(bz_field) if desc is None: desc = "bugzilla_" + bz_field p4_field = keyword_translator.translate_0_to_1(desc) if fields_p4_to_bz.has_key(p4_field): # "Bugzilla fields '%s' and '%s' both map to Perforce field '%s'." raise error, catalog.msg(324, (bz_field, fields_p4_to_bz[p4_field], p4_field)) fields_bz_to_p4[bz_field] = p4_field fields_p4_to_bz[p4_field] = bz_field return fields_bz_to_p4
def main(argv): # Things to do before an install. if len(argv) <= 1: # "Installing service to start automatically..." print catalog.msg(1013) argv = argv + '--startup auto install'.split() if argv[-1] == 'install': service_exe = win32serviceutil.LocatePythonServiceExe() cmd = '"%s" /register' % service_exe os.system(cmd) # Start a service. Construct a command line and pass it into # start_service. if argv[1] == 'start': arguments = [] controls = (('P4DTI_CONFIG', '--p4dti-config'), ('P4DTI_EVTLOG', '--p4dti-evtlog'), ('P4DTI_LOGLEVEL', '--p4dti-loglevel'), ('P4DTI_ADMINADDR', '--p4dti-adminaddr'), ) for key, arg in controls: if os.environ.has_key(key): arguments = arguments + [arg, os.environ[key]] return start_service(arguments) # Things to do before a remove. if argv[1] == 'remove': # "Ensuring service is stopped first..." print catalog.msg(1014) rc = action([argv[0]] + ['stop']) if rc == 0: pass elif rc == 1062: # "OK (can ignore that error). Proceed with the remove..." print catalog.msg(1015) else: return rc # Now proceed with the action. rc = action(argv) sys.stdout.flush() return rc
def check_jobspec(self, description): satisfactory = 1 _, wanted_fields = description actual_jobspec = self.get_jobspec() self.validate_jobspec(actual_jobspec) actual_fields = self.jobspec_map(actual_jobspec, 1) wanted_fields = self.jobspec_map(description, 1) # remove P4DTI fields, which are checked by validate_jobspec() for field in self.p4dti_fields.keys(): if actual_fields.has_key(field): del actual_fields[field] if wanted_fields.has_key(field): del wanted_fields[field] shared_fields = [] # check that all wanted fields are present. for field in wanted_fields.keys(): if actual_fields.has_key(field): shared_fields.append(field) else: # field is absent. # "Jobspec does not have field '%s'." self.log(716, field) satisfactory = 0 for field in shared_fields: # field is present actual_spec = actual_fields[field] wanted_spec = wanted_fields[field] del actual_fields[field] # check datatype actual_type = actual_spec[2] wanted_type = wanted_spec[2] if actual_type == wanted_type: # matching datatypes if actual_type == 'select': # select fields should have matching values. actual_values = string.split(actual_spec[6], '/') wanted_values = string.split(wanted_spec[6], '/') shared_values = [] for value in wanted_values: if value in actual_values: shared_values.append(value) for value in shared_values: actual_values.remove(value) wanted_values.remove(value) if wanted_values: if len(wanted_values) > 1: # "The jobspec does not allow values '%s' # in field '%s', so these values cannot be # replicated from the defect tracker." self.log(718, (string.join(wanted_values, '/'), field)) else: # "The jobspec does not allow value '%s' # in field '%s', so this value cannot be # replicated from the defect tracker." self.log(719, (wanted_values[0], field)) if actual_values: if len(actual_values) > 1: # "Field '%s' in the jobspec allows values # '%s', which cannot be replicated to the # defect tracker." self.log(720, (field, string.join(actual_values, '/'))) else: # "Field '%s' in the jobspec allows value # '%s', which cannot be replicated to the # defect tracker." self.log(721, (field, actual_values[0])) elif ((wanted_type == 'date' and (actual_type == 'word' or actual_type == 'select')) or (actual_type == 'date' and (wanted_type == 'word' or wanted_type == 'select'))): # "Field '%s' in the jobspec should be a '%s' field, # not '%s'. This field cannot be replicated to or # from the defect tracker." self.log(724, (field, wanted_type, actual_type)) satisfactory = 0 else: wanted_order = self.restriction_order[wanted_type] actual_order = self.restriction_order.get(actual_type, None) if actual_order is None: # "Jobspec field '%s' has unknown datatype '%s' # which may cause problems when replicating this # field." self.log(731, (field, actual_type)) elif wanted_order > actual_order: # "Jobspec field '%s' has a less restrictive # datatype ('%s' not '%s') which may cause # problems replicating this field to the defect # tracker." self.log(723, (field, actual_type, wanted_type)) else: # "Jobspec field '%s' has a more restrictive # datatype ('%s' not '%s') which may cause # problems replicating this field from the defect # tracker." self.log(722, (field, actual_type, wanted_type)) # check persistence if actual_spec[4] != wanted_spec[4]: # "Field '%s' in the jobspec should have persistence # '%s', not '%s'. There may be problems replicating # this field to or from the defect tracker." self.log(725, (field, wanted_spec[4], actual_spec[4])) if actual_fields: for field in actual_fields.keys(): # "Perforce job field '%s' will not be replicated to the # defect tracker." self.log(726, field) # Possibly should also check that some of the # Perforce-required fields are present. See the lengthy # comment below (under "jobspec_has_p4_fields"). if not satisfactory: # "Current jobspec cannot be used for replication." raise error, catalog.msg(717)
def validate_jobspec(self, jobspec): if not self.jobspec_has_p4dti_fields(jobspec): # "Jobspec does not support P4DTI." raise error, catalog.msg(715)
def log(self, id, args = ()): if self.logger: msg = catalog.msg(id, args) self.logger.log(msg)
def run(self, arguments, input = None, repeat = False): assert isinstance(arguments, basestring) assert input is None or isinstance(input, types.DictType) # Build a command line suitable for use with CMD.EXE on Windows # NT, or /bin/sh on POSIX. Make sure to quote the Perforce # command if it contains spaces. See job000049. if ' ' in self.client_executable: command_words = ['"%s"' % self.client_executable] else: command_words = [self.client_executable] command_words.append('-G') if self.port: command_words.extend(['-p', self.port]) if self.user: command_words.extend(['-u', self.user]) if self.password and not self.config_file: command_words.extend(['-P', self.password]) if self.client: command_words.extend(['-c', self.client]) if self.unicode: command_words.extend(['-C', 'utf8']) command_words.append(arguments.encode('utf8')) # Pass the input dictionary (if any) to Perforce. temp_filename = None if input: input = self.encode_dict(input) tempfile.template = 'p4dti_data' temp_filename = tempfile.mktemp() # Python marshalled dictionaries are binary, so use mode # 'wb'. temp_file = open(temp_filename, 'wb') self.marshal_dump_0(input, temp_file) temp_file.close() command_words.extend(['<', temp_filename]) # "Perforce input: '%s'." self.log(700, input) command = string.join(command_words, ' ') # "Perforce command: '%s'." self.log(701, command) stream = portable.popen_read_binary(command) # Read the results of the Perforce command. results = [] try: while 1: results.append(marshal.load(stream)) except EOFError: if temp_filename: os.remove(temp_filename) for r in results: if isinstance(r,dict): for (k,v) in r.items(): if isinstance(v, str): r[k] = v.decode(self.encoding, 'replace') # Check the exit status of the Perforce command, rather than # simply returning empty output when the command didn't run for # some reason (such as the Perforce server being down). This # code was inserted to resolve job job000158. RB 2000-12-14 exit_status = stream.close() if exit_status != None: # "Perforce status: '%s'." self.log(702, exit_status) # "Perforce results: '%s'." self.log(703, results) # Check for errors from Perforce (either errors returned in the # data, or errors signalled by the exit status, or both) and # raise a Python exception. # # Perforce signals an error by the presence of a 'code' key in # the dictionary output. (This isn't a totally reliable way to # spot an error in a Perforce command, because jobs can have # 'code' fields too. See job000003. However, the P4DTI makes # sure that its jobs don't have such a field.) if (len(results) == 1 and results[0].has_key('code') and results[0]['code'] == 'error'): msg = results[0]['data'].strip() if exit_status: if msg.find('Unicode') != -1: self.unicode = not(self.unicode) unicode_switch = (self.unicode and 'on') or 'off' if (not repeat): # "Perforce message '%s'. Switching Unicode # mode %s to retry." self.log(734, (msg, unicode_switch)) return self.run(arguments, input, repeat = True) else: # "Perforce message '%s'. Is P4CHARSET set with a # non-Unicode server? Reverting to Unicode mode %s." raise error, catalog.msg(736, (msg, unicode_switch)) else: # "%s The Perforce client exited with error code %d." raise error, catalog.msg(706, (msg, exit_status)) else: # "%s" raise error, catalog.msg(708, msg) elif exit_status: # "The Perforce client exited with error code %d. The # server might be down; the server address might be # incorrect; or your Perforce license might have expired." raise error, catalog.msg(707, exit_status) else: return results
# It should be used according to the instructions in section 9.2, # "Refreshing jobs in Perforce" of the P4DTI Administrator's Guide [RB # 2000-08-10]. # # The intended readership of this document is project developers. # # This document is not confidential. import catalog import sys if __name__ == '__main__': # "WARNING! This script will update all jobs in Perforce. Please # use it according to the instructions in section 9.2 of the P4DTI # Administrator's Guide. Are you sure you want to go ahead?" sys.stdout.write(catalog.msg(1002).wrap(79)) sys.stdout.write(' ') sys.stdout.flush() if sys.stdin.readline()[0] in 'yY': from init import r r.refresh_perforce_jobs() # A. REFERENCES # # [RB 2000-08-10] "Perforce Defect Tracking Integration Administrator's # Guide"; Richard Brooksby; Ravenbrook Limited; 2000-08-10; # <http://www.ravenbrook.com/project/p4dti/version/2.4/manual/ag/>. # # # B. DOCUMENT HISTORY
def broken(version, release, config): # "MySQLdb version '%s' (release '%s') detected. # This release is incompatible with the P4DTI." raise error, catalog.msg(1005, (version, release))
def unsupported_old(version, release, config): # "MySQLdb version '%s' (release '%s') detected. # This old release is not supported by the P4DTI, and may not # provide functions on which the P4DTI relies." config.logger.log(catalog.msg(1023, (version, release)))
def unsupported(version, release, config): # "MySQLdb version '%s' (release '%s') detected. # This release is not supported by the P4DTI, but may work." msg = catalog.msg(1006, (version, release)) config.logger.log(msg)
def deprecated_unicode(version, release, config): # "MySQLdb version '%s' (release '%s') detected. # This release is supported by the P4DTI, but deprecated. # Operation with Unicode text may be incorrect. # Future versions of the P4DTI may not support this release." config.logger.log(catalog.msg(1024, (version, release)))