r"""
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]