Source code for insights.parsers.httpd_conf

"""
HttpdConf - files ``/etc/httpd/conf/httpd.conf`` and ``/etc/httpd/conf.d/*``
============================================================================

Parse the keyword-and-value-but-also-vaguely-XML of an Apache configuration
file.

Generally, each line is split on the first space into key and value, leading
and trailing space being ignored.

Sample (edited) httpd.conf file::

    ServerRoot "/etc/httpd"
    LoadModule auth_basic_module modules/mod_auth_basic.so
    LoadModule auth_digest_module modules/mod_auth_digest.so

    <Directory />
        Options FollowSymLinks
        AllowOverride None
    </Directory>

    <IfModule mod_mime_magic.c>
    #   MIMEMagicFile /usr/share/magic.mime
        MIMEMagicFile conf/magic
    </IfModule>

    ErrorLog "|/usr/sbin/httplog -z /var/log/httpd/error_log.%Y-%m-%d"

    SSLProtocol -ALL +SSLv3
    #SSLProtocol all -SSLv2

    NSSProtocol SSLV3 TLSV1.0
    #NSSProtocol ALL

    # prefork MPM
    <IfModule prefork.c>
    StartServers       8
    MinSpareServers    5
    MaxSpareServers   20
    ServerLimit      256
    MaxClients       256
    MaxRequestsPerChild  200
    </IfModule>

    # worker MPM
    <IfModule worker.c>
    StartServers         4
    MaxClients         300
    MinSpareThreads     25
    MaxSpareThreads     75
    ThreadsPerChild     25
    MaxRequestsPerChild  0
    </IfModule>

Examples:

    >>> httpd_conf['ServerRoot'][-1].value
    '/etc/httpd'
    >>> httpd_conf['LoadModule'][0].value
    'auth_basic_module modules/mod_auth_basic.so'
    >>> httpd_conf['LoadModule'][-1].value
    'auth_digest_module modules/mod_auth_digest.so'
    >>> httpd_conf['Directory', '/']['Options'][-1].value
    'FollowSymLinks'
    >>> type(httpd_conf[('IfModule','prefork.c')]) == type({})
    True
    >>> httpd_conf[('IfModule','mod_mime_magic.c')]
    {'MIMEMagicFile': [ParsedData(value='conf/magic', line='MIMEMagicFile conf/magic', section='IfModule', section_name='mod_mime_magic.c', file_name='path', file_path='/path')]}
    >>> httpd_conf[('IfModule','prefork.c')]['StartServers'][0].value
    '8'
    >>> 'ThreadsPerChild' in httpd_conf[('IfModule','prefork.c')]
    False
    >>> httpd_conf[('IfModule','worker.c')]['MaxRequestsPerChild'][-1].value
    '0'

"""
from collections import namedtuple

import re
from copy import deepcopy
from .. import Parser, parser, get_active_lines, LegacyItemAccess
from insights.specs import Specs
from insights.util import deprecated

ParsedData = namedtuple('ParsedData', ['value', 'line', 'section', 'section_name', 'file_name', 'file_path'])
"""namedtuple: Type for storing the parsed httpd configuration's directive information."""


[docs]@parser(Specs.httpd_conf) class HttpdConf(LegacyItemAccess, Parser): """ .. note:: This parser is deprecated, please use :py:class:`insights.combiners.httpd_conf.HttpdConfTree` instead. Get the key value pairs separated on the first space, ignoring leading and trailing spaces. If the file is ``httpd.conf``, it also stores first half, before ``IncludeOptional conf.d/*.conf`` line, and the rest, to the ``first_half`` and ``second_half`` attributes respectively. Attributes: data (dict): Dictionary of parsed data with key being the option and value a list of named tuples with the following properties: - ``value`` - the value of the keyword. - ``line`` - the complete line as found in the config file. The reason why it is a list is to store data for directives which can use selective overriding such as ``UserDir``. first_half (dict): Parsed data from main config file before inclusion of other files in the same format as ``data``. second_half (dict): Parsed data from main config file after inclusion of other files in the same format as ``data``. """ def __init__(self, *args, **kwargs): deprecated(HttpdConf, "Import HttpdConfTree from insights.combiner.httpd_conf instead.") self.data = {} self.first_half = {} self.second_half = {} super(HttpdConf, self).__init__(*args, **kwargs)
[docs] def parse_content(self, content): def add_to_dict_list(dictionary, key, element): """ Utility function to create a dictionary of lists instead of using defaultdict, because rule would be able to unknowingly modify defaultdict structures. Args: dictionary (dict): The changed dictionary. key (str): The dictionary key to be changed. If it is in the dictionary, ``element`` is going to be appended to ``dictionary[key]`` list. If the ``key`` is not in the dictionary, it is created so that ``dictionary[key] = [element]``. element (Object): A value to be appended to the list under ``dictionary[key]``. """ if key not in dictionary: dictionary[key] = [element] else: dictionary[key].append(element) where_to_store = self.first_half # Set which part of file is the parser at # Flag to be used for different parsing of the main config file main_config = self.file_name == 'httpd.conf' section = [] # Can be treated as a stack for line in get_active_lines(content): if main_config and where_to_store is not self.second_half: # Dividing line looks like 'IncludeOptional conf.d/*.conf' if re.search(r'^\s*IncludeOptional\s+conf\.d', line): where_to_store = self.second_half # new section start if line.startswith('<') and not line.startswith('</'): splits = line.strip('<>').split(None, 1) section.append(((splits[0], splits[1] if len(splits) == 2 else ''), {})) # one section end elif line.startswith('</'): sec, pd = section.pop() # for nested section if section: if sec not in section[-1][-1]: section[-1][-1][sec] = {} dict_deep_merge(section[-1][-1][sec], pd) else: if sec not in self.data: self.data[sec] = {} if main_config: where_to_store[sec] = {} dict_deep_merge(self.data[sec], pd) if main_config: dict_deep_merge(where_to_store[sec], pd) else: try: option, value = [s.strip() for s in line.split(None, 1)] except ValueError: continue # Skip lines which are not 'Option Value' value = value.strip('\'"') if section: cur_sec = section[-1][0] parsed_data = ParsedData(value, line, cur_sec[0], cur_sec[1], self.file_name, self.file_path) # before: section = [(('IfModule', 'worker.c'), {})] add_to_dict_list(section[-1][-1], option, parsed_data) # after: section = [(('IfModule', 'worker.c'), [{'MaxClients': (256, 'MaxClients 256')}])] else: parsed_data = ParsedData(value, line, None, None, self.file_name, self.file_path) add_to_dict_list(self.data, option, parsed_data) if main_config: add_to_dict_list(where_to_store, option, parsed_data)
[docs]def dict_deep_merge(tgt, src): """ Utility function to merge the source dictionary `src` to the target dictionary recursively Note: The type of the values in the dictionary can only be `dict` or `list` Parameters: tgt (dict): The target dictionary src (dict): The source dictionary """ for k, v in src.items(): if k in tgt: if isinstance(tgt[k], dict) and isinstance(v, dict): dict_deep_merge(tgt[k], v) else: tgt[k].extend(deepcopy(v)) else: tgt[k] = deepcopy(v)