Merge pull request #30752 from terminalmage/zh459

Backport systemd and yum/dnf optimizations from develop into 2015.8
This commit is contained in:
Mike Place 2016-02-01 11:11:42 -07:00
commit a49b75e065
4 changed files with 855 additions and 647 deletions

View file

@ -1,18 +1,24 @@
# -*- coding: utf-8 -*-
'''
Provide the service module for systemd
.. versionadded:: 0.10.0
'''
# Import python libs
from __future__ import absolute_import
import copy
import errno
import glob
import logging
import os
import re
import glob
import shlex
# Import 3rd-party libs
import salt.ext.six as six
import salt.utils.itertools
import salt.utils.systemd
import salt.exceptions
from salt.exceptions import CommandExecutionError, CommandNotFoundError
from salt.ext import six
log = logging.getLogger(__name__)
@ -20,20 +26,25 @@ __func_alias__ = {
'reload_': 'reload'
}
SYSTEM_CONFIG_PATH = '/lib/systemd/system'
LOCAL_CONFIG_PATH = '/etc/systemd/system'
LEGACY_INIT_SCRIPT_PATH = '/etc/init.d'
VALID_UNIT_TYPES = ['service', 'socket', 'device', 'mount', 'automount',
'swap', 'target', 'path', 'timer']
INITSCRIPT_PATH = '/etc/init.d'
VALID_UNIT_TYPES = ('service', 'socket', 'device', 'mount', 'automount',
'swap', 'target', 'path', 'timer')
# Define the module's virtual name
__virtualname__ = 'service'
# Disable check for string substitution
# pylint: disable=E1321
def __virtual__():
'''
Only work on systems that have been booted with systemd
'''
if __grains__['kernel'] == 'Linux' and salt.utils.systemd.booted(__context__):
if __grains__['kernel'] == 'Linux' \
and salt.utils.systemd.booted(__context__):
return __virtualname__
return False
@ -45,117 +56,35 @@ def _canonical_unit_name(name):
'''
if any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES):
return name
return '{0}.service'.format(name)
return '%s.service' % name
def _canonical_template_unit_name(name):
def _check_for_unit_changes(name):
'''
Build a canonical unit name for unit instances based on templates.
Check for modified/updated unit files, and run a daemon-reload if any are
found.
'''
return re.sub(r'@.+?(\.|$)', r'@\1', name)
contextkey = 'systemd._check_for_unit_changes'
if contextkey not in __context__:
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
# Set context key to avoid repeating this check
__context__[contextkey] = True
def _systemctl_cmd(action, name):
def _clear_context():
'''
Build a systemctl command line. Treat unit names without one
of the valid suffixes as a service.
Remove context
'''
return 'systemctl {0} {1}'.format(action, _canonical_unit_name(name))
def _get_all_units():
'''
Get all units and their state. Units ending in .service
are normalized so that they can be referenced without a type suffix.
'''
rexp = re.compile(r'(?m)^(?P<name>.+)\.(?P<type>' +
'|'.join(VALID_UNIT_TYPES) +
r')\s+loaded\s+(?P<active>[^\s]+)')
out = __salt__['cmd.run_stdout'](
'systemctl --all --full --no-legend --no-pager list-units'
)
ret = {}
for match in rexp.finditer(out):
name = match.group('name')
if match.group('type') != 'service':
name += '.' + match.group('type')
ret[name] = match.group('active')
return ret
def _get_all_unit_files():
'''
Get all unit files and their state. Unit files ending in .service
are normalized so that they can be referenced without a type suffix.
'''
rexp = re.compile(r'(?m)^(?P<name>.+)\.(?P<type>' +
'|'.join(VALID_UNIT_TYPES) +
r')\s+(?P<state>.+)$')
out = __salt__['cmd.run_stdout'](
'systemctl --full --no-legend --no-pager list-unit-files'
)
ret = {}
for match in rexp.finditer(out):
name = match.group('name')
if match.group('type') != 'service':
name += '.' + match.group('type')
ret[name] = match.group('state')
return ret
def _get_all_legacy_init_scripts():
'''
Get all old-fashioned init-style scripts. State is always inactive, because
systemd would already show them otherwise.
'''
ret = {}
if not os.path.isdir(LEGACY_INIT_SCRIPT_PATH):
return ret
for fn in os.listdir(LEGACY_INIT_SCRIPT_PATH):
if not os.path.isfile(os.path.join(LEGACY_INIT_SCRIPT_PATH, fn)) or fn.startswith('rc'):
# Using list() here because modifying a dictionary during iteration will
# raise a RuntimeError.
for key in list(__context__):
try:
if key.startswith('systemd._systemctl_status.') \
or key in ('systemd.systemd_services',):
__context__.pop(key)
except AttributeError:
continue
log.info('Legacy init script: "%s".', fn)
ret[fn] = 'inactive'
return ret
def _untracked_custom_unit_found(name):
'''
If the passed service name is not in the output from get_all(), but a unit
file exist in /etc/systemd/system, return True. Otherwise, return False.
'''
unit_path = os.path.join('/etc/systemd/system',
_canonical_unit_name(name))
return name not in get_all() and os.access(unit_path, os.R_OK)
def _unit_file_changed(name):
'''
Returns True if systemctl reports that the unit file has changed, otherwise
returns False.
'''
return 'warning: unit file changed on disk' in \
__salt__['cmd.run'](_systemctl_cmd('status', name)).lower()
def systemctl_reload():
'''
Reloads systemctl, an action needed whenever unit files are updated.
CLI Example:
.. code-block:: bash
salt '*' service.systemctl_reload
'''
retcode = __salt__['cmd.retcode']('systemctl --system daemon-reload')
if retcode != 0:
log.error('Problem performing systemctl daemon-reload')
return retcode == 0
def _default_runlevel():
@ -200,22 +129,6 @@ def _default_runlevel():
return runlevel
def _runlevel():
'''
Return the current runlevel
'''
if 'systemd._runlevel' in __context__:
return __context__['systemd._runlevel']
out = __salt__['cmd.run']('runlevel', python_shell=False)
try:
ret = out.split()[1]
except IndexError:
# The runlevel is unknown, return the default
ret = _default_runlevel()
__context__['systemd._runlevel'] = ret
return ret
def _get_service_exec():
'''
Debian uses update-rc.d to manage System-V style services.
@ -226,53 +139,175 @@ def _get_service_exec():
return executable
def _get_systemd_services():
'''
Use os.listdir() to get all the unit files
'''
contextkey = 'systemd.systemd_services'
if contextkey in __context__:
return __context__[contextkey]
ret = set()
for path in (SYSTEM_CONFIG_PATH, LOCAL_CONFIG_PATH):
for fullname in os.listdir(path):
try:
unit_name, unit_type = fullname.rsplit('.', 1)
except ValueError:
continue
if unit_type in VALID_UNIT_TYPES:
ret.add(unit_name if unit_type == 'service' else fullname)
__context__[contextkey] = copy.deepcopy(ret)
return ret
def _get_sysv_services():
'''
Use os.listdir() and os.access() to get all the initscripts
'''
try:
sysv_services = os.listdir(INITSCRIPT_PATH)
except OSError as exc:
if exc.errno == errno.EEXIST:
pass
elif exc.errno == errno.EACCES:
log.error(
'Unable to check sysvinit scripts, permission denied to %s',
INITSCRIPT_PATH
)
else:
log.error(
'Error %d encountered trying to check sysvinit scripts: %s',
exc.errno,
exc.strerror
)
return []
systemd_services = _get_systemd_services()
ret = []
for sysv_service in sysv_services:
if os.access(os.path.join(INITSCRIPT_PATH, sysv_service), os.X_OK):
if sysv_service in systemd_services:
log.debug(
'sysvinit script \'%s\' found, but systemd unit '
'\'%s.service\' already exists',
sysv_service, sysv_service
)
continue
ret.append(sysv_service)
return ret
def _has_sysv_exec():
'''
Return the current runlevel
'''
if 'systemd._has_sysv_exec' not in __context__:
contextkey = 'systemd._has_sysv_exec'
if contextkey not in __context__:
try:
__context__['systemd._has_sysv_exec'] = bool(_get_service_exec())
except(
salt.exceptions.CommandExecutionError,
salt.exceptions.CommandNotFoundError
):
__context__['systemd._has_sysv_exec'] = False
return __context__['systemd._has_sysv_exec']
__context__[contextkey] = bool(_get_service_exec())
except (CommandExecutionError, CommandNotFoundError):
__context__[contextkey] = False
return __context__[contextkey]
def _sysv_exists(name):
script = '/etc/init.d/{0}'.format(name)
return os.access(script, os.X_OK)
def _service_is_sysv(name):
def _runlevel():
'''
A System-V style service will have a control script in
/etc/init.d.
Return True only if the service doesnt also provide a systemd unit file.
Return the current runlevel
'''
return (_has_sysv_exec() and
name in _get_all_units() and
name not in _get_all_unit_files() and
_sysv_exists(name))
contextkey = 'systemd._runlevel'
if contextkey in __context__:
return __context__[contextkey]
out = __salt__['cmd.run']('runlevel', python_shell=False)
try:
ret = out.split()[1]
except IndexError:
# The runlevel is unknown, return the default
ret = _default_runlevel()
__context__[contextkey] = ret
return ret
def _sysv_is_disabled(name):
def _systemctl_cmd(action, name=None):
'''
A System-V style service is assumed disabled if there is no
start-up link (starts with "S") to its script in /etc/init.d in
the current runlevel.
Build a systemctl command line. Treat unit names without one
of the valid suffixes as a service.
'''
return not bool(glob.glob('/etc/rc{0}.d/S*{1}'.format(_runlevel(), name)))
ret = ['systemctl']
if isinstance(action, six.string_types):
action = shlex.split(action)
ret.extend(action)
if name is not None:
ret.append(_canonical_unit_name(name))
if 'status' in ret:
ret.extend(['-n', '0'])
return ret
def _sysv_is_enabled(name):
def _systemctl_status(name):
'''
Assume that if a System-V style service is not disabled then it
must be enabled.
Helper function which leverages __context__ to keep from running 'systemctl
status' more than once.
'''
return not _sysv_is_disabled(name)
contextkey = 'systemd._systemctl_status.%s' % name
if contextkey in __context__:
return __context__[contextkey]
__context__[contextkey] = __salt__['cmd.run'](
_systemctl_cmd('status', name),
python_shell=False,
ignore_retcode=True
)
return __context__[contextkey]
def _sysv_enabled(name):
'''
A System-V style service is assumed disabled if the "startup" symlink
(starts with "S") to its script is found in /etc/init.d in the current
runlevel.
'''
return bool(glob.glob('/etc/rc%s.d/S*%s' % (_runlevel(), name)))
def _untracked_custom_unit_found(name):
'''
If the passed service name is not available, but a unit file exist in
/etc/systemd/system, return True. Otherwise, return False.
'''
unit_path = os.path.join('/etc/systemd/system',
_canonical_unit_name(name))
return os.access(unit_path, os.R_OK) and not available(name)
def _unit_file_changed(name):
'''
Returns True if systemctl reports that the unit file has changed, otherwise
returns False.
'''
return "'systemctl daemon-reload'" in _systemctl_status(name).lower()
def systemctl_reload():
'''
.. versionadded:: 0.15.0
Reloads systemctl, an action needed whenever unit files are updated.
CLI Example:
.. code-block:: bash
salt '*' service.systemctl_reload
'''
out = __salt__['cmd.run_all'](
_systemctl_cmd('--system daemon-reload'),
python_shell=False,
redirect_stderr=True
)
if out['retcode'] != 0:
raise CommandExecutionError(
'Problem performing systemctl daemon-reload: %s' % out['stdout']
)
_clear_context()
return True
def get_enabled():
@ -285,21 +320,33 @@ def get_enabled():
salt '*' service.get_enabled
'''
ret = []
units = _get_all_unit_files()
services = _get_all_units()
for name, state in six.iteritems(units):
if state == 'enabled':
ret.append(name)
for name, state in six.iteritems(services):
if name in units:
ret = set()
# Get enabled systemd units. Can't use --state=enabled here because it's
# not present until systemd 216.
out = __salt__['cmd.run'](
_systemctl_cmd('--full --no-legend --no-pager list-unit-files'),
python_shell=False,
ignore_retcode=True,
)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
except ValueError:
continue
# performance; if the legacy initscript doesnt exists,
# don't contiue up with systemd query
if not _service_is_sysv(name):
else:
if unit_state != 'enabled':
continue
try:
unit_name, unit_type = fullname.rsplit('.', 1)
except ValueError:
continue
if _sysv_is_enabled(name):
ret.append(name)
if unit_type in VALID_UNIT_TYPES:
ret.add(unit_name if unit_type == 'service' else fullname)
# Add in any sysvinit services that are enabled
ret.update(set(
[x for x in _get_sysv_services() if _sysv_enabled(x)]
))
return sorted(ret)
@ -313,12 +360,72 @@ def get_disabled():
salt '*' service.get_disabled
'''
ret = []
known_services = _get_all_unit_files()
known_services.update(_get_all_legacy_init_scripts())
for name, state in six.iteritems(known_services):
if state == 'disabled':
ret.append(name)
ret = set()
# Get disabled systemd units. Can't use --state=disabled here because it's
# not present until systemd 216.
out = __salt__['cmd.run'](
_systemctl_cmd('--full --no-legend --no-pager list-unit-files'),
python_shell=False,
ignore_retcode=True,
)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
except ValueError:
continue
else:
if unit_state != 'disabled':
continue
try:
unit_name, unit_type = fullname.rsplit('.', 1)
except ValueError:
continue
if unit_type in VALID_UNIT_TYPES:
ret.add(unit_name if unit_type == 'service' else fullname)
# Add in any sysvinit services that are disabled
ret.update(set(
[x for x in _get_sysv_services() if not _sysv_enabled(x)]
))
return sorted(ret)
def get_static():
'''
.. versionadded:: 2015.8.5
Return a list of all static services
CLI Example:
.. code-block:: bash
salt '*' service.get_static
'''
ret = set()
# Get static systemd units. Can't use --state=static here because it's
# not present until systemd 216.
out = __salt__['cmd.run'](
_systemctl_cmd('--full --no-legend --no-pager list-unit-files'),
python_shell=False,
ignore_retcode=True,
)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
except ValueError:
continue
else:
if unit_state != 'static':
continue
try:
unit_name, unit_type = fullname.rsplit('.', 1)
except ValueError:
continue
if unit_type in VALID_UNIT_TYPES:
ret.add(unit_name if unit_type == 'service' else fullname)
# sysvinit services cannot be static
return sorted(ret)
@ -332,14 +439,17 @@ def get_all():
salt '*' service.get_all
'''
return sorted(set(list(_get_all_units().keys()) + list(_get_all_unit_files().keys())
+ list(_get_all_legacy_init_scripts().keys())))
ret = _get_systemd_services()
ret.update(set(_get_sysv_services()))
return sorted(ret)
def available(name):
'''
Check that the given service is available taking into account
template units.
.. versionadded:: 0.10.4
Check that the given service is available taking into account template
units.
CLI Example:
@ -347,24 +457,26 @@ def available(name):
salt '*' service.available sshd
'''
name = _canonical_template_unit_name(name)
if name.endswith('.service'):
name = name[:-8] # len('.service') is 8
units = get_all()
if name in units:
return True
elif '@' in name:
templatename = name[:name.find('@') + 1]
return templatename in units
out = _systemctl_status(name).lower()
for line in salt.utils.itertools.split(out, '\n'):
match = re.match(r'\s+loaded:\s+(\S+)', line)
if match:
ret = match.group(1) != 'not-found'
break
else:
return False
raise CommandExecutionError(
'Failed to get information on unit \'%s\'' % name
)
return ret
def missing(name):
'''
The inverse of service.available.
Returns ``True`` if the specified service is not available, otherwise returns
``False``.
.. versionadded:: 2014.1.0
The inverse of :py:func:`service.available
<salt.modules.systemd.available>`. Returns ``True`` if the specified
service is not available, otherwise returns ``False``.
CLI Example:
@ -377,6 +489,8 @@ def missing(name):
def unmask(name):
'''
.. versionadded:: 2015.5.0
Unmask the specified service with systemd
CLI Example:
@ -385,32 +499,64 @@ def unmask(name):
salt '*' service.unmask <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
return not (__salt__['cmd.retcode'](_systemctl_cmd('unmask', name))
or __salt__['cmd.retcode'](_systemctl_cmd('unmask --runtime', name)))
_check_for_unit_changes(name)
mask_status = masked(name)
if not mask_status:
log.debug('Service \'%s\' is not masked', name)
return True
cmd = 'unmask --runtime' if 'runtime' in mask_status else 'unmask'
out = __salt__['cmd.run_all'](_systemctl_cmd(cmd, name),
python_shell=False,
redirect_stderr=True)
if out['retcode'] != 0:
raise CommandExecutionError('Failed to unmask service \'%s\'' % name)
return True
def mask(name):
def mask(name, runtime=False):
'''
.. versionadded:: 2015.5.0
Mask the specified service with systemd
runtime : False
Set to ``True`` to mask this service only until the next reboot
.. versionadded:: 2015.8.5
CLI Example:
.. code-block:: bash
salt '*' service.mask <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
return not __salt__['cmd.retcode'](_systemctl_cmd('mask', name))
_check_for_unit_changes(name)
cmd = 'mask --runtime' if runtime else 'mask'
out = __salt__['cmd.run_all'](_systemctl_cmd(cmd, name),
python_shell=False,
redirect_stderr=True)
if out['retcode'] != 0:
raise CommandExecutionError('Failed to mask service \'%s\'' % name)
return True
def masked(name):
'''
Return if the named service is masked.
.. versionadded:: 2015.8.0
.. versionchanged:: 2015.8.5
The return data for this function has changed. If the service is
masked, the return value will now be the output of the ``systemctl
is-enabled`` command (so that a persistent mask can be distinguished
from a runtime mask). If the service is not masked, then ``False`` will
be returned.
Check whether or not a service is masked
CLI Example:
@ -418,10 +564,13 @@ def masked(name):
salt '*' service.masked <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
out = __salt__['cmd.run_all'](_systemctl_cmd('is-enabled', name), ignore_retcode=True)
return out['retcode'] == 1 and 'masked' in out['stdout']
_check_for_unit_changes(name)
out = __salt__['cmd.run'](
_systemctl_cmd('is-enabled', name),
python_shell=False,
ignore_retcode=True,
)
return out if 'masked' in out else False
def start(name):
@ -450,9 +599,9 @@ def stop(name):
salt '*' service.stop <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
return not __salt__['cmd.retcode'](_systemctl_cmd('stop', name))
_check_for_unit_changes(name)
return __salt__['cmd.retcode'](_systemctl_cmd('stop', name),
python_shell=False) == 0
def restart(name):
@ -465,10 +614,10 @@ def restart(name):
salt '*' service.restart <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
_check_for_unit_changes(name)
unmask(name)
return not __salt__['cmd.retcode'](_systemctl_cmd('restart', name))
return __salt__['cmd.retcode'](_systemctl_cmd('restart', name),
python_shell=False) == 0
def reload_(name):
@ -481,14 +630,16 @@ def reload_(name):
salt '*' service.reload <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
_check_for_unit_changes(name)
unmask(name)
return not __salt__['cmd.retcode'](_systemctl_cmd('reload', name))
return __salt__['cmd.retcode'](_systemctl_cmd('reload', name),
python_shell=False) == 0
def force_reload(name):
'''
.. versionadded:: 0.12.0
Force-reload the specified service with systemd
CLI Example:
@ -497,15 +648,15 @@ def force_reload(name):
salt '*' service.force_reload <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
_check_for_unit_changes(name)
unmask(name)
return not __salt__['cmd.retcode'](_systemctl_cmd('force-reload', name))
return __salt__['cmd.retcode'](_systemctl_cmd('force-reload', name),
python_shell=False) == 0
# The unused sig argument is required to maintain consistency in the state
# system
def status(name, sig=None):
# The unused sig argument is required to maintain consistency with the API
# established by Salt's service management states.
def status(name, sig=None): # pylint: disable=unused-argument
'''
Return the status for a service via systemd, returns a bool
whether the service is running.
@ -516,13 +667,15 @@ def status(name, sig=None):
salt '*' service.status <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
return not __salt__['cmd.retcode'](_systemctl_cmd('is-active', name),
ignore_retcode=True)
_check_for_unit_changes(name)
return __salt__['cmd.retcode'](_systemctl_cmd('is-active', name),
python_shell=False,
ignore_retcode=True) == 0
def enable(name, **kwargs):
# The unused kwargs argument is required to maintain consistency with the API
# established by Salt's service management states.
def enable(name, **kwargs): # pylint: disable=unused-argument
'''
Enable the named service to start when the system boots
@ -532,17 +685,21 @@ def enable(name, **kwargs):
salt '*' service.enable <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
_check_for_unit_changes(name)
unmask(name)
if _service_is_sysv(name):
executable = _get_service_exec()
cmd = '{0} -f {1} defaults 99'.format(executable, name)
return not __salt__['cmd.retcode'](cmd, python_shell=False)
return not __salt__['cmd.retcode'](_systemctl_cmd('enable', name))
if name in _get_sysv_services():
cmd = [_get_service_exec(), '-f', name, 'defaults', '99']
return __salt__['cmd.retcode'](cmd,
python_shell=False,
ignore_retcode=True) == 0
return __salt__['cmd.retcode'](_systemctl_cmd('enable', name),
python_shell=False,
ignore_retcode=True) == 0
def disable(name, **kwargs):
# The unused kwargs argument is required to maintain consistency with the API
# established by Salt's service management states.
def disable(name, **kwargs): # pylint: disable=unused-argument
'''
Disable the named service to not start when the system boots
@ -552,38 +709,20 @@ def disable(name, **kwargs):
salt '*' service.disable <service name>
'''
if _untracked_custom_unit_found(name) or _unit_file_changed(name):
systemctl_reload()
if _service_is_sysv(name):
executable = _get_service_exec()
cmd = [executable, '-f', name, 'remove']
return not __salt__['cmd.retcode'](cmd, python_shell=False)
return not __salt__['cmd.retcode'](_systemctl_cmd('disable', name))
_check_for_unit_changes(name)
if name in _get_sysv_services():
cmd = [_get_service_exec(), '-f', name, 'remove']
return __salt__['cmd.retcode'](cmd,
python_shell=False,
ignore_retcode=True) == 0
return __salt__['cmd.retcode'](_systemctl_cmd('disable', name),
python_shell=False,
ignore_retcode=True) == 0
def _templated_instance_enabled(name):
'''
Services instantiated based on templates can not be checked with
systemctl is-enabled. Presence of the actual symlinks is checked
as a fall-back.
'''
if '@' not in name:
return False
find_unit_by_name = 'find {0} -name {1} -type l -print -quit'
return len(__salt__['cmd.run'](
find_unit_by_name.format(LOCAL_CONFIG_PATH,
_canonical_unit_name(name))
))
def _enabled(name):
is_enabled = \
not __salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name),
ignore_retcode=True)
return is_enabled or _templated_instance_enabled(name) or _sysv_is_enabled(name)
def enabled(name, **kwargs):
# The unused kwargs argument is required to maintain consistency with the API
# established by Salt's service management states.
def enabled(name, **kwargs): # pylint: disable=unused-argument
'''
Return if the named service is enabled to start on boot
@ -593,7 +732,25 @@ def enabled(name, **kwargs):
salt '*' service.enabled <service name>
'''
return _enabled(name)
# Try 'systemctl is-enabled' first, then look for a symlink created by
# systemctl (older systemd releases did not support using is-enabled to
# check templated services), and lastly check for a sysvinit service.
if __salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name),
python_shell=False,
ignore_retcode=True) == 0:
return True
elif '@' in name:
# On older systemd releases, templated services could not be checked
# with ``systemctl is-enabled``. As a fallback, look for the symlinks
# created by systemctl when enabling templated services.
cmd = ['find', LOCAL_CONFIG_PATH, '-name', name,
'-type', 'l', '-print', '-quit']
# If the find command returns any matches, there will be output and the
# string will be non-empty.
if bool(__salt__['cmd.run'](cmd, python_shell=False)):
return True
else:
return _sysv_enabled(name)
def disabled(name):
@ -606,11 +763,13 @@ def disabled(name):
salt '*' service.disabled <service name>
'''
return not _enabled(name) and not _sysv_is_enabled(name)
return not enabled(name)
def show(name):
'''
.. versionadded:: 2014.7.0
Show properties of one or more units/jobs or the manager
CLI Example:
@ -638,6 +797,8 @@ def show(name):
def execs():
'''
.. versionadded:: 2014.7.0
Return a list of all files specified as ``ExecStart`` for all services.
CLI Example:

View file

@ -21,16 +21,18 @@ Support for YUM/DNF
# Import python libs
from __future__ import absolute_import
import copy
import fnmatch
import itertools
import logging
import os
import re
import string
from distutils.version import LooseVersion as _LooseVersion # pylint: disable=no-name-in-module,import-error
# Import 3rd-party libs
# pylint: disable=import-error,redefined-builtin
import salt.ext.six as six
from salt.ext.six.moves import shlex_quote as _cmd_quote
# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import zip
try:
import yum
@ -44,10 +46,11 @@ try:
HAS_RPMUTILS = True
except ImportError:
HAS_RPMUTILS = False
# pylint: enable=import-error
# pylint: enable=import-error,redefined-builtin
# Import salt libs
import salt.utils
import salt.utils.itertools
import salt.utils.decorators as decorators
import salt.utils.pkg.rpm
from salt.exceptions import (
@ -79,13 +82,29 @@ def __virtual__():
return False
def _strip_headers(output, *args):
if not args:
args_lc = ('installed packages',
'available packages',
'updated packages',
'upgraded packages')
else:
args_lc = [x.lower() for x in args]
ret = ''
for line in salt.utils.itertools.split(output, '\n'):
if line.lower() not in args_lc:
ret += line + '\n'
return ret
def _yum():
'''
return yum or dnf depending on version
'''
contextkey = 'yum_bin'
if contextkey not in __context__:
if 'fedora' in __grains__['os'].lower() and int(__grains__['osrelease']) >= 22:
if 'fedora' in __grains__['os'].lower() \
and int(__grains__['osrelease']) >= 22:
__context__[contextkey] = 'dnf'
else:
__context__[contextkey] = 'yum'
@ -159,6 +178,41 @@ def _check_repoquery():
raise CommandExecutionError('Unable to install yum-utils')
def _yum_pkginfo(output):
'''
Parse yum/dnf output (which could contain irregular line breaks if package
names are long) retrieving the name, version, etc., and return a list of
pkginfo namedtuples.
'''
cur = {}
keys = itertools.cycle(('name', 'version', 'repoid'))
values = salt.utils.itertools.split(_strip_headers(output))
osarch = __grains__['osarch']
for (key, value) in zip(keys, values):
if key == 'name':
try:
cur['name'], cur['arch'] = value.rsplit('.', 1)
except ValueError:
cur['name'] = value
cur['arch'] = osarch
cur['name'] = salt.utils.pkg.rpm.resolve_name(cur['name'],
cur['arch'],
osarch)
else:
if key == 'repoid':
# Installed packages show a '@' at the beginning
value = value.lstrip('@')
cur[key] = value
if key == 'repoid':
# We're done with this package, create the pkginfo namedtuple
pkginfo = salt.utils.pkg.rpm.pkginfo(**cur)
# Clear the dict for the next package
cur = {}
# Yield the namedtuple
if pkginfo is not None:
yield pkginfo
def _check_versionlock():
'''
Ensure that the appropriate versionlock plugin is present
@ -184,23 +238,19 @@ def _repoquery(repoquery_args,
'''
_check_repoquery()
if _yum() == 'dnf':
cmd = 'dnf repoquery --quiet --queryformat {0} {1}'.format(
_cmd_quote(
query_format.replace('-%{VERSION}_', '-%{EPOCH}:%{VERSION}_')
),
repoquery_args
)
cmd = ['dnf', 'repoquery', '--quiet', '--queryformat',
query_format.replace('-%{VERSION}_', '-%{EPOCH}:%{VERSION}_')]
else:
cmd = 'repoquery --plugins --queryformat {0} {1}'.format(
_cmd_quote(query_format), repoquery_args
)
cmd = ['repoquery', '--plugins', '--queryformat', query_format]
cmd.extend(repoquery_args)
call = __salt__['cmd.run_all'](cmd, output_loglevel='trace')
if call['retcode'] != 0:
comment = ''
# When checking for packages some yum modules return data via
# stderr that don't cause non-zero return codes. j perfect
# example of this is when spacewalk is installed but not yet
# registered. We should ignore those when getting pkginfo.
# When checking for packages some yum modules return data via stderr
# that don't cause non-zero return codes. A perfect example of this is
# when spacewalk is installed but not yet registered. We should ignore
# those when getting pkginfo.
if 'stderr' in call and not salt.utils.is_true(ignore_stderr):
comment += call['stderr']
if 'stdout' in call:
@ -230,7 +280,7 @@ def _get_repo_options(**kwargs):
fromrepo = repo
use_dnf_repoquery = kwargs.get('repoquery', False) and _yum() == 'dnf'
repo_arg = []
ret = []
if fromrepo:
log.info('Restricting to repo \'{0}\''.format(fromrepo))
if use_dnf_repoquery:
@ -239,9 +289,9 @@ def _get_repo_options(**kwargs):
# This is good, because --repo does not work at all (see
# https://bugzilla.redhat.com/show_bug.cgi?id=1299261 for more
# information). Using --repoid here so this will actually work.
repo_arg.append('--repoid=\'{0}\''.format(fromrepo))
ret.append('--repoid=\'{0}\''.format(fromrepo))
else:
repo_arg.append(
ret.append(
'--disablerepo=\'*\' --enablerepo=\'{0}\''.format(fromrepo)
)
else:
@ -252,7 +302,7 @@ def _get_repo_options(**kwargs):
)
else:
log.info('Disabling repo \'{0}\''.format(disablerepo))
repo_arg.append('--disablerepo=\'{0}\''.format(disablerepo))
ret.append('--disablerepo=\'{0}\''.format(disablerepo))
if enablerepo:
if use_dnf_repoquery:
log.warning(
@ -260,8 +310,8 @@ def _get_repo_options(**kwargs):
)
else:
log.info('Enabling repo \'{0}\''.format(enablerepo))
repo_arg.append('--enablerepo=\'{0}\''.format(enablerepo))
return ' '.join(repo_arg)
ret.append('--enablerepo=\'{0}\''.format(enablerepo))
return ret
def _get_excludes_option(**kwargs):
@ -276,11 +326,11 @@ def _get_excludes_option(**kwargs):
log.warning(
'Ignoring disableexcludes, not supported in dnf repoquery'
)
return ''
return []
else:
log.info('Disabling excludes for \'{0}\''.format(disable_excludes))
return '--disableexcludes=\'{0}\''.format(disable_excludes)
return ''
return ['--disableexcludes=\'{0}\''.format(disable_excludes)]
return []
def _get_branch_option(**kwargs):
@ -386,13 +436,13 @@ def _normalize_basedir(basedir=None):
Returns a list of directories.
'''
if basedir is None:
basedir = []
# if we are passed a string (for backward compatibility), convert to a list
if isinstance(basedir, six.string_types):
basedir = [x.strip() for x in basedir.split(',')]
if basedir is None:
basedir = []
# nothing specified, so use the reposdir option as the default
if not basedir:
basedir = _get_yum_config_value('reposdir')
@ -460,18 +510,13 @@ def latest_version(*names, **kwargs):
# Initialize the return dict with empty strings, and populate namearch_map.
# namearch_map will provide a means of distinguishing between multiple
# matches for the same package name, for example a target of 'glibc' on an
# x86_64 arch would return both x86_64 and i686 versions when searched
# using repoquery:
#
# $ repoquery --all --pkgnarrow=available glibc
# glibc-0:2.12-1.132.el6.i686
# glibc-0:2.12-1.132.el6.x86_64
# x86_64 arch would return both x86_64 and i686 versions.
#
# Note that the logic in the for loop below would place the osarch into the
# map for noarch packages, but those cases are accounted for when iterating
# through the repoquery results later on. If the repoquery match for that
# package is a noarch, then the package is assumed to be noarch, and the
# namearch_map is ignored.
# through the 'yum list' results later on. If the match for that package is
# a noarch, then the package is assumed to be noarch, and the namearch_map
# is ignored.
ret = {}
namearch_map = {}
for name in names:
@ -491,61 +536,47 @@ def latest_version(*names, **kwargs):
if refresh:
refresh_db(**kwargs)
def _query_pkgs(name, pkgs):
'''
Return the newest available match from the _repoquery_pkginfo() output
'''
matches = []
for pkg in (x for x in pkgs if x.name == name):
if pkg.arch == 'noarch' or pkg.arch == namearch_map[name] \
or salt.utils.pkg.rpm.check_32(pkg.arch):
matches.append(pkg.version)
sorted_matches = sorted(
[_LooseVersion(x) for x in matches],
# Get available versions for specified package(s)
cmd = [_yum(), '--quiet']
cmd.extend(repo_arg)
cmd.extend(exclude_arg)
cmd.extend(['list', 'available'])
cmd.extend(names)
out = __salt__['cmd.run_all'](cmd,
output_loglevel='trace',
ignore_retcode=True,
python_shell=False)
if out['retcode'] != 0:
if out['stderr']:
# Check first if this is just a matter of the packages being
# up-to-date.
cur_pkgs = list_pkgs()
if not all([x in cur_pkgs for x in names]):
log.error(
'Problem encountered getting latest version for the '
'following package(s): {0}. Stderr follows: \n{1}'.format(
', '.join(names),
out['stderr']
)
)
updates = []
else:
# Sort by version number (highest to lowest) for loop below
updates = sorted(
_yum_pkginfo(out['stdout']),
key=lambda pkginfo: _LooseVersion(pkginfo.version),
reverse=True
)
try:
return sorted_matches[0].vstring
except IndexError:
return None
if _yum() == 'dnf':
avail_pkgs = _repoquery_pkginfo(
'{0} --available {1}'.format(repo_arg, ' '.join(names))
)
# When using 'dnf repoquery --available', all available versions are
# returned, irrespective of whether or not they are installed. This is
# different from how yum-utils' version of repoquery works.
all_pkgs = list_pkgs(versions_as_list=True)
for name in names:
# Get newest available version of package
newest_avail = _query_pkgs(name, avail_pkgs)
if newest_avail is None:
# No matches, no need to check if pacakge is already installed
continue
# Get newest installed version of package
try:
cver = all_pkgs.get(name, [])[-1]
except IndexError:
cver = None
if cver is None \
or salt.utils.compare_versions(ver1=newest_avail,
oper='>',
ver2=cver,
cmp_func=version_cmp):
ret[name] = newest_avail
else:
avail_pkgs = _repoquery_pkginfo(
'{0} {1} --pkgnarrow=available {2}'.format(
repo_arg,
exclude_arg,
' '.join(names)
)
)
for name in names:
newest_avail = _query_pkgs(name, avail_pkgs)
if newest_avail is not None:
ret[name] = newest_avail
for name in names:
for pkg in (x for x in updates if x.name == name):
if pkg.arch == 'noarch' or pkg.arch == namearch_map[name] \
or salt.utils.pkg.rpm.check_32(pkg.arch):
ret[name] = pkg.version
# no need to check another match, if there was one
break
else:
ret[name] = ''
# Return a string if only one package name passed
if len(names) == 1:
@ -682,6 +713,14 @@ def list_repo_pkgs(*args, **kwargs):
can be passed and the results will be filtered to packages matching those
names. This is recommended as it speeds up the function considerably.
.. warning::
Running this function on RHEL/CentOS 6 and earlier will be more
resource-intensive, as the version of yum that ships with older
RHEL/CentOS has no yum subcommand for listing packages from a
repository. Thus, a ``yum list installed`` and ``yum list available``
are run, which generates a lot of output, which must then be analyzed
to determine which package information to include in the return data.
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
@ -732,21 +771,70 @@ def list_repo_pkgs(*args, **kwargs):
)
ret = {}
for repo in repos:
if _yum() == 'dnf':
# As of 0.1.15, dnf repoquery does not support showing duplicates
repoquery_cmd = '--repoid="{0}"'.format(repo)
else:
repoquery_cmd = '--all --repoid="{0}" --show-duplicates'.format(repo)
def _check_args(args, name):
'''
Do glob matching on args and return True if a match was found.
Otherwise, return False
'''
for arg in args:
repoquery_cmd += ' "{0}"'.format(arg)
all_pkgs = _repoquery_pkginfo(repoquery_cmd)
for pkg in all_pkgs:
if fnmatch.fnmatch(name, arg):
return True
return False
def _no_repository_packages():
'''
Check yum version, the repository-packages subcommand is only in
3.4.3 and newer.
'''
if _yum() == 'yum':
yum_version = _LooseVersion(
__salt__['cmd.run'](
['yum', '--version'],
python_shell=False
).splitlines()[0].strip()
)
return yum_version < _LooseVersion('3.4.3')
return False
def _parse_output(output, strict=False):
for pkg in _yum_pkginfo(output):
if strict and (pkg.repoid not in repos
or not _check_args(args, pkg.name)):
continue
repo_dict = ret.setdefault(pkg.repoid, {})
version_list = repo_dict.setdefault(pkg.name, [])
version_list.append(pkg.version)
version_list = repo_dict.setdefault(pkg.name, set())
version_list.add(pkg.version)
if _no_repository_packages():
cmd_prefix = ['yum', '--quiet', 'list']
for pkg_src in ('installed', 'available'):
# Check installed packages first
out = __salt__['cmd.run_all'](
cmd_prefix + [pkg_src],
output_loglevel='trace',
ignore_retcode=True,
python_shell=False
)
if out['retcode'] == 0:
_parse_output(out['stdout'], strict=True)
else:
for repo in repos:
cmd = [_yum(), '--quiet', 'repository-packages', repo,
'list', '--showduplicates']
# Can't concatenate because args is a tuple, using list.extend()
cmd.extend(args)
out = __salt__['cmd.run_all'](cmd,
output_loglevel='trace',
ignore_retcode=True,
python_shell=False)
if out['retcode'] != 0 and 'Error:' in out['stdout']:
continue
_parse_output(out['stdout'])
for reponame in ret:
# Sort versions newest to oldest
for pkgname in ret[reponame]:
sorted_versions = sorted(
[_LooseVersion(x) for x in ret[reponame][pkgname]],
@ -780,14 +868,18 @@ def list_upgrades(refresh=True, **kwargs):
if salt.utils.is_true(refresh):
refresh_db(**kwargs)
if _yum() == 'dnf':
upgrades_cmd = '{0} --upgrades'.format(repo_arg)
else:
upgrades_cmd = '{0} {1} --all --pkgnarrow=updates'.format(
repo_arg, exclude_arg)
cmd = [_yum(), '--quiet']
cmd.extend(repo_arg)
cmd.extend(exclude_arg)
cmd.extend(['list', 'upgrades' if _yum() == 'dnf' else 'updates'])
out = __salt__['cmd.run_all'](cmd,
output_loglevel='trace',
ignore_retcode=True,
python_shell=False)
if out['retcode'] != 0 and 'Error:' in out:
return {}
updates = _repoquery_pkginfo(upgrades_cmd)
return dict([(x.name, x.version) for x in updates])
return dict([(x.name, x.version) for x in _yum_pkginfo(out['stdout'])])
def info_installed(*names):
@ -849,12 +941,12 @@ def check_db(*names, **kwargs):
exclude_arg = _get_excludes_option(repoquery=True, **kwargs)
if _yum() == 'dnf':
repoquery_base = '{0} --whatprovides'.format(repo_arg)
repoquery_base = repo_arg + ['--whatprovides']
avail_cmd = repo_arg
else:
repoquery_base = '{0} {1} --all --quiet --whatprovides'.format(
repo_arg, exclude_arg)
avail_cmd = '{0} --pkgnarrow=all --all'.format(repo_arg)
repoquery_base = repo_arg + exclude_arg
repoquery_base.extend(['--all', '--quiet', '--whatprovides'])
avail_cmd = repo_arg + ['--pkgnarrow=all', '--all']
if 'pkg._avail' in __context__:
avail = __context__['pkg._avail']
@ -878,7 +970,7 @@ def check_db(*names, **kwargs):
for name in names:
ret.setdefault(name, {})['found'] = name in avail
if not ret[name]['found']:
repoquery_cmd = '{0} {1}'.format(repoquery_base, name)
repoquery_cmd = repoquery_base + [name]
provides = sorted(
set(x.name for x in _repoquery_pkginfo(repoquery_cmd))
)
@ -935,26 +1027,19 @@ def refresh_db(**kwargs):
repo_arg = _get_repo_options(**kwargs)
exclude_arg = _get_excludes_option(**kwargs)
branch_arg = _get_branch_option(**kwargs)
yum_cmd = _yum()
clean_cmd = '{yum} -q clean expire-cache {repo} {exclude} {branch}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg
)
update_cmd = '{yum} -q check-update {repo} {exclude} {branch}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg
)
clean_cmd = [_yum(), '--quiet', 'clean', 'expire-cache']
update_cmd = [_yum(), '--quiet', 'check-update']
for args in (repo_arg, exclude_arg, branch_arg):
if args:
clean_cmd.extend(args)
update_cmd.extend(args)
__salt__['cmd.run'](clean_cmd)
return retcodes.get(
__salt__['cmd.retcode'](update_cmd, ignore_retcode=True),
False
)
__salt__['cmd.run'](clean_cmd, python_shell=False)
result = __salt__['cmd.retcode'](update_cmd,
ignore_retcode=True,
python_shell=False)
return retcodes.get(result, False)
def clean_metadata(**kwargs):
@ -1179,7 +1264,7 @@ def install(name=None,
arch = '.' + archpart
pkgname = namepart
pkgstr = '"{0}-{1}{2}"'.format(pkgname, version_num, arch)
pkgstr = '{0}-{1}{2}'.format(pkgname, version_num, arch)
else:
pkgstr = pkgpath
@ -1198,39 +1283,53 @@ def install(name=None,
else:
downgrade.append(pkgstr)
yum_cmd = _yum()
def _add_common_args(cmd):
'''
DRY function to add args common to all yum/dnf commands
'''
for args in (repo_arg, exclude_arg, branch_arg):
if args:
cmd.extend(args)
if skip_verify:
cmd.append('--nogpgcheck')
if targets:
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
gpgcheck='--nogpgcheck' if skip_verify else '',
pkg=' '.join(targets),
cmd = [_yum(), '-y']
if _yum() == 'dnf':
cmd.extend(['--best', '--allowerasing'])
_add_common_args(cmd)
cmd.append('install')
cmd.extend(targets)
__salt__['cmd.run_all'](
cmd,
output_loglevel='trace',
python_shell=False,
redirect_stderr=True
)
__salt__['cmd.run'](cmd, output_loglevel='trace')
if downgrade:
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
gpgcheck='--nogpgcheck' if skip_verify else '',
pkg=' '.join(downgrade),
cmd = [_yum(), '-y']
_add_common_args(cmd)
cmd.append('downgrade')
cmd.extend(downgrade)
__salt__['cmd.run_all'](
cmd,
output_loglevel='trace',
python_shell=False,
redirect_stderr=True
)
__salt__['cmd.run'](cmd, output_loglevel='trace')
if to_reinstall:
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
gpgcheck='--nogpgcheck' if skip_verify else '',
pkg=' '.join(six.itervalues(to_reinstall)),
cmd = [_yum(), '-y']
_add_common_args(cmd)
cmd.append('reinstall')
cmd.extend(six.itervalues(to_reinstall))
__salt__['cmd.run_all'](
cmd,
output_loglevel='trace',
python_shell=False,
redirect_stderr=True
)
__salt__['cmd.run'](cmd, output_loglevel='trace')
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
@ -1291,14 +1390,15 @@ def upgrade(refresh=True, skip_verify=False, **kwargs):
refresh_db(**kwargs)
old = list_pkgs()
cmd = '{yum} -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format(
yum=_yum(),
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
gpgcheck='--nogpgcheck' if skip_verify else '')
cmd = [_yum(), '--quiet', '-y']
for args in (repo_arg, exclude_arg, branch_arg):
if args:
cmd.extend(args)
if skip_verify:
cmd.append('--nogpgcheck')
cmd.append('upgrade')
__salt__['cmd.run'](cmd, output_loglevel='trace')
__salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False)
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
ret = salt.utils.compare_dicts(old, new)
@ -1343,8 +1443,7 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
targets = [x for x in pkg_params if x in old]
if not targets:
return {}
quoted_targets = [_cmd_quote(target) for target in targets]
cmd = _yum() + ' -y remove {0}'.format(' '.join(quoted_targets))
cmd = [_yum(), '-y', 'remove'] + targets
__salt__['cmd.run'](cmd, output_loglevel='trace')
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
@ -1465,8 +1564,8 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
ret[target]['comment'] = ('Package {0} is set to be held.'
.format(target))
else:
cmd = _yum() + ' versionlock {0}'.format(target)
out = __salt__['cmd.run_all'](cmd)
cmd = [_yum(), 'versionlock', target]
out = __salt__['cmd.run_all'](cmd, python_shell=False)
if out['retcode'] == 0:
ret[target].update(result=True)
@ -1548,19 +1647,15 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
'result': False,
'comment': ''}
search_locks = [lock for lock in current_locks
if target in lock]
search_locks = [x for x in current_locks if target in x]
if search_locks:
if 'test' in __opts__ and __opts__['test']:
ret[target].update(result=None)
ret[target]['comment'] = ('Package {0} is set to be unheld.'
.format(target))
else:
quoted_targets = [_cmd_quote(item) for item in search_locks]
cmd = _yum() + ' versionlock delete {0}'.format(
' '.join(quoted_targets)
)
out = __salt__['cmd.run_all'](cmd)
cmd = [_yum(), 'versionlock', 'delete'] + search_locks
out = __salt__['cmd.run_all'](cmd, python_shell=False)
if out['retcode'] == 0:
ret[target].update(result=True)
@ -2316,14 +2411,11 @@ def owner(*paths):
return ''
ret = {}
for path in paths:
cmd = 'rpm -qf --queryformat {0} \'{1}\''.format(
_cmd_quote('%{{NAME}}'),
path
)
ret[path] = __salt__['cmd.run_stdout'](
cmd.format(path),
output_loglevel='trace'
)
['rpm', '-qf', '--queryformat', '%{NAME}', path],
output_loglevel='trace',
python_shell=False
)
if 'not owned' in ret[path].lower():
ret[path] = ''
if len(ret) == 1:

