def configuration(config): # Check Bugzilla specific configuration parameters. check_config.check_string_or_none(config, 'bugzilla_directory') check_config.check_host(config, 'dbms_host') check_config.check_int(config, 'dbms_port') check_config.check_string(config, 'dbms_database') check_config.check_string(config, 'dbms_user') check_config.check_string(config, 'dbms_password') check_config.check_string(config, 'migrated_user_password') check_config.check_list_of(config, 'migrated_user_groups', types.StringType, 'strings') check_config.check_list_of(config, 'replicated_fields', types.StringType, 'strings') check_config.check_list_of(config, 'omitted_fields', types.StringType, 'strings') check_config.check_list_of_string_pairs(config, 'field_names') check_bugzilla_directory(config) check_field_lists(config) # Handle logger. We need a list of logger objects: log_params = { 'priority': config.log_level, 'max_length': config.log_max_message_length, } # The log messages should go to (up to) three places: # 1. to standard output (if running from a command line); loggers = [] if config.use_stdout_log: loggers.append(apply(logger.file_logger, (), log_params)) # 2. to the file named by the log_file configuration parameter (if # not None); if config.log_file != None: loggers.append(apply(logger.file_logger, (open(config.log_file, "a"),), log_params)) # 3. to the Windows event log (if use_windows_event_log is true). if os.name == 'nt' and config.use_windows_event_log: loggers.append(apply(logger.win32_event_logger, (config.rid,), log_params)) # 4. to the Unix syslog (if use_system_log is true). if os.name == 'posix' and config.use_system_log: loggers.append(apply(logger.sys_logger, (), log_params)) # now a single logger object which logs to all of the logger objects # in our list: config.logger = logger.multi_logger(loggers) # Open a connection to the Bugzilla database. This makes a DB-API # v2.0 connection object. To work with a database other than # MySQL, change this to make an appropriate connection object. # Note that in that case changes are also needed in bugzilla.py # where we deal with MySQL-specific types such as tinyint. db = mysqldb_support.connect(config) # Make a Bugzilla DB object. Note that this same object is used # subsequently by the replicator itself. config.bugzilla = bugzilla.bugzilla(db, config) # Get the types of the 'bugs' table from Bugzilla bugs_types = config.bugzilla.get_types('bugs') # Check field names against Bugzilla database and construct # a map, Bugzilla field name to Perforce field name. fields_bz_to_p4 = get_fields_bz_to_p4(config, bugs_types) user_name_length = get_user_name_length(config) # strict user translator doesn't allow unknown users strict_user_translator = dt_bugzilla.user_translator( config.replicator_address, config.p4_user, allow_unknown = 0) # lax user translator does allow unknown users lax_user_translator = dt_bugzilla.user_translator( config.replicator_address, config.p4_user, allow_unknown = 1) # p4_fields maps Bugzilla field name to the jobspec data (number, # name, type, length, dispositon, preset, values, help text, # translator). The fields Job and Date are special: they are not # replicated from Bugzilla but are required by Perforce, so we # have them here. Note that their help text is given (the other # help texts will be obtained from the bz_field_map). p4_fields = { \ '(JOB)': ( 101, 'Job', 'word', 32, 'required', None, None, "The job name.", None ), '(DATE)': ( 104, 'Date', 'date', 20, 'always', '$now', None, "The date this job was last modified.", None ), # P4DTI fields: '(FILESPECS)': ( 191, 'P4DTI-filespecs', 'text', 0, 'optional', None, None, "Associated filespecs.", None ), '(RID)': ( 192, 'P4DTI-rid', 'word', 32, 'required', 'None', None, "P4DTI replicator identifier. Do not edit!", None ), '(ISSUE)': ( 193, 'P4DTI-issue-id', 'word', 32, 'required', 'None', None, "Bugzilla issue database identifier. Do not " "edit!", None ), '(USER)': ( 194, 'P4DTI-user', 'word', 32, 'always', '$user', None, "Last user to edit this job. You can't edit " "this!", None ), } if fields_bz_to_p4.has_key('bug_status'): if bugs_types['bug_status']['type'] != 'enum': # "The 'bug_status' column of Bugzilla's 'bugs' table is not an # enum type." raise error, catalog.msg(308) # Make a list of (Bugzilla state, Perforce state) pairs. state_pairs = make_state_pairs(bugs_types['bug_status']['values'], config.closed_state) # Work out the legal values of the State field in the jobspec. Note # that "closed" must be a legal state because "p4 fix -c CHANGE # JOBNAME" always sets the State to "closed" even if "closed" is not # a legal value. See job000225. legal_states = map((lambda x: x[1]), state_pairs) if 'closed' not in legal_states: legal_states.append('closed') state_values = string.join(legal_states, '/') p4_fields['bug_status'] = ( 102, fields_bz_to_p4['bug_status'], 'select', bugs_types['bug_status']['length'], 'required', state_pairs[0][1], state_values, bz_field_map['bug_status'][1], dt_bugzilla.status_translator(state_pairs)) if fields_bz_to_p4.has_key('assigned_to'): p4_fields['assigned_to'] = ( 103, fields_bz_to_p4['assigned_to'], 'word', user_name_length, 'required', '$user', None, bz_field_map['assigned_to'][1], strict_user_translator) if fields_bz_to_p4.has_key('short_desc'): p4_fields['short_desc'] = ( 105, fields_bz_to_p4['short_desc'], 'text', bugs_types['short_desc']['length'], 'required', '$blank', None, bz_field_map['short_desc'][1], dt_bugzilla.text_translator() ) if fields_bz_to_p4.has_key('resolution'): if bugs_types['resolution']['type'] != 'enum': # "The 'resolution' column of Bugzilla's 'bugs' table is not an # enum type." raise error, catalog.msg(309) if not 'FIXED' in bugs_types['resolution']['values']: # "The 'resolution' column of Bugzilla's 'bugs' table does not # have a 'FIXED' value." raise error, catalog.msg(310) # Make a list of possible resolutions. (resolutions, default_resolution) = translate_enum('resolution', bugs_types['resolution']) p4_fields['resolution'] = ( 106, fields_bz_to_p4['resolution'], 'select', bugs_types['resolution']['length'], 'required', default_resolution, resolutions, bz_field_map['resolution'][1], enum_translator) # Additional replicated fields will be sequential from this field id. p4_field_id = 110 # Go through the replicated_fields list, build structures and add # them to p4_fields. for bz_field in config.replicated_fields: p4_field = fields_bz_to_p4[bz_field] p4_fields[bz_field] = ((p4_field_id, ) + make_p4_field_spec(bz_field, p4_field, bugs_types[bz_field], user_name_length, strict_user_translator)) p4_field_id = p4_field_id + 1 if p4_field_id >= 191: # "Too many fields to replicate: Perforce jobs can contain # only 99 fields." raise error, catalog.msg(317) comment = ("# A Perforce Job Specification automatically " "produced by the\n" "# Perforce Defect Tracking Integration\n") jobspec = (comment, p4_fields.values()) # Set configuration parameters needed by dt_bugzilla. config.append_only_fields = append_only_fields config.read_only_fields = read_only_fields config.jobname_function = lambda bug: 'bug%d' % bug['bug_id'] # Set configuration parameters needed by the replicator. config.date_translator = dt_bugzilla.date_translator() config.job_owner_field = fields_bz_to_p4.get('assigned_to', 'User') config.job_status_field = fields_bz_to_p4.get('bug_status', 'Status') config.job_date_field = 'Date' config.jobspec = jobspec config.prepare_issue_advanced = prepare_issue_advanced config.text_translator = dt_bugzilla.text_translator() config.translate_jobspec_advanced = translate_jobspec_advanced config.user_translator = lax_user_translator # The field_map parameter is a list of triples (Bugzilla database # field name, Perforce field name, translator) required by the # replicator. We use the filter to remove the fields that aren't # replicated: these have no translator. config.field_map = \ map(lambda item: (item[0], item[1][1], item[1][8]), filter(lambda item: item[1][8] != None, p4_fields.items())) return config
def make_p4_field_spec(bz_field, p4_field, bz_type, user_name_length, strict_user_translator): if bz_field_map.has_key(bz_field): p4_comment = bz_field_map[bz_field][1] else: p4_comment = None if p4_comment is None: p4_comment = ("Bugzilla's '%s' field" % bz_field) bz_type_class = bz_type['type'] p4_values = None p4_length = None # if there is a default, use it. if bz_type.get('default'): p4_class = 'default' p4_default = bz_type['default'] else: p4_class = 'optional' p4_default = None # Figure out the Perforce types, lengths, values, and default, and # the translator. # Maybe this should be table-driven. if bz_type_class == 'float': # "Field '%s' specified in 'replicated_fields' list has # floating-point type: this is not yet supported by P4DTI." raise error, catalog.msg(315, bz_field) elif bz_type_class == 'user': p4_type = 'word' p4_length = user_name_length trans = strict_user_translator elif bz_type_class == 'enum': p4_type = 'select' p4_values, p4_default = translate_enum(bz_field, bz_type) trans = enum_translator elif bz_type_class == 'int': p4_type = 'word' trans = dt_bugzilla.int_translator() elif bz_type_class == 'date': p4_type = 'date' p4_length = 20 trans = dt_bugzilla.date_translator() elif bz_type_class == 'timestamp': p4_type = 'date' p4_length = 20 trans = dt_bugzilla.timestamp_translator() elif bz_type_class == 'text': p4_type = 'text' trans = dt_bugzilla.text_translator() else: # "Field '%s' specified in 'replicated_fields' list has type # '%s': this is not yet supported by P4DTI." raise error, catalog.msg(314, (bz_field, bz_type['sql_type'])) # "p4 -G" uses the field "code" to indicate whether the Perforce # command succeeded or failed. See job000003. if p4_field == 'code': # "You can't have a field called 'code' in the Perforce # jobspec." raise error, catalog.msg(316) # Fixed-length fields get the length from Bugzilla. if p4_length == None: p4_length = bz_type['length'] if bz_field in read_only_fields: p4_comment = (p4_comment + " DO NOT MODIFY.") if bz_field in append_only_fields: p4_comment = (p4_comment + " ONLY MODIFY BY APPENDING.") return ( p4_field, p4_type, p4_length, p4_class, p4_default, p4_values, p4_comment, trans, )