Source code for insights.combiners.journald_conf

"""
Journald configuration
======================

Combiner for parsing of journald configuration. man journald.conf describes where various
journald config files can reside and how they take precedence one over another. The combiner
implements the logic and provides an interface for querying active settings.

The journald.conf file is a key=value file with hash comments.

The parsers this combiner uses process only active settings (lines that are not commented out). The
resulting settings (after being processed by the precedence evaluation algorithm) are then provided
by the `get_active_settings_value` method and `active_settings` dictionary and by the
`get_active_setting_value_and_file_name` method and `active_settings_with_file_name` dictionary.

Options that are commented out are not returned - a rule using this parser has to be aware of which
default value is assumed by systemd if the particular option is not specified.

Priority from lowest to highest:

* built-in defaults (the same as the default commented entries in /etc/systemd/journald.conf)
* /etc/systemd/journald.conf
* \*.conf in whatever directory in lexicographic order from lowest to highest
* if two \*.conf files with the same name are both in /usr/lib and /etc, the file in /etc wholly
  overwrites the file in /usr/lib

from man journald.conf in RHEL 7.3:

CONFIGURATION DIRECTORIES AND PRECEDENCE

       Default configuration is defined during compilation, so a configuration
       file is only needed when it is necessary to deviate from those
       defaults. By default the configuration file in /etc/systemd/ contains
       commented out entries showing the defaults as a guide to the
       administrator. This file can be edited to create local overrides.

       When packages need to customize the configuration, they can install
       configuration snippets in /usr/lib/systemd/\*.conf.d/. Files in /etc/
       are reserved for the local administrator, who may use this logic to
       override the configuration files installed by vendor packages. The main
       configuration file is read before any of the configuration directories,
       and has the lowest precedence; entries in a file in any configuration
       directory override entries in the single configuration file. Files in
       the \*.conf.d/ configuration subdirectories are sorted by their filename
       in lexicographic order, regardless of which of the subdirectories they
       reside in. If multiple files specify the same option, the entry in the
       file with the lexicographically latest name takes precedence. It is
       recommended to prefix all filenames in those subdirectories with a
       two-digit number and a dash, to simplify the ordering of the files.

       To disable a configuration file supplied by the vendor, the recommended
       way is to place a symlink to /dev/null in the configuration directory
       in /etc/, with the same filename as the vendor configuration file.

Examples:

    >>> conf = shared[JournaldConfAll]
    >>> conf.get_active_setting_value('Storage')
    'auto'
    >>> 'Storage' in conf.active_settings_with_file_name
    True
    >>> conf.get_active_setting_value_and_file_name('Storage')
    ('auto', '/etc/systemd/journald.conf')
"""

from insights.core.plugins import combiner
from insights.parsers.journald_conf import EtcJournaldConf, EtcJournaldConfD, UsrJournaldConfD


# TODO - further insights work - convert this to a generic option & file priority evaluator for
#        other combiners.

