# 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 not in [0,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]
and not hasattr(param, '__call__')):
# "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):
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):
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 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_list(config, name):
param = getattr(config, name)
if not isinstance(param, types.ListType):
# "Configuration parameter '%s' must be a list."
raise error, catalog.msg(205, name)
# 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).
#
# 2002-02-15 GDR Determine a function by the existence of a __call__
# method.
#
# 2003-11-25 NB Added check_list_of_string_pairs
#
#
# C. COPYRIGHT AND LICENSE
#
# 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/version/2.1/code/replicator/check_config.py#1 $