# Perforce Defect Tracking Integration Project # # # CHECK_CONFIG.PY -- VALIDATE CONFIGURATION PARAMETERS # # Nick Barnes, Ravenbrook Limited, 2001-01-18 # # # 1. INTRODUCTION # # This module defines a set of functions that check that a parameter is # suitable for a particular purpose (e.g., using as an e-mail address). They # return if the parameter is suitable and raise a clear error message if it is # not. # # Checking the configuration is intended to: # # 1. Keep the support cost low by allowing administrators to identify and fix # incorrect configurations by themselves [Requirements, 35]; # # 2. Keep installation time low by finding problems with the configuration # early [Requirements, 63]. # # See job000048, job000075, job000165, job000168 and job000170 for problems # we've had with inadequate configuration checking. # # The intended readership of this document is project developers. # # This document is not confidential. import catalog import re import types error = "P4DTI configuration error" # 2. CHECKING FUNCTIONS # 2.1. Check that parameter is a boolean (0 or 1) def check_bool(config, name): param = getattr(config, name) if (param != 0 and param != 1): # "Configuration parameter '%s' must be 0 or 1." raise error, catalog.msg(200, name) # 2.2. Check that parameter is a date # # We require dates to be strings of the form "2001-03-14 12:34:56" [ISO 8601]. 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)) # 2.3. Check that parameter looks like an e-mail address # # [RFC 822] defines an 'addr-spec' as follows. # # addr-spec = local-part "@" domain # local-part = word *("." word) # word = atom # domain = sub-domain *("." sub-domain) # sub-domain = domain-ref # domain-ref = atom # atom = 1* # specials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" # / <"> / "." / "[" / "]" # # (A CHAR is an ASCII character.) # # This isn't the full story. "Structured field bodies" in RFC822 also # permit quoting, domain literals and comments. This code doesn't # recognise these features. So users of the P4DTI will have to get by # without. 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)) # 2.4. Check that parameter is a Python function def check_function(config, name): param = getattr(config, name) if type(param) not in [types.FunctionType, types.MethodType, types.BuiltinFunctionType, types.BuiltinMethodType]: # "Configuration parameter '%s' must be a function." raise error, catalog.msg(203, name) # 2.5. Check that parameter looks like a network host # # We don't do more than check that the parameter is a string. We could check # that it matches a regexp or we could look it up in DNS. But in fact it's not # a severe problem not fail to try to check that the host exists at this point, # because the P4DTI tries to connect to all hosts (defect tracker, Perforce, # SMTP) when it starts up so any problems will be found quickly. def check_host(config, name): param = getattr(config, name) check_string(config, name) # 2.6. Check that parameter is an integer def check_int(config, name): param = getattr(config, name) if not isinstance(param, types.IntType): # "Configuration parameter '%s' must be an integer." raise error, catalog.msg(204, name) # 2.7. Check that parameter is a list def check_list_of(config, name, type, typename): param = getattr(config, name) if not isinstance(param, types.ListType): # "Configuration parameter '%s' must be a list." raise error, catalog.msg(205, 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)) # 2.8. Check that parameter is a string def check_string(config, name): param = getattr(config, name) if not isinstance(param, types.StringType): # "Configuration parameter '%s' must be a string." raise error, catalog.msg(207, name) # 2.9. Check that parameter is a string or None # # This is used for optional configuration parameters. def check_string_or_none(config, name): param = getattr(config, name) if not (param == None or isinstance(param, types.StringType)): # "Configuration parameter '%s' must be None or a string." raise error, catalog.msg(208, name) # 2.10. Check that parameter is an identifier # # Replicator and Perforce server identifiers must be from 1 to 32 characters # long, start with a letter or underscore, and consist only of letters, numbers # and underscores. 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)) # 2.11. Check that parameters is suitable for use as a changelist URL # # A changelist URL must contain exactly one instance of the %d format # specifier. Any other percentage signs must be doubled. def check_changelist_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 "%d": found = 0 break if param[i] == 'd': found = found + 1 i = i + 1 if found != 1: # "Configuration parameter '%s' (value '%s') must contain exactly one # %%d format specifier, any number of doubled percents, but no other # format specifiers." raise error, catalog.msg(210, (name, param)) # 2.12. Check that parameter is suitable for use as a job URL # # A job URL must contain exactly one instance of the %s format # specifier. Any other percentage signs must be doubled. 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)) # A. REFERENCES # # [ISO 8601] "Representation of dates and times"; ISO; 1988-06-15. # # [Requirements] "Perforce Defect Tracking Integration Project # Requirements"; Gareth Rees; Ravenbrook Limited; 2000-05-24; # . # # [RFC 822] "Standard for the format of ARPA Internet text messages"; # David H Crocker; 1982-08-13; . # # # B. DOCUMENT HISTORY # # 2001-01-18 NB Moved from configure_bugzilla.py so we can share with other # DTs. # # 2001-01-23 GDR Extended check_email so that it checks RFC822 address syntax. # # 2001-02-04 GDR Alphabetized. Added check_date. # # 2001-02-16 NB Added check_function (for checking replicate_p parameter). # # 2001-03-02 RB Transferred copyright to Perforce under their license. # # 2001-03-12 GDR Use messages when raising errors. # # 2001-03-14 GDR Formatted as a document; added references to requirements. # The check_date function checks the range of the date components. # # 2001-03-15 GDR Added check_identifier. # # 2001-03-24 GDR Added check_changelist_url. # # 2001-07-09 NB Add check_job_url. # # 2001-11-21 GDR check_function() allows methods as well as ordinary # functions. Check functions now take the module as the first argument # instead of the parameter (this avoids duplicate of code and so reduces # incorrect error messages). # # # C. COPYRIGHT AND LICENCE # # This file is copyright (c) 2001 Perforce Software, Inc. All rights # reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # # $Id: //info.ravenbrook.com/project/p4dti/branch/2001-11-22/bugzilla-parameters/code/replicator/check_config.py#2 $