[docs] @combiner(EtcJournaldConf, optional=[EtcJournaldConfD, UsrJournaldConfD]) class JournaldConfAll(object): """ Combiner for accessing files from the parsers EtcJournaldConf, EtcJournaldConfD, UsrJournaldConfD and evaluating effective active settings based on the rules of file priority and file shadowing as described in man journald.conf. Can be later refactored to a combiner for parsing all configuration files with key=option lines, like journald files. Rules of evaluation: * Files from EtcJournaldConfD wholly shadow/overwrite files from UsrJournaldConfD with identical names. * Files ordered by name from lowest priority to highest (a.conf has lower priority than b.conf). * Option values overwritten by the file with the highest priority. * The one central file has either the lowest priority or the highest priority, based on the central_file_lowest_prio argument. That is: * An entire file in UsrJournaldConfD is overwritten by a same-named file from EtcJournaldConfD. * A single option value is overwritten when another file with a higher priority has an option with the same option name. Example of file precedence:: /etc/systemd/journald.conf: key0=value0 key1=value1 /usr/lib/systemd/journald.conf.d/a.conf: key2=value2 key3=value3 key4=value4 key1=value5 /usr/lib/systemd/journald.conf.d/b.conf: key5=value6 key6=value7 key1=value8 key2=value9 key4=value10 /usr/lib/systemd/journald.conf.d/c.conf: key7=value11 key5=value12 key1=value13 /etc/systemd/journald.conf.d/b.conf: key1=value14 key5=value15 the resulting configuration: key0=value0 key1=value13 # c.conf has highest priority key2=value2 # b.conf from /usr is shadowed by b.conf from /etc so value from a.conf is used key3=value3 key4=value4 # b.conf from /usr is shadowed by b.conf from /etc so value from a.conf is used key5=value12 # c.conf has higher priority than b.conf # key6 doesn't exist because b.conf from /usr is shadowed by b.conf from /etc key7=value11 """ def __init__(self, journal_conf, journal_conf_d, usr_journal_conf_d): # preparation for future possible refactoring into a more general combiner central_file_lowest_prio = True # comments in this method describe journald configuration; it should work for similar ones etc_confd = {} # parser instances indexed by file name usr_confd = {} # parser instances indexed by file name if journal_conf_d: for parser_instance in journal_conf_d: etc_confd[parser_instance.file_name] = parser_instance if usr_journal_conf_d: for parser_instance in usr_journal_conf_d: usr_confd[parser_instance.file_name] = parser_instance files_shadowed_not_used = set() # full file paths of files that are shadowed by others effective_confd = {} # deduplicated *.conf files, taking shadowing /usr by /etc into account for file_name, parser_instance in usr_confd.items(): effective_confd[file_name] = parser_instance # /etc/systemd/journald.conf.d/*.conf shadow /usr/lib/systemd/journald.conf.d/*.conf files # with the same name. The following loop overwrites these same-named files by their /etc # counterparts: for file_name, parser_instance in etc_confd.items(): if file_name in effective_confd: shadowed_file_name = effective_confd[file_name].file_path if shadowed_file_name: # empty and None file names are not added (that is invalid anyway) files_shadowed_not_used.add(shadowed_file_name) effective_confd[file_name] = parser_instance files_shadowed_not_used = sorted(files_shadowed_not_used) # deterministic behavior, sorted paths sorted_file_names = sorted(effective_confd.keys(), key=str) parsers_list = [effective_confd[file_name] for file_name in sorted_file_names] if central_file_lowest_prio: parsers_list = [journal_conf] + parsers_list else: parsers_list = parsers_list + [journal_conf] files_used_priority_order = [] # from lowest to highest priority, not including empty files # storing only the active values as (val, file_name), taking precedence rules into account resulting_options_with_file_name = {} # *.conf files from the lowest priority to the highest, so that the last value stays for parser_instance in parsers_list: if parser_instance.active_settings: # do not iterate if empty or None if parser_instance.file_path: # empty and None file names are not added (that is invalid anyway), see test_11 files_used_priority_order.append(parser_instance.file_path) for k, v in parser_instance.active_settings.items(): resulting_options_with_file_name[k] = (v, parser_instance.file_path) # not named simply as `active_settings` so as not to confuse the contents with the # `active_settings` dictionary in parsers/journald_conf.py # (dict[str, str] vs. dict[str, tuple[str, str]]) self.active_settings_with_file_name = resulting_options_with_file_name # Not saving directly to self so that if this function fails in the middle, nothing is saved # for the offshoot chance that an exception would be swallowed and the invalid instance # used - prevents incomplete data from being used. self.files_shadowed_not_used = files_shadowed_not_used self.files_used_priority_order = files_used_priority_order super(JournaldConfAll, self).__init__()
[docs] def get_active_setting_value(self, setting_name): """ Access active setting value by setting name. Args: setting_name (string): Setting name """ return self.active_settings_with_file_name[setting_name][0]
[docs] def get_active_setting_value_and_file_name(self, setting_name): """ Access active setting value by setting name. Returns the active setting value and file name of the file in which it is defined. Other files that also specify the setting but are shadowed are ignored and not reported. Args: setting_name (string): Setting name Returns: tuple[str, str]: setting value, file name """ return self.active_settings_with_file_name[setting_name]