def test_setup_logging_with_root_logger(self): """ Test setup_logging does not duplicate root logger handler. """ with mock.patch('logging.config.dictConfig') as dc: config = {'root': {'handlers': ['default']}} logging.setup_logging(config) dc.assert_called_once_with(DEFAULT_CONFIG)
def test_setup_logging_default(self): """ Test setup_logging with default configuration. """ with mock.patch('logging.config.dictConfig') as dc: config = {} logging.setup_logging(config) dc.assert_called_once_with(DEFAULT_CONFIG)
def test_setup_logging_with_formatter(self): """ Test setup_logging does not overwrite a custom formatter. """ with mock.patch('logging.config.dictConfig') as dc: config = { 'formatters': { 'default': { 'format': '%(asctime)s: %(message)s' } } } expected = deepcopy(DEFAULT_CONFIG) expected.update(config) logging.setup_logging(config) dc.assert_called_once_with(expected)
def test_setup_logging_with_handler(self): """ Test setup_logging does not overwrite a custom handler. """ with mock.patch('logging.config.dictConfig') as dc: config = { 'handlers': { 'default': { 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'default', 'filename': 'commissaire.log' } } } expected = deepcopy(DEFAULT_CONFIG) expected.update(config) logging.setup_logging(config) dc.assert_called_once_with(expected)
def read_config_file(path=None, default='/etc/commissaire/commissaire.conf'): """ Attempts to parse a configuration file, formatted as a JSON object. If a config file path is explicitly given, then failure to open the file will raise an IOError. Otherwise a default path is tried, but no IOError is raised on failure. If the file can be opened but not parsed, an exception is always raised. :param path: Full path to the config file, or None :type path: str or None :param default: The default file path to user :type default: str :returns: configuration content as a dictionary :rtype: dict :raises: IOError, TypeError, ValueError """ json_object = {} using_default = False # As with the fileinput module, replace '-' with sys.stdin. if path == '-': json_object = json.load(sys.stdin) else: if path is None: path = default using_default = True try: with open(path, 'r') as fp: json_object = json.load(fp) if using_default: print('Using configuration in {}'.format(path)) except IOError: if not using_default: raise if type(json_object) is not dict: raise TypeError('{}: File content must be a JSON object'.format(path)) # Recursively normalize the JSON member names. json_object = _normalize_member_names(json_object) # Process any logging configuration straight away. # This is NOT included in the returned dictionary. if 'logging' in json_object: setup_logging(json_object.pop('logging')) # Handle the debug log-level option straight away. # This is NOT included in the returned dictionary. if json_object.pop('debug', False): logging.getLogger().setLevel(logging.DEBUG) logging.info('Debugging messages enabled') # Special case: # # In the configuration file, the "authentication_plugins" member # can also be specified as a list of JSON objects. Each object must # have at least a 'name' member specifying the plugin module name. auth_plugins = json_object.get('authentication_plugins', []) configured_plugins = {} if auth_plugins and not isinstance(auth_plugins, list): raise ValueError('{}: "{}" must be a list. Not at {}.'.format( path, auth_plugins, type(auth_plugins))) for plugin in auth_plugins: if isinstance(plugin, dict): if 'name' not in plugin.keys(): raise ValueError('{}: "{}" is missing a "name" member'.format( path, plugin)) # Since it's valid we can parse it down into the # expected format for loading. configured_plugins[plugin.pop('name')] = plugin # Overwrite authentication_plugins with the configured_plugins json_object['authentication_plugins'] = configured_plugins # Special case: # # In the configuration file, the "storage_handlers" member can # be specified as a JSON object or a list of JSON objects. handler_key = 'storage_handlers' handler_list = json_object.get(handler_key) if isinstance(handler_list, dict): json_object[handler_key] = [handler_list] return json_object
def read_config_file(path=None, default=C.DEFAULT_CONFIGURATION_FILE): """ Attempts to parse configuration data, formatted as a JSON object. This first tries to read configuration from etcd, if an environment variable named ``ETCD_MACHINES`` is defined (a comma-delimited list of URLs). The etcd key is derived from the basename of the default configuration file (e.g. for ``/path/to/storage.conf`` the etcd key would be ``/commissaire/config/storage``). Failing that, the function then tries to read a local config file. If a config file path is explicitly given, then failure to open the file will raise an IOError. Otherwise a default path is tried, but no IOError is raised on failure. If the file can be opened but not parsed, an exception is always raised. :param path: Full path to the config file, or None :type path: str or None :param default: The default file path to user :type default: str :returns: configuration content as a dictionary :rtype: dict :raises: IOError, TypeError, ValueError """ json_object = {} using_default = False if 'ETCD_MACHINES' in os.environ: # Derive etcd key from the default file path. basename = os.path.basename(default).split('.', 1)[0] key = '/commissaire/config/' + basename _read_etcd_config_key(key, json_object) if not json_object: # As with the fileinput module, replace '-' with sys.stdin. if path == '-': json_object = json.load(sys.stdin) else: if path is None: path = default using_default = True try: with open(path, 'r') as fp: json_object = json.load(fp) if using_default: print('Using configuration in {}'.format(path)) except IOError: if not using_default: raise if not isinstance(json_object, dict): raise TypeError( '{}: File content must be a JSON object'.format(path)) # Recursively normalize the JSON member names. json_object = _normalize_member_names(json_object) # Process any logging configuration straight away. # This is NOT included in the returned dictionary. if 'logging' in json_object: setup_logging(json_object.pop('logging')) # Handle the debug log-level option straight away. # This is NOT included in the returned dictionary. if json_object.pop('debug', False): logging.getLogger().setLevel(logging.DEBUG) logging.info('Debugging messages enabled') # Special case: # # In the configuration file, the "authentication_plugins" member # can also be specified as a list of JSON objects. Each object must # have at least a 'name' member specifying the plugin module name. auth_plugins = json_object.get('authentication_plugins', []) configured_plugins = {} if auth_plugins and not isinstance(auth_plugins, list): raise ValueError( '{}: "{}" must be a list. Not at {}.'.format( path, auth_plugins, type(auth_plugins))) for plugin in auth_plugins: if isinstance(plugin, dict): if 'name' not in plugin.keys(): raise ValueError( '{}: "{}" is missing a "name" member'.format( path, plugin)) # Since it's valid we can parse it down into the # expected format for loading. configured_plugins[plugin.pop('name')] = plugin # Overwrite authentication_plugins with the configured_plugins json_object['authentication_plugins'] = configured_plugins # Special case: # # In the configuration file, the "storage_handlers" member can # be specified as a JSON object or a list of JSON objects. handler_key = 'storage_handlers' handler_list = json_object.get(handler_key) if isinstance(handler_list, dict): json_object[handler_key] = [handler_list] return json_object