mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Initial support for handling /etc/filesystems on AIX
This commit is contained in:
parent
b87bf905c2
commit
210076276f
1 changed files with 405 additions and 0 deletions
|
@ -5,6 +5,7 @@ Salt module to manage Unix mounts and the fstab file
|
|||
|
||||
# Import python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
from collections import OrderedDict
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
@ -23,6 +24,7 @@ from salt.exceptions import CommandNotFoundError, CommandExecutionError
|
|||
from salt.ext import six
|
||||
from salt.ext.six.moves import filter, zip # pylint: disable=import-error,redefined-builtin
|
||||
|
||||
|
||||
# Set up logger
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -432,6 +434,138 @@ class _vfstab_entry(object):
|
|||
return True
|
||||
|
||||
|
||||
class _filesystems_entry(object):
|
||||
'''
|
||||
Utility class for manipulating filesystem entries. Primarily we're parsing,
|
||||
formatting, and comparing lines. Parsing emits dicts expected from
|
||||
fstab() or raises a ValueError.
|
||||
|
||||
Note: We'll probably want to use os.normpath and os.normcase on 'name'
|
||||
'''
|
||||
|
||||
class ParseError(ValueError):
|
||||
'''
|
||||
Error raised when a line isn't parsible as an fstab entry
|
||||
'''
|
||||
|
||||
filesystems_keys = ('device', 'name', 'fstype', 'vfstype', 'opts', 'mount')
|
||||
|
||||
# preserve data format
|
||||
compatibility_keys = ('device', 'name', 'fstype', 'vfstype', 'opts', 'mount', 'account', 'boot', 'check', 'free', 'nodename', 'quota', 'size', 'vol', 'log')
|
||||
|
||||
@classmethod
|
||||
def dict_from_lines(cls, lines, keys=filesystems_keys):
|
||||
if len(lines) < 2:
|
||||
raise ValueError('Invalid number of lines: {0}'.format(lines))
|
||||
|
||||
if len(keys) < 6:
|
||||
raise ValueError('Invalid key array: {0}'.format(keys))
|
||||
|
||||
blk_lines = lines
|
||||
orddict = OrderedDict()
|
||||
orddict['name'] = blk_lines[0].split(':')[0].strip()
|
||||
blk_lines.pop(0)
|
||||
for line in blk_lines:
|
||||
if line.startswith('#'):
|
||||
raise cls.ParseError("Comment!")
|
||||
|
||||
comps = line.split('= ')
|
||||
if len(comps) != 2:
|
||||
raise cls.ParseError("Invalid Entry!")
|
||||
|
||||
orddict[comps[0].strip()] = comps[1].strip()
|
||||
|
||||
return orddict
|
||||
|
||||
@classmethod
|
||||
def dict_from_cmd_line(cls, ipargs, keys=filesystems_keys):
|
||||
if len(keys) < 1:
|
||||
raise ValueError('Invalid key array: {0}'.format(keys))
|
||||
|
||||
cmdln_dict = ipargs
|
||||
for key in keys:
|
||||
# ignore unknown or local scope keys
|
||||
if key.startswith('__'):
|
||||
continue
|
||||
|
||||
if key not in _filesystems_entry.compatibility_keys:
|
||||
cmdln_dict[key] = keys[key]
|
||||
|
||||
return cmdln_dict
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, *args, **kwargs):
|
||||
return cls(** cls.dict_from_cmd_line(*args, **kwargs))
|
||||
|
||||
@classmethod
|
||||
def dict_to_lines(cls, fsys_dict_entry):
|
||||
entry = fsys_dict_entry
|
||||
strg_out = entry['name'] + ':' + os.linesep
|
||||
for k, v in six.viewitems(entry):
|
||||
if 'name' not in k:
|
||||
strg_out += '\t{0}\t\t= {1}'.format(k, v) + os.linesep
|
||||
strg_out += os.linesep
|
||||
return six.text_type(strg_out)
|
||||
|
||||
def dict_from_entry(self):
|
||||
ret = OrderedDict()
|
||||
ret[self.criteria['name']] = self.criteria
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
String value, only works for full repr
|
||||
'''
|
||||
return self.dict_to_lines(self.criteria)
|
||||
|
||||
def __repr__(self):
|
||||
'''
|
||||
Always works
|
||||
'''
|
||||
return repr(self.criteria)
|
||||
|
||||
def pick(self, keys):
|
||||
'''
|
||||
Returns an instance with just those keys
|
||||
'''
|
||||
subset = dict([(key, self.criteria[key]) for key in keys])
|
||||
return self.__class__(**subset)
|
||||
|
||||
def __init__(self, **criteria):
|
||||
'''
|
||||
Store non-empty, non-null values to use as filter
|
||||
'''
|
||||
items = [key_value for key_value in six.iteritems(criteria) if key_value[1] is not None]
|
||||
items = [(key_value1[0], six.text_type(key_value1[1])) for key_value1 in items]
|
||||
self.criteria = OrderedDict(items)
|
||||
|
||||
@staticmethod
|
||||
def norm_path(path):
|
||||
'''
|
||||
Resolve equivalent paths equivalently
|
||||
'''
|
||||
return os.path.normcase(os.path.normpath(path))
|
||||
|
||||
def match(self, fsys_view):
|
||||
'''
|
||||
Compare potentially partial criteria against built filesystems entry dictionary
|
||||
'''
|
||||
evalue_dict = fsys_view[1]
|
||||
for key, value in six.viewitems(self.criteria):
|
||||
if key in evalue_dict:
|
||||
if evalue_dict[key] != value:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
'''
|
||||
Return value for input key
|
||||
'''
|
||||
return self.criteria[key]
|
||||
|
||||
|
||||
def fstab(config='/etc/fstab'):
|
||||
'''
|
||||
.. versionchanged:: 2016.3.2
|
||||
|
@ -1422,3 +1556,274 @@ def delete_mount_cache(real_name):
|
|||
if not cache_write:
|
||||
raise CommandExecutionError('Unable to write mount cache.')
|
||||
return True
|
||||
|
||||
|
||||
def _filesystems(config='/etc/filesystems', leading_key=True):
|
||||
'''
|
||||
.. versionadded:: 2018.3.3
|
||||
|
||||
List the contents of the filesystems
|
||||
|
||||
config
|
||||
File containing filesystem infomation
|
||||
|
||||
leading_key
|
||||
True return dictionary keyed by 'name' value and dictionary with other keys, values
|
||||
{ '/dir' : { 'dev': '/dev/hd8', .... }}
|
||||
|
||||
False return dictionary keyed by 'name' value and dictionary with all keys, values
|
||||
{ '/dir' : { 'name' : '/dir', 'dev': '/dev/hd8', .... }}
|
||||
'''
|
||||
ret = OrderedDict()
|
||||
lines = []
|
||||
parsing_block = False
|
||||
if not os.path.isfile(config) or 'AIX' not in __grains__['kernel']:
|
||||
return ret
|
||||
|
||||
# read in block of filesystems, block starts with '/' till empty line
|
||||
with salt.utils.files.fopen(config) as ifile:
|
||||
for line in ifile:
|
||||
line = salt.utils.stringutils.to_unicode(line)
|
||||
|
||||
# skip till first entry
|
||||
if not line.startswith('/') and not parsing_block:
|
||||
continue
|
||||
|
||||
if line.startswith('/'):
|
||||
parsing_block = True
|
||||
lines.append(line)
|
||||
elif not line.split():
|
||||
parsing_block = False
|
||||
try:
|
||||
entry = _filesystems_entry.dict_from_lines(
|
||||
lines,
|
||||
_filesystems_entry.compatibility_keys)
|
||||
lines = []
|
||||
if 'opts' in entry:
|
||||
entry['opts'] = entry['opts'].split(',')
|
||||
while entry['name'] in ret:
|
||||
entry['name'] += '_'
|
||||
|
||||
if leading_key:
|
||||
ret[entry.pop('name')] = entry
|
||||
else:
|
||||
ret[entry['name']] = entry
|
||||
|
||||
except _filesystems_entry.ParseError:
|
||||
pass
|
||||
else:
|
||||
lines.append(line)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def filesystems(config='/etc/filesystems'):
|
||||
'''
|
||||
.. versionadded:: 2018.3.3
|
||||
|
||||
List the contents of the filesystems
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' mount.filesystems
|
||||
'''
|
||||
ret = {}
|
||||
if 'AIX' not in __grains__['kernel']:
|
||||
return ret
|
||||
|
||||
return _filesystems(config)
|
||||
|
||||
|
||||
def set_filesystems(
|
||||
name,
|
||||
device,
|
||||
fstype,
|
||||
vfstype,
|
||||
opts='-',
|
||||
mount='true',
|
||||
config='/etc/filesystems',
|
||||
test=False,
|
||||
match_on='auto',
|
||||
**kwargs):
|
||||
'''
|
||||
.. versionadded:: 2018.3.3
|
||||
|
||||
Verify that this mount is represented in the filesystems, change the mount
|
||||
to match the data passed, or add the mount if it is not present.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' mount.set_filesystems /mnt/foo /dev/sdz1 jfs2
|
||||
'''
|
||||
# Fix the opts type if it is a list
|
||||
if isinstance(opts, list):
|
||||
opts = ','.join(opts)
|
||||
|
||||
# preserve arguments for updating
|
||||
entry_args = {
|
||||
'name': name,
|
||||
'dev': device.replace('\\ ', '\\040'),
|
||||
'fstype': fstype,
|
||||
'vfstype': vfstype,
|
||||
'opts': opts,
|
||||
'mount': mount,
|
||||
}
|
||||
|
||||
view_lines = []
|
||||
ret = None
|
||||
|
||||
if 'AIX' not in __grains__['kernel']:
|
||||
return ret
|
||||
|
||||
# Transform match_on into list--items will be checked later
|
||||
if isinstance(match_on, list):
|
||||
pass
|
||||
elif not isinstance(match_on, six.string_types):
|
||||
msg = 'match_on must be a string or list of strings'
|
||||
raise CommandExecutionError(msg)
|
||||
elif match_on == 'auto':
|
||||
# Try to guess right criteria for auto....
|
||||
# added IBM types from sys/vmount.h after btrfs
|
||||
# NOTE: missing some special fstypes here
|
||||
specialFSes = frozenset([
|
||||
'none',
|
||||
'tmpfs',
|
||||
'sysfs',
|
||||
'proc',
|
||||
'fusectl',
|
||||
'debugfs',
|
||||
'securityfs',
|
||||
'devtmpfs',
|
||||
'cgroup',
|
||||
'btrfs',
|
||||
'cdrfs',
|
||||
'procfs',
|
||||
'jfs',
|
||||
'jfs2',
|
||||
'nfs',
|
||||
'sfs',
|
||||
'nfs3',
|
||||
'cachefs',
|
||||
'udfs',
|
||||
'cifs',
|
||||
'namefs',
|
||||
'pmemfs',
|
||||
'ahafs',
|
||||
'nfs4',
|
||||
'autofs',
|
||||
'stnfs'])
|
||||
|
||||
if fstype in specialFSes:
|
||||
match_on = ['name']
|
||||
else:
|
||||
match_on = ['dev']
|
||||
else:
|
||||
match_on = [match_on]
|
||||
|
||||
# generate entry and criteria objects, handle invalid keys in match_on
|
||||
entry_ip = _filesystems_entry.from_line(entry_args, kwargs)
|
||||
try:
|
||||
criteria = entry_ip.pick(match_on)
|
||||
|
||||
except KeyError:
|
||||
filterFn = lambda key: key not in _filesystems_entry.filesystems_keys
|
||||
invalid_keys = filter(filterFn, match_on)
|
||||
|
||||
msg = 'Unrecognized keys in match_on: "{0}"'.format(invalid_keys)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
# parse file, use ret to cache status
|
||||
if not os.path.isfile(config):
|
||||
raise CommandExecutionError('Bad config file "{0}"'.format(config))
|
||||
|
||||
# read in block of filesystem, block starts with '/' till empty line
|
||||
try:
|
||||
fsys_filedict = _filesystems(config, False)
|
||||
for fsys_view in six.viewitems(fsys_filedict):
|
||||
if criteria.match(fsys_view):
|
||||
ret = 'present'
|
||||
if entry_ip.match(fsys_view):
|
||||
view_lines.append(fsys_view)
|
||||
else:
|
||||
ret = 'change'
|
||||
kv = entry_ip['name']
|
||||
view_lines.append((kv, entry_ip))
|
||||
else:
|
||||
view_lines.append(fsys_view)
|
||||
|
||||
except (IOError, OSError) as exc:
|
||||
msg = 'Couldn\'t read from {0}: {1}'
|
||||
raise CommandExecutionError(msg.format(config, exc))
|
||||
|
||||
# add line if not present or changed
|
||||
if ret is None:
|
||||
for dict_view in six.viewitems(entry_ip.dict_from_entry()):
|
||||
view_lines.append(dict_view)
|
||||
ret = 'new'
|
||||
|
||||
if ret != 'present': # ret in ['new', 'change']:
|
||||
try:
|
||||
with salt.utils.files.fopen(config, 'wb') as ofile:
|
||||
# The line was changed, commit it!
|
||||
for fsys_view in view_lines:
|
||||
entry = fsys_view[1]
|
||||
mystrg = _filesystems_entry.dict_to_lines(entry)
|
||||
ofile.writelines(salt.utils.data.encode(mystrg))
|
||||
except (IOError, OSError):
|
||||
msg = 'File not writable {0}'
|
||||
raise CommandExecutionError(msg.format(config))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def rm_filesystems(name, device, config='/etc/filesystems'):
|
||||
'''
|
||||
.. versionadded:: 2018.3.3
|
||||
|
||||
Remove the mount point from the filesystems
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' mount.rm_filesystems /mnt/foo /dev/sdg
|
||||
'''
|
||||
modified = False
|
||||
view_lines = []
|
||||
|
||||
if 'AIX' not in __grains__['kernel']:
|
||||
return modified
|
||||
|
||||
criteria = _filesystems_entry(name=name, dev=device)
|
||||
try:
|
||||
fsys_filedict = _filesystems(config, False)
|
||||
for fsys_view in six.viewitems(fsys_filedict):
|
||||
try:
|
||||
if criteria.match(fsys_view):
|
||||
modified = True
|
||||
else:
|
||||
view_lines.append(fsys_view)
|
||||
|
||||
except _filesystems_entry.ParseError:
|
||||
view_lines.append(fsys_view)
|
||||
|
||||
except (IOError, OSError) as exc:
|
||||
msg = "Couldn't read from {0}: {1}"
|
||||
raise CommandExecutionError(msg.format(config, exc))
|
||||
|
||||
if modified:
|
||||
try:
|
||||
with salt.utils.files.fopen(config, 'wb') as ofile:
|
||||
for fsys_view in view_lines:
|
||||
entry = fsys_view[1]
|
||||
mystrg = _filesystems_entry.dict_to_lines(entry)
|
||||
ofile.writelines(salt.utils.data.encode(mystrg))
|
||||
except (IOError, OSError) as exc:
|
||||
msg = "Couldn't write to {0}: {1}"
|
||||
raise CommandExecutionError(msg.format(config, exc))
|
||||
|
||||
return modified
|
||||
|
|
Loading…
Add table
Reference in a new issue