View file

@ -5,6 +5,7 @@ Common
# Import python libs
from __future__ import absolute_import
import collections
import logging
# Import salt libs
@ -33,7 +34,7 @@ ARCHES = ARCHES_64 + ARCHES_32 + ARCHES_PPC + ARCHES_S390 + \
QUERYFORMAT = '%{NAME}_|-%{VERSION}_|-%{RELEASE}_|-%{ARCH}_|-%{REPOID}'
def _osarch():
def get_osarch():
'''
Get the os architecture using rpm --eval
'''
@ -51,37 +52,48 @@ def check_32(arch, osarch=None):
Returns True if both the OS arch and the passed arch are 32-bit
'''
if osarch is None:
osarch = _osarch()
osarch = get_osarch()
return all(x in ARCHES_32 for x in (osarch, arch))
def pkginfo(name, version, arch, repoid):
'''
Build and return a pkginfo namedtuple
'''
pkginfo_tuple = collections.namedtuple(
'PkgInfo',
('name', 'version', 'arch', 'repoid')
)
return pkginfo_tuple(name, version, arch, repoid)
def resolve_name(name, arch, osarch=None):
'''
Resolve the package name and arch into a unique name referred to by salt.
For example, on a 64-bit OS, a 32-bit package will be pkgname.i386.
'''
if osarch is None:
osarch = get_osarch()
if not check_32(arch, osarch) and arch not in (osarch, 'noarch'):
name += '.{0}'.format(arch)
return name
def parse_pkginfo(line, osarch=None):
'''
A small helper to parse an rpm/repoquery command's output. Returns a
namedtuple
pkginfo namedtuple.
'''
# Importing `collections` here since this function is re-namespaced into
# another module
import collections
pkginfo = collections.namedtuple(
'PkgInfo',
('name', 'version', 'arch', 'repoid')
)
try:
name, pkg_version, release, arch, repoid = line.split('_|-')
name, version, release, arch, repoid = line.split('_|-')
# Handle unpack errors (should never happen with the queryformat we are
# using, but can't hurt to be careful).
except ValueError:
return None
if osarch is None:
osarch = _osarch()
if not check_32(arch, osarch):
if arch not in (osarch, 'noarch'):
name += '.{0}'.format(arch)
name = resolve_name(name, arch, osarch)
if release:
pkg_version += '-{0}'.format(release)
version += '-{0}'.format(release)
return pkginfo(name, pkg_version, arch, repoid)
return pkginfo(name, version, arch, repoid)

View file

@ -5,8 +5,10 @@
# Import Python libs
from __future__ import absolute_import
import os
# Import Salt Testing Libs
from salt.exceptions import CommandExecutionError
from salttesting import TestCase, skipIf
from salttesting.helpers import ensure_in_syspath
from salttesting.mock import (
@ -25,6 +27,26 @@ from salt.modules import systemd
systemd.__salt__ = {}
systemd.__context__ = {}
_SYSTEMCTL_STATUS = {
'sshd.service': '''\
* sshd.service - OpenSSH Daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; disabled; vendor preset: disabled)
Active: inactive (dead)''',
'foo.service': '''\
* foo.service
Loaded: not-found (Reason: No such file or directory)
Active: inactive (dead)'''
}
_LIST_UNIT_FILES = '''\
service1.service enabled
service2.service disabled
service3.service static
timer1.timer enabled
timer2.timer disabled
timer3.timer static'''
@skipIf(NO_MOCK, NO_MOCK_REASON)
class SystemdTestCase(TestCase):
@ -35,208 +57,129 @@ class SystemdTestCase(TestCase):
'''
Test to Reloads systemctl
'''
mock = MagicMock(side_effect=[1, 0])
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertFalse(systemd.systemctl_reload())
mock = MagicMock(side_effect=[
{'stdout': 'Who knows why?',
'stderr': '',
'retcode': 1,
'pid': 12345},
{'stdout': '',
'stderr': '',
'retcode': 0,
'pid': 54321},
])
with patch.dict(systemd.__salt__, {'cmd.run_all': mock}):
self.assertRaisesRegexp(
CommandExecutionError,
'Problem performing systemctl daemon-reload: Who knows why?',
systemd.systemctl_reload
)
self.assertTrue(systemd.systemctl_reload())
def test_get_enabled(self):
'''
Test to return a list of all enabled services
Test to return a list of all enabled services
'''
def sysv(name):
if name in ['d', 'e']:
return True
return False
cmd_mock = MagicMock(return_value=_LIST_UNIT_FILES)
listdir_mock = MagicMock(return_value=['foo', 'bar', 'baz', 'README'])
sd_mock = MagicMock(
return_value=set(
[x.replace('.service', '') for x in _SYSTEMCTL_STATUS]
)
)
access_mock = MagicMock(
side_effect=lambda x, y: x != os.path.join(
systemd.INITSCRIPT_PATH,
'README'
)
)
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
def sysve(name):
if name in ['e']:
return True
return False
mock = MagicMock(return_value={"a": "enabled", "b": "enabled",
"c": "disabled"})
lmock = MagicMock(return_value={"d": "disabled",
"a": "disabled",
"b": "disabled",
"e": "disabled"})
with patch.object(systemd, "_sysv_is_disabled", sysve):
with patch.object(systemd, "_service_is_sysv", sysv):
with patch.object(systemd, '_get_all_unit_files', mock):
with patch.object(systemd, '_get_all_units', lmock):
self.assertListEqual(
systemd.get_enabled(), ["a", "b", "d"])
with patch.dict(systemd.__salt__, {'cmd.run': cmd_mock}):
with patch.object(os, 'listdir', listdir_mock):
with patch.object(systemd, '_get_systemd_services', sd_mock):
with patch.object(os, 'access', side_effect=access_mock):
with patch.object(systemd, '_sysv_enabled',
sysv_enabled_mock):
self.assertListEqual(
systemd.get_enabled(),
['baz', 'service1', 'timer1.timer']
)
def test_get_disabled(self):
'''
Test to return a list of all disabled services
Test to return a list of all disabled services
'''
mock = MagicMock(return_value={"a": "enabled", "b": "enabled",
"c": "disabled"})
with patch.object(systemd, '_get_all_unit_files', mock):
mock = MagicMock(return_value={})
with patch.object(systemd, '_get_all_legacy_init_scripts', mock):
self.assertListEqual(systemd.get_disabled(), ["c"])
cmd_mock = MagicMock(return_value=_LIST_UNIT_FILES)
# 'foo' should collide with the systemd services (as returned by
# sd_mock) and thus not be returned by _get_sysv_services(). It doesn't
# matter that it's not part of the _LIST_UNIT_FILES output, we just
# want to ensure that 'foo' isn't identified as a disabled initscript
# even though below we are mocking it to show as not enabled (since
# only 'baz' will be considered an enabled sysv service).
listdir_mock = MagicMock(return_value=['foo', 'bar', 'baz', 'README'])
sd_mock = MagicMock(
return_value=set(
[x.replace('.service', '') for x in _SYSTEMCTL_STATUS]
)
)
access_mock = MagicMock(
side_effect=lambda x, y: x != os.path.join(
systemd.INITSCRIPT_PATH,
'README'
)
)
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
with patch.dict(systemd.__salt__, {'cmd.run': cmd_mock}):
with patch.object(os, 'listdir', listdir_mock):
with patch.object(systemd, '_get_systemd_services', sd_mock):
with patch.object(os, 'access', side_effect=access_mock):
with patch.object(systemd, '_sysv_enabled',
sysv_enabled_mock):
self.assertListEqual(
systemd.get_disabled(),
['bar', 'service2', 'timer2.timer']
)
def test_get_all(self):
'''
Test to return a list of all available services
Test to return a list of all available services
'''
mock = MagicMock(return_value={"a": "enabled", "b": "enabled",
"c": "disabled"})
with patch.object(systemd, '_get_all_units', mock):
mock = MagicMock(return_value={"a1": "enabled", "b1": "disabled",
"c1": "enabled"})
with patch.object(systemd, '_get_all_unit_files', mock):
mock = MagicMock(return_value={})
with patch.object(systemd,
'_get_all_legacy_init_scripts', mock):
self.assertListEqual(systemd.get_all(),
['a', 'a1', 'b', 'b1', 'c', 'c1'])
listdir_mock = MagicMock(side_effect=[
['foo.service', 'multi-user.target.wants', 'mytimer.timer'],
['foo.service', 'multi-user.target.wants', 'bar.service'],
['mysql', 'nginx', 'README']
])
access_mock = MagicMock(
side_effect=lambda x, y: x != os.path.join(
systemd.INITSCRIPT_PATH,
'README'
)
)
with patch.object(os, 'listdir', listdir_mock):
with patch.object(os, 'access', side_effect=access_mock):
self.assertListEqual(
systemd.get_all(),
['bar', 'foo', 'mysql', 'mytimer.timer', 'nginx']
)
def test_available(self):
'''
Test to check that the given service is available
Test to check that the given service is available
'''
mock = MagicMock(side_effect=["a", "@", "c"])
with patch.object(systemd, '_canonical_template_unit_name', mock):
mock = MagicMock(side_effect=[{"a": "z", "b": "z"},
{"@": "z", "b": "z"},
{"a": "z", "b": "z"}])
with patch.object(systemd, 'get_all', mock):
self.assertTrue(systemd.available("sshd"))
self.assertTrue(systemd.available("sshd"))
self.assertFalse(systemd.available("sshd"))
mock = MagicMock(side_effect=lambda x: _SYSTEMCTL_STATUS[x])
with patch.object(systemd, '_systemctl_status', mock):
self.assertTrue(systemd.available('sshd.service'))
self.assertFalse(systemd.available('foo.service'))
def test_missing(self):
'''
Test to the inverse of service.available.
'''
mock = MagicMock(return_value=True)
with patch.object(systemd, 'available', mock):
self.assertFalse(systemd.missing("sshd"))
def test_unmask(self):
'''
Test to unmask the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.unmask("sshd"))
def test_start(self):
'''
Test to start the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.start("sshd"))
def test_stop(self):
'''
Test to stop the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.stop("sshd"))
def test_restart(self):
'''
Test to restart the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.restart("sshd"))
def test_reload_(self):
'''
Test to Reload the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.reload_("sshd"))
def test_force_reload(self):
'''
Test to force-reload the specified service with systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.force_reload("sshd"))
def test_status(self):
'''
Test to return the status for a service via systemd
'''
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
self.assertTrue(systemd.status("sshd"))
def test_enable(self):
'''
Test to enable the named service to start when the system boots
'''
exe = MagicMock(return_value='foo')
tmock = MagicMock(return_value=True)
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
with patch.object(systemd, "_service_is_sysv", mock):
self.assertTrue(systemd.enable("sshd"))
with patch.object(systemd, "_get_service_exec", exe):
with patch.object(systemd, "_service_is_sysv", tmock):
self.assertTrue(systemd.enable("sshd"))
def test_disable(self):
'''
Test to disable the named service to not
start when the system boots
'''
exe = MagicMock(return_value='foo')
tmock = MagicMock(return_value=True)
mock = MagicMock(return_value=False)
with patch.object(systemd, '_untracked_custom_unit_found', mock):
with patch.object(systemd, '_unit_file_changed', mock):
with patch.dict(systemd.__salt__, {'cmd.retcode': mock}):
with patch.object(systemd, "_service_is_sysv", mock):
self.assertTrue(systemd.disable("sshd"))
with patch.object(systemd, "_get_service_exec", exe):
with patch.object(systemd, "_service_is_sysv", tmock):
self.assertTrue(systemd.disable("sshd"))
def test_enabled(self):
'''
Test to return if the named service is enabled to start on boot
'''
mock = MagicMock(return_value=True)
with patch.object(systemd, '_enabled', mock):
self.assertTrue(systemd.enabled("sshd"))
def test_disabled(self):
'''
Test to Return if the named service is disabled to start on boot
'''
mock = MagicMock(return_value=True)
with patch.object(systemd, '_enabled', mock):
self.assertFalse(systemd.disabled("sshd"))
mock = MagicMock(side_effect=lambda x: _SYSTEMCTL_STATUS[x])
with patch.object(systemd, '_systemctl_status', mock):
self.assertFalse(systemd.missing('sshd.service'))
self.assertTrue(systemd.missing('foo.service'))
def test_show(self):
'''