"""
NFS exports configuration
=========================
NFSExports and NFSExportsD provide a parsed output of the content of an exports
file as defined in ``man exports(5)``. The content is parsed into a
dictionary, where the key is the export path and the value is another
dictionary, where the key is the hostname and the value is the option list,
parsed into an actual list.
The default (``"-"``) hostname is not specially handled, nor are wildcards.
If export paths are defined multiple times in a file, only the first one is
parsed. All subsequent redefinitions are not parsed and the raw line is added
to the ``ignored_lines`` member.
All raw lines are kept in ``raw_lines``, which is a ``dict`` where the key is
the export path and the value is the stripped raw line.
Parsers included in this module are:
NFSExports - file ``nfs_exports``
---------------------------------
NFSExportsD - files in the ``nfs_exports.d`` directory
------------------------------------------------------
Sample content of the ``/etc/exports`` file::
/home/utcs/shared/ro @group(ro,sync) ins1.example.com(rw,sync,no_root_squash) ins2.example.com(rw,sync,no_root_squash)
/home/insights/shared/rw @group(rw,sync) ins1.example.com(rw,sync,no_root_squash) ins2.example.com(ro,sync,no_root_squash)
/home/insights/shared/special/all/mail @group(rw,sync,no_root_squash)
/home/insights/ins/special/all/config @group(ro,sync,no_root_squash) ins1.example.com(rw,sync,no_root_squash)
#/home/insights ins1.example.com(rw,sync,no_root_squash)
/home/example @group(rw,sync,root_squash) ins1.example.com(rw,sync,no_root_squash) ins2.example.com(rw,sync,no_root_squash)
# A duplicate host for this exported path
/home/example ins2.example.com(rw,sync,no_root_squash)
Examples:
>>> type(exports)
<class 'insights.parsers.nfs_exports.NFSExports'>
>>> type(exports.data) == type({})
True
>>> exports.raw_lines['/home/insights/shared/rw'] # List of lines that define this path
['/home/insights/shared/rw @group(rw,sync) ins1.example.com(rw,sync,no_root_squash) ins2.example.com(ro,sync,no_root_squash)']
>>> exports.raw_lines['/home/example'] # Lines are stored even if they contain duplicate hosts
['/home/example @group(rw,sync,root_squash) ins1.example.com(rw,sync,no_root_squash) ins2.example.com(rw,sync,no_root_squash)', '/home/example ins2.example.com(rw,sync,no_root_squash)']
>>> exports.ignored_exports
{'/home/example': {'ins2.example.com': ['rw', 'sync', 'no_root_squash']}}
>>> sorted(list(exports.all_options()))
['no_root_squash', 'ro', 'root_squash', 'rw', 'sync']
>>> sorted(list(exports.export_paths()))
['/home/example', '/home/insights/ins/special/all/config', '/home/insights/shared/rw', '/home/insights/shared/special/all/mail', '/home/utcs/shared/ro']
"""
from itertools import chain
from .. import Parser, parser
from . import get_active_lines
from insights.specs import Specs
[docs]class NFSExportsBase(Parser):
"""
Class to parse ``/etc/exports`` and ``/etc/exports.d/*.exports``.
Exports are stored keyed on the path of the export, and then the host
definition. The flags are stored as a list. NFS allows the same path
to be listed on multiple lines and in multiple files, but an exported
path can only have one definition for a given host.
Attributes:
data (dict): Key is export path, value is a dict, where the key is the
client host and the value is a list of options.
ignored_exports (dict): A dictionary of exported paths that have host
definitions that conflicted with a previous definition.
ignored_lines (dict): A synonym for the above `ignored_exports`
dictionary, for historical reasons.
raw_lines (dict of lists): The list of the raw lines that define each
exported path, including any lines that may have ignored exports.
"""
def _parse_host(self, content):
if "(" in content:
host, options = content.split("(")
options = options.rstrip(")").split(",")
else:
host, options = content, []
return host, options
def _parse_line(self, line):
split = [i.strip() for i in line.split()]
path, hosts = split[0], dict(self._parse_host(s) for s in split[1:])
return path, hosts
[docs] def parse_content(self, content):
# Exports can be duplicated, but the path-host tuple cannot: the
# first read will be stored and all later path-host tuples cause
# `exportfs` to generate a warning when setting up the export.
self.data = {}
self.ignored_exports = {}
self.ignored_lines = self.ignored_exports
self.raw_lines = {}
for line in get_active_lines(content):
path, hosts = self._parse_line(line)
if path not in self.data:
# New path, just add the hosts.
self.data[path] = hosts
self.raw_lines[path] = [line]
else:
# Add to raw lines even if some (or all) hosts are ignored.
self.raw_lines[path].append(line)
# Have to check each path-host tuple
for host, flags in hosts.items():
if host not in self.data[path]:
# Only add if it doesn't already exist.
self.data[path][host] = flags
else:
if path not in self.ignored_exports:
self.ignored_exports[path] = {host: flags}
else:
self.ignored_exports[path][host] = flags
[docs] def export_paths(self):
"""Returns the set of all export paths as strings"""
return set(self.data.keys())
[docs] def all_options(self):
"""Returns the set of all options used in all export entries"""
items = chain.from_iterable(hosts.values() for hosts in self.data.values())
return set(chain.from_iterable(items))
def __iter__(self):
return iter(self.data.items())
[docs]@parser(Specs.nfs_exports)
class NFSExports(NFSExportsBase):
"""Subclass to attach ``nfs_exports`` spec to"""
pass
[docs]@parser(Specs.nfs_exports_d)
class NFSExportsD(NFSExportsBase):
"""Subclass to attach ``nfs_exports.d`` spec to"""
pass