Merge pull request #54958 from aplanas/backport_50380

(Backport 50380) systemd: add optional root parameter
This commit is contained in:
Daniel Wozniak 2019-12-11 08:25:00 -07:00 committed by GitHub
commit e43f211a92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 76 deletions

View file

@ -56,7 +56,7 @@ def __virtual__():
Only work on systems that have been booted with systemd
'''
if __grains__['kernel'] == 'Linux' \
and salt.utils.systemd.booted(__context__):
and salt.utils.systemd.booted(__context__):
return __virtualname__
return (
False,
@ -65,6 +65,16 @@ def __virtual__():
)
def _root(path, root):
'''
Relocate an absolute path to a new root directory.
'''
if root:
return os.path.join(root, os.path.relpath(path, os.path.sep))
else:
return path
def _canonical_unit_name(name):
'''
Build a canonical unit name treating unit names without one
@ -123,15 +133,15 @@ def _check_for_unit_changes(name):
__context__[contextkey] = True
def _check_unmask(name, unmask, unmask_runtime):
def _check_unmask(name, unmask, unmask_runtime, root=None):
'''
Common code for conditionally removing masks before making changes to a
service's state.
'''
if unmask:
unmask_(name, runtime=False)
unmask_(name, runtime=False, root=root)
if unmask_runtime:
unmask_(name, runtime=True)
unmask_(name, runtime=True, root=root)
def _clear_context():
@ -193,15 +203,16 @@ def _default_runlevel():
return runlevel
def _get_systemd_services():
def _get_systemd_services(root):
'''
Use os.listdir() to get all the unit files
'''
ret = set()
for path in SYSTEM_CONFIG_PATHS + (LOCAL_CONFIG_PATH,):
# Make sure user has access to the path, and if the path is a link
# it's likely that another entry in SYSTEM_CONFIG_PATHS or LOCAL_CONFIG_PATH
# points to it, so we can ignore it.
# Make sure user has access to the path, and if the path is a
# link it's likely that another entry in SYSTEM_CONFIG_PATHS
# or LOCAL_CONFIG_PATH points to it, so we can ignore it.
path = _root(path, root)
if os.access(path, os.R_OK) and not os.path.islink(path):
for fullname in os.listdir(path):
try:
@ -213,19 +224,20 @@ def _get_systemd_services():
return ret
def _get_sysv_services(systemd_services=None):
def _get_sysv_services(root, systemd_services=None):
'''
Use os.listdir() and os.access() to get all the initscripts
'''
initscript_path = _root(INITSCRIPT_PATH, root)
try:
sysv_services = os.listdir(INITSCRIPT_PATH)
sysv_services = os.listdir(initscript_path)
except OSError as exc:
if exc.errno == errno.ENOENT:
pass
elif exc.errno == errno.EACCES:
log.error(
'Unable to check sysvinit scripts, permission denied to %s',
INITSCRIPT_PATH
initscript_path
)
else:
log.error(
@ -236,11 +248,11 @@ def _get_sysv_services(systemd_services=None):
return []
if systemd_services is None:
systemd_services = _get_systemd_services()
systemd_services = _get_systemd_services(root)
ret = []
for sysv_service in sysv_services:
if os.access(os.path.join(INITSCRIPT_PATH, sysv_service), os.X_OK):
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 '
@ -303,7 +315,8 @@ def _strip_scope(msg):
return '\n'.join(ret).strip()
def _systemctl_cmd(action, name=None, systemd_scope=False, no_block=False):
def _systemctl_cmd(action, name=None, systemd_scope=False, no_block=False,
root=None):
'''
Build a systemctl command line. Treat unit names without one
of the valid suffixes as a service.
@ -316,6 +329,8 @@ def _systemctl_cmd(action, name=None, systemd_scope=False, no_block=False):
ret.append('systemctl')
if no_block:
ret.append('--no-block')
if root:
ret.extend(['--root', root])
if isinstance(action, six.string_types):
action = shlex.split(action)
ret.extend(action)
@ -343,26 +358,27 @@ def _systemctl_status(name):
return __context__[contextkey]
def _sysv_enabled(name):
def _sysv_enabled(name, root):
'''
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.
'''
# Find exact match (disambiguate matches like "S01anacron" for cron)
for match in glob.glob('/etc/rc%s.d/S*%s' % (_runlevel(), name)):
rc = _root('/etc/rc{}.d/S*{}'.format(_runlevel(), name), root)
for match in glob.glob(rc):
if re.match(r'S\d{,2}%s' % name, os.path.basename(match)):
return True
return False
def _untracked_custom_unit_found(name):
def _untracked_custom_unit_found(name, root=None):
'''
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))
system = _root('/etc/systemd/system', root)
unit_path = os.path.join(system, _canonical_unit_name(name))
return os.access(unit_path, os.R_OK) and not _check_available(name)
@ -371,7 +387,8 @@ 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)['stdout'].lower()
status = _systemctl_status(name)['stdout'].lower()
return "'systemctl daemon-reload'" in status
def systemctl_reload():
@ -389,8 +406,7 @@ def systemctl_reload():
out = __salt__['cmd.run_all'](
_systemctl_cmd('--system daemon-reload'),
python_shell=False,
redirect_stderr=True
)
redirect_stderr=True)
if out['retcode'] != 0:
raise CommandExecutionError(
'Problem performing systemctl daemon-reload: %s' % out['stdout']
@ -414,8 +430,7 @@ def get_running():
out = __salt__['cmd.run'](
_systemctl_cmd('--full --no-legend --no-pager'),
python_shell=False,
ignore_retcode=True,
)
ignore_retcode=True)
for line in salt.utils.itertools.split(out, '\n'):
try:
comps = line.strip().split()
@ -438,10 +453,13 @@ def get_running():
return sorted(ret)
def get_enabled():
def get_enabled(root=None):
'''
Return a list of all enabled services
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -452,10 +470,10 @@ def get_enabled():
# 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'),
_systemctl_cmd('--full --no-legend --no-pager list-unit-files',
root=root),
python_shell=False,
ignore_retcode=True,
)
ignore_retcode=True)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
@ -473,15 +491,18 @@ def get_enabled():
# Add in any sysvinit services that are enabled
ret.update(set(
[x for x in _get_sysv_services() if _sysv_enabled(x)]
[x for x in _get_sysv_services(root) if _sysv_enabled(x, root)]
))
return sorted(ret)
def get_disabled():
def get_disabled(root=None):
'''
Return a list of all disabled services
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -492,10 +513,10 @@ def get_disabled():
# 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'),
_systemctl_cmd('--full --no-legend --no-pager list-unit-files',
root=root),
python_shell=False,
ignore_retcode=True,
)
ignore_retcode=True)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
@ -513,17 +534,20 @@ def get_disabled():
# Add in any sysvinit services that are disabled
ret.update(set(
[x for x in _get_sysv_services() if not _sysv_enabled(x)]
[x for x in _get_sysv_services(root) if not _sysv_enabled(x, root)]
))
return sorted(ret)
def get_static():
def get_static(root=None):
'''
.. versionadded:: 2015.8.5
Return a list of all static services
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -534,10 +558,10 @@ def get_static():
# 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'),
_systemctl_cmd('--full --no-legend --no-pager list-unit-files',
root=root),
python_shell=False,
ignore_retcode=True,
)
ignore_retcode=True)
for line in salt.utils.itertools.split(out, '\n'):
try:
fullname, unit_state = line.strip().split(None, 1)
@ -557,18 +581,21 @@ def get_static():
return sorted(ret)
def get_all():
def get_all(root=None):
'''
Return a list of all available services
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
salt '*' service.get_all
'''
ret = _get_systemd_services()
ret.update(set(_get_sysv_services(systemd_services=ret)))
ret = _get_systemd_services(root)
ret.update(set(_get_sysv_services(root, systemd_services=ret)))
return sorted(ret)
@ -606,7 +633,7 @@ def missing(name):
return not available(name)
def unmask_(name, runtime=False):
def unmask_(name, runtime=False, root=None):
'''
.. versionadded:: 2015.5.0
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
@ -633,6 +660,9 @@ def unmask_(name, runtime=False):
removes a runtime mask only when this argument is set to ``True``,
and otherwise removes an indefinite mask.
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -641,15 +671,16 @@ def unmask_(name, runtime=False):
salt '*' service.unmask foo runtime=True
'''
_check_for_unit_changes(name)
if not masked(name, runtime):
if not masked(name, runtime, root=root):
log.debug('Service \'%s\' is not %smasked',
name, 'runtime-' if runtime else '')
return True
cmd = 'unmask --runtime' if runtime else 'unmask'
out = __salt__['cmd.run_all'](_systemctl_cmd(cmd, name, systemd_scope=True),
python_shell=False,
redirect_stderr=True)
out = __salt__['cmd.run_all'](
_systemctl_cmd(cmd, name, systemd_scope=True, root=root),
python_shell=False,
redirect_stderr=True)
if out['retcode'] != 0:
raise CommandExecutionError('Failed to unmask service \'%s\'' % name)
@ -657,7 +688,7 @@ def unmask_(name, runtime=False):
return True
def mask(name, runtime=False):
def mask(name, runtime=False, root=None):
'''
.. versionadded:: 2015.5.0
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
@ -678,6 +709,9 @@ def mask(name, runtime=False):
.. versionadded:: 2015.8.5
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -688,9 +722,10 @@ def mask(name, runtime=False):
_check_for_unit_changes(name)
cmd = 'mask --runtime' if runtime else 'mask'
out = __salt__['cmd.run_all'](_systemctl_cmd(cmd, name, systemd_scope=True),
python_shell=False,
redirect_stderr=True)
out = __salt__['cmd.run_all'](
_systemctl_cmd(cmd, name, systemd_scope=True, root=root),
python_shell=False,
redirect_stderr=True)
if out['retcode'] != 0:
raise CommandExecutionError(
@ -701,7 +736,7 @@ def mask(name, runtime=False):
return True
def masked(name, runtime=False):
def masked(name, runtime=False, root=None):
'''
.. versionadded:: 2015.8.0
.. versionchanged:: 2015.8.5
@ -731,6 +766,9 @@ def masked(name, runtime=False):
only checks for runtime masks if this argument is set to ``True``.
Otherwise, it will check for an indefinite mask.
root
Enable/disable/mask unit files in the specified root directory
CLI Examples:
.. code-block:: bash
@ -739,7 +777,7 @@ def masked(name, runtime=False):
salt '*' service.masked foo runtime=True
'''
_check_for_unit_changes(name)
root_dir = '/run' if runtime else '/etc'
root_dir = _root('/run' if runtime else '/etc', root)
link_path = os.path.join(root_dir,
'systemd',
'system',
@ -1055,9 +1093,10 @@ def status(name, sig=None): # pylint: disable=unused-argument
results = {}
for service in services:
_check_for_unit_changes(service)
results[service] = __salt__['cmd.retcode'](_systemctl_cmd('is-active', service),
python_shell=False,
ignore_retcode=True) == 0
results[service] = __salt__['cmd.retcode'](
_systemctl_cmd('is-active', service),
python_shell=False,
ignore_retcode=True) == 0
if contains_globbing:
return results
return results[name]
@ -1065,7 +1104,8 @@ def status(name, sig=None): # pylint: disable=unused-argument
# **kwargs is required to maintain consistency with the API established by
# Salt's service management states.
def enable(name, no_block=False, unmask=False, unmask_runtime=False, **kwargs): # pylint: disable=unused-argument
def enable(name, no_block=False, unmask=False, unmask_runtime=False,
root=None, **kwargs): # pylint: disable=unused-argument
'''
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
@ -1101,6 +1141,9 @@ def enable(name, no_block=False, unmask=False, unmask_runtime=False, **kwargs):
In previous releases, Salt would simply unmask a service before
enabling. This behavior is no longer the default.
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -1108,8 +1151,8 @@ def enable(name, no_block=False, unmask=False, unmask_runtime=False, **kwargs):
salt '*' service.enable <service name>
'''
_check_for_unit_changes(name)
_check_unmask(name, unmask, unmask_runtime)
if name in _get_sysv_services():
_check_unmask(name, unmask, unmask_runtime, root)
if name in _get_sysv_services(root):
cmd = []
if salt.utils.systemd.has_scope(__context__) \
and __salt__['config.get']('systemd.scope', True):
@ -1123,7 +1166,8 @@ def enable(name, no_block=False, unmask=False, unmask_runtime=False, **kwargs):
python_shell=False,
ignore_retcode=True) == 0
ret = __salt__['cmd.run_all'](
_systemctl_cmd('enable', name, systemd_scope=True, no_block=no_block),
_systemctl_cmd('enable', name, systemd_scope=True, no_block=no_block,
root=root),
python_shell=False,
ignore_retcode=True)
@ -1137,7 +1181,7 @@ def enable(name, no_block=False, unmask=False, unmask_runtime=False, **kwargs):
# The unused kwargs argument is required to maintain consistency with the API
# established by Salt's service management states.
def disable(name, no_block=False, **kwargs): # pylint: disable=unused-argument
def disable(name, no_block=False, root=None, **kwargs): # pylint: disable=unused-argument
'''
.. versionchanged:: 2015.8.12,2016.3.3,2016.11.0
On minions running systemd>=205, `systemd-run(1)`_ is now used to
@ -1157,6 +1201,9 @@ def disable(name, no_block=False, **kwargs): # pylint: disable=unused-argument
.. versionadded:: 2017.7.0
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -1164,7 +1211,7 @@ def disable(name, no_block=False, **kwargs): # pylint: disable=unused-argument
salt '*' service.disable <service name>
'''
_check_for_unit_changes(name)
if name in _get_sysv_services():
if name in _get_sysv_services(root):
cmd = []
if salt.utils.systemd.has_scope(__context__) \
and __salt__['config.get']('systemd.scope', True):
@ -1179,17 +1226,21 @@ def disable(name, no_block=False, **kwargs): # pylint: disable=unused-argument
ignore_retcode=True) == 0
# Using cmd.run_all instead of cmd.retcode here to make unit tests easier
return __salt__['cmd.run_all'](
_systemctl_cmd('disable', name, systemd_scope=True, no_block=no_block),
_systemctl_cmd('disable', name, systemd_scope=True, no_block=no_block,
root=root),
python_shell=False,
ignore_retcode=True)['retcode'] == 0
# 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
def enabled(name, root=None, **kwargs): # pylint: disable=unused-argument
'''
Return if the named service is enabled to start on boot
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
@ -1199,7 +1250,7 @@ def enabled(name, **kwargs): # pylint: disable=unused-argument
# 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),
if __salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name, root=root),
python_shell=False,
ignore_retcode=True) == 0:
return True
@ -1207,43 +1258,50 @@ def enabled(name, **kwargs): # pylint: disable=unused-argument
# 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,
local_config_path = _root(LOCAL_CONFIG_PATH, '/')
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
elif name in _get_sysv_services():
return _sysv_enabled(name)
elif name in _get_sysv_services(root):
return _sysv_enabled(name, root)
return False
def disabled(name):
def disabled(name, root=None):
'''
Return if the named service is disabled from starting on boot
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
.. code-block:: bash
salt '*' service.disabled <service name>
'''
return not enabled(name)
return not enabled(name, root=root)
def show(name):
def show(name, root=None):
'''
.. versionadded:: 2014.7.0
Show properties of one or more units/jobs or the manager
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
salt '*' service.show <service name>
'''
ret = {}
out = __salt__['cmd.run'](_systemctl_cmd('show', name),
out = __salt__['cmd.run'](_systemctl_cmd('show', name, root=root),
python_shell=False)
for line in salt.utils.itertools.split(out, '\n'):
comps = line.split('=')
@ -1263,19 +1321,22 @@ def show(name):
return ret
def execs():
def execs(root=None):
'''
.. versionadded:: 2014.7.0
Return a list of all files specified as ``ExecStart`` for all services.
root
Enable/disable/mask unit files in the specified root directory
CLI Example:
salt '*' service.execs
'''
ret = {}
for service in get_all():
data = show(service)
for service in get_all(root=root):
data = show(service, root=root)
if 'ExecStart' not in data:
continue
ret[service] = data['ExecStart']['path']

View file

@ -110,7 +110,7 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin):
'README'
)
)
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
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):
@ -146,7 +146,7 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin):
'README'
)
)
sysv_enabled_mock = MagicMock(side_effect=lambda x: x == 'baz')
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):