Merge pull request #32560 from rallytime/merge-develop

[develop] Merge forward from 2016.3 to develop
This commit is contained in:
Nicole Thomas 2016-04-14 09:44:39 -06:00
commit 04efcf52b6
23 changed files with 694 additions and 208 deletions

View File

@ -11,6 +11,22 @@ Salt 2015.8.7 Release Notes
Changes for v2015.8.4..v2015.8.7
--------------------------------
For :py:mod:`pkg.installed <salt.states.pkg.installed>` states, on Linux
distributions which use yum/dnf, packages which have a non-zero epoch in the
version number now require this epoch to be included when specifying an exact
version for a package. For example:
.. code-block:: yaml
vim-enhanced:
pkg.installed:
- version: 2:7.4.160-1.el7
The :py:mod:`pkg.latest_version <salt.modules.yumpkg.latest_version>` and
:py:mod:`pkg.list_repo_pkgs <salt.modules.yumpkg.list_repo_pkgs>` functions can
be used to get the correct version string to use, as they will now contain the
epoch when it is non-zero.
Extended changelog courtesy of Todd Stansell (https://github.com/tjstansell/salt-changelogs):
*Generated at: 2016-02-11T22:13:51Z*

View File

@ -25,7 +25,8 @@ except ImportError:
HAS_LDAP = False
# Defaults, override in master config
__defopts__ = {'auth.ldap.uri': '',
__defopts__ = {'auth.ldap.basedn': '',
'auth.ldap.uri': '',
'auth.ldap.server': 'localhost',
'auth.ldap.port': '389',
'auth.ldap.tls': False,
@ -45,16 +46,10 @@ def _config(key, mandatory=True, opts=None):
'''
Return a value for 'name' from master config file options or defaults.
'''
try:
if opts:
try:
return opts['auth.ldap.{0}'.format(key)]
except KeyError:
if mandatory:
msg = 'missing auth.ldap.{0} in master config'.format(key)
raise SaltInvocationError(msg)
return False
value = opts['auth.ldap.{0}'.format(key)]
else:
try:
value = __opts__['auth.ldap.{0}'.format(key)]
except KeyError:
try:

View File

@ -18,6 +18,7 @@ import stat
import traceback
import binascii
import weakref
import getpass
# Import third party libs
import salt.ext.six as six
@ -102,6 +103,11 @@ def gen_keys(keydir, keyname, keysize, user=None):
# Between first checking and the generation another process has made
# a key! Use the winner's key
return priv
# Do not try writing anything, if directory has no permissions.
if not os.access(keydir, os.W_OK):
raise IOError('Write access denied to "{0}" for user "{1}".'.format(os.path.abspath(keydir), getpass.getuser()))
cumask = os.umask(191)
with salt.utils.fopen(priv, 'wb+') as f:
f.write(gen.exportKey('PEM'))

View File

@ -15,6 +15,14 @@ framer jobcleaner be active first setup
frame fsclean
enter
do salt raet maint fileserver clean
go clearfslocks
frame clearfslocks
enter
do salt raet maint fileserver clear locks
go cleargitpillarlocks
frame cleargitpillarlocks
enter
do salt raet maint git pillar clear locks
go start
frame start
do salt raet maint old jobs clear

View File

@ -111,6 +111,40 @@ class SaltRaetMaintFileserverClean(ioflo.base.deeding.Deed):
salt.daemons.masterapi.clean_fsbackend(self.opts.value)
class SaltRaetMaintFileserverClearLocks(ioflo.base.deeding.Deed):
'''
Clear the fileserver backend caches
FloScript:
do salt raet maint fileserver clear locks at enter
'''
Ioinits = {'opts': '.salt.opts'}
def action(self):
'''
Clean!
'''
salt.daemons.masterapi.clear_fsbackend_locks(self.opts.value)
class SaltRaetMaintGitPillarClearLocks(ioflo.base.deeding.Deed):
'''
Clear the fileserver backend caches
FloScript:
do salt raet maint git pillar clear locks at enter
'''
Ioinits = {'opts': '.salt.opts'}
def action(self):
'''
Clean!
'''
salt.daemons.masterapi.clear_git_pillar_locks(self.opts.value)
class SaltRaetMaintOldJobsClear(ioflo.base.deeding.Deed):
'''
Iterate over the jobs directory and clean out the old jobs

View File

@ -30,6 +30,7 @@ import salt.key
import salt.fileserver
import salt.utils.atomicfile
import salt.utils.event
import salt.utils.gitfs
import salt.utils.verify
import salt.utils.minions
import salt.utils.gzip_util
@ -137,6 +138,36 @@ def clean_fsbackend(opts):
)
def clear_fsbackend_locks(opts):
'''
Clear any locks from configured backends
'''
for back_name in ('git', 'hg', 'svn'):
if back_name in opts['fileserver_backend']:
full_name = back_name + 'fs'
backend = getattr(salt.fileserver, full_name, None)
if backend is None:
log.warning('Unable to access %s backend', full_name)
continue
backend.__opts__ = opts
backend.clear_lock()
def clear_git_pillar_locks(opts):
'''
Clear any update/checkout locks present in git_pillar remotes
'''
for ext_pillar in opts.get('ext_pillar', []):
pillar_type = next(iter(ext_pillar))
if pillar_type == 'git' and isinstance(ext_pillar[pillar_type], list):
pillar = salt.utils.gitfs.GitPillar(opts)
pillar.init_remotes(ext_pillar[pillar_type],
git_pillar.PER_REMOTE_OVERRIDES)
for lock_type in ('update', 'checkout'):
for remote in pillar.remotes:
remote.clear_lock(lock_type=lock_type)
def clean_expired_tokens(opts):
'''
Clean expired tokens from the master

View File

@ -17,7 +17,7 @@ from salt.utils.process import SignalHandlingMultiprocessingProcess
log = logging.getLogger(__name__)
def start_engines(opts, proc_mgr):
def start_engines(opts, proc_mgr, proxy=None):
'''
Fire up the configured engines!
'''
@ -27,7 +27,7 @@ def start_engines(opts, proc_mgr):
runners = []
utils = salt.loader.utils(opts)
funcs = salt.loader.minion_mods(opts, utils=utils)
engines = salt.loader.engines(opts, funcs, runners)
engines = salt.loader.engines(opts, funcs, runners, proxy=proxy)
engines_opt = opts.get('engines', [])
if isinstance(engines_opt, dict):
@ -58,7 +58,8 @@ def start_engines(opts, proc_mgr):
fun,
engine_opts,
funcs,
runners
runners,
proxy
),
name=name
)
@ -68,7 +69,7 @@ class Engine(SignalHandlingMultiprocessingProcess):
'''
Execute the given engine in a new process
'''
def __init__(self, opts, fun, config, funcs, runners, log_queue=None):
def __init__(self, opts, fun, config, funcs, runners, proxy, log_queue=None):
'''
Set up the process executor
'''
@ -78,6 +79,7 @@ class Engine(SignalHandlingMultiprocessingProcess):
self.fun = fun
self.funcs = funcs
self.runners = runners
self.proxy = proxy
# __setstate__ and __getstate__ are only used on Windows.
# We do this so that __init__ will be invoked on Windows in the child
@ -90,6 +92,7 @@ class Engine(SignalHandlingMultiprocessingProcess):
state['config'],
state['funcs'],
state['runners'],
state['proxy'],
log_queue=state['log_queue']
)
@ -99,6 +102,7 @@ class Engine(SignalHandlingMultiprocessingProcess):
'config': self.config,
'funcs': self.funcs,
'runners': self.runners,
'proxy': self.proxy,
'log_queue': self.log_queue}
def run(self):
@ -116,7 +120,8 @@ class Engine(SignalHandlingMultiprocessingProcess):
self.engine = salt.loader.engines(self.opts,
self.funcs,
self.runners)
self.runners,
proxy=self.proxy)
kwargs = self.config or {}
try:
self.engine[self.fun](**kwargs)

View File

@ -61,6 +61,7 @@ if salt.utils.is_windows():
try:
import wmi # pylint: disable=import-error
import salt.utils.winapi
import win32api
HAS_WMI = True
except ImportError:
log.exception(
@ -409,11 +410,8 @@ def _memdata(osdata):
if comps[0].strip() == 'Memory' and comps[1].strip() == 'size:':
grains['mem_total'] = int(comps[2].strip())
elif osdata['kernel'] == 'Windows' and HAS_WMI:
with salt.utils.winapi.Com():
wmi_c = wmi.WMI()
# this is a list of each stick of ram in a system
# WMI returns it as the string value of the number of bytes
tot_bytes = sum([int(x.Capacity) for x in wmi_c.Win32_PhysicalMemory()], 0)
# get the Total Physical memory as reported by msinfo32
tot_bytes = win32api.GlobalMemoryStatusEx()['TotalPhys']
# return memory info in gigabytes
grains['mem_total'] = int(tot_bytes / (1024 ** 2))
return grains

View File

@ -272,12 +272,13 @@ def raw_mod(opts, name, functions, mod='modules'):
return dict(loader._dict) # return a copy of *just* the funcs for `name`
def engines(opts, functions, runners):
def engines(opts, functions, runners, proxy=None):
'''
Return the master services plugins
'''
pack = {'__salt__': functions,
'__runners__': runners}
'__runners__': runners,
'__proxy__': proxy}
return LazyLoader(
_module_dirs(opts, 'engines', 'engines'),
opts,

View File

@ -225,6 +225,10 @@ class Maintenance(SignalHandlingMultiprocessingProcess):
last = int(time.time())
# Clean out the fileserver backend cache
salt.daemons.masterapi.clean_fsbackend(self.opts)
# Clear any locks set for the active fileserver backends
salt.daemons.masterapi.clear_fsbackend_locks(self.opts)
# Clear any locks set for git_pillar
salt.daemons.masterapi.clear_git_pillar_locks(self.opts)
# Clean out pub auth
salt.daemons.masterapi.clean_pub_auth(self.opts)

View File

@ -855,7 +855,11 @@ class Minion(MinionBase):
log.info('Creating minion process manager')
self.process_manager = ProcessManager(name='MinionProcessManager')
self.io_loop.spawn_callback(self.process_manager.run, async=True)
self.io_loop.spawn_callback(salt.engines.start_engines, self.opts, self.process_manager)
# We don't have the proxy setup yet, so we can't start engines
# Engines need to be able to access __proxy__
if not salt.utils.is_proxy():
self.io_loop.spawn_callback(salt.engines.start_engines, self.opts,
self.process_manager)
# Install the SIGINT/SIGTERM handlers if not done so far
if signal.getsignal(signal.SIGINT) is signal.SIG_DFL:
@ -2964,7 +2968,7 @@ class ProxyMinion(Minion):
# Then load the proxy module
self.proxy = salt.loader.proxy(self.opts)
# Check config 'add_proxymodule_to_opts' Remove this in Boron.
# Check config 'add_proxymodule_to_opts' Remove this in Carbon.
if self.opts['add_proxymodule_to_opts']:
self.opts['proxymodule'] = self.proxy
@ -2975,6 +2979,12 @@ class ProxyMinion(Minion):
self.proxy.pack['__ret__'] = self.returners
self.proxy.pack['__pillar__'] = self.opts['pillar']
# Start engines here instead of in the Minion superclass __init__
# This is because we need to inject the __proxy__ variable but
# it is not setup until now.
self.io_loop.spawn_callback(salt.engines.start_engines, self.opts,
self.process_manager, proxy=self.proxy)
if ('{0}.init'.format(fq_proxyname) not in self.proxy
or '{0}.shutdown'.format(fq_proxyname) not in self.proxy):
log.error('Proxymodule {0} is missing an init() or a shutdown() or both.'.format(fq_proxyname))

View File

@ -680,7 +680,7 @@ def _change_state(name, action, expected, *args, **kwargs):
'state': {'old': expected, 'new': expected},
'comment': ('Container \'{0}\' already {1}'
.format(name, expected))}
response = _client_wrapper(action, name, *args, **kwargs)
_client_wrapper(action, name, *args, **kwargs)
_clear_context()
try:
post = state(name)
@ -689,8 +689,6 @@ def _change_state(name, action, expected, *args, **kwargs):
post = None
ret = {'result': post == expected,
'state': {'old': pre, 'new': post}}
if action == 'wait':
ret['exit_status'] = response
return ret
@ -4856,7 +4854,7 @@ def unpause(name):
unfreeze = salt.utils.alias_function(unpause, 'unfreeze')
def wait(name):
def wait(name, ignore_already_stopped=False, fail_on_exit_status=False):
'''
Wait for the container to exit gracefully, and return its exit code
@ -4867,6 +4865,13 @@ def wait(name):
name
Container name or ID
ignore_already_stopped
Boolean flag that prevent execution to fail, if a container
is already stopped.
fail_on_exit_status
Boolean flag to report execution as failure if ``exit_status``
is different than 0.
**RETURN DATA**
@ -4885,7 +4890,36 @@ def wait(name):
salt myminion dockerng.wait mycontainer
'''
return _change_state(name, 'wait', 'stopped')
try:
pre = state(name)
except CommandExecutionError:
# Container doesn't exist anymore
return {'result': ignore_already_stopped,
'comment': 'Container \'{0}\' absent'.format(name)}
already_stopped = pre == 'stopped'
response = _client_wrapper('wait', name)
_clear_context()
try:
post = state(name)
except CommandExecutionError:
# Container doesn't exist anymore
post = None
if already_stopped:
success = ignore_already_stopped
elif post == 'stopped':
success = True
else:
success = False
result = {'result': success,
'state': {'old': pre, 'new': post},
'exit_status': response}
if already_stopped:
result['comment'] = 'Container \'{0}\' already stopped'.format(name)
if fail_on_exit_status and result['result']:
result['result'] = result['exit_status'] == 0
return result
# Functions to run commands inside containers

View File

@ -408,6 +408,34 @@ def sync_proxymodules(saltenv=None, refresh=False):
return ret
def sync_engines(saltenv=None, refresh=False):
'''
.. versionadded:: 2016.3.0
Sync engine modules from ``salt://_engines`` to the minion
saltenv : base
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
refresh : True
If ``True``, refresh the available execution modules on the minion.
This refresh will be performed even if no new engine modules are synced.
Set to ``False`` to prevent this refresh.
CLI Examples:
.. code-block:: bash
salt '*' saltutil.sync_engines
salt '*' saltutil.sync_engines saltenv=base,dev
'''
ret = _sync('engines', saltenv)
if refresh:
refresh_modules()
return ret
def sync_output(saltenv=None, refresh=True):
'''
Sync outputters from ``salt://_output`` to the minion
@ -540,6 +568,7 @@ def sync_all(saltenv=None, refresh=True):
ret['utils'] = sync_utils(saltenv, False)
ret['log_handlers'] = sync_log_handlers(saltenv, False)
ret['proxymodules'] = sync_proxymodules(saltenv, False)
ret['engines'] = sync_engines(saltenv, False)
if refresh:
refresh_modules()
refresh_pillar()

View File

@ -1434,12 +1434,19 @@ def list_products(all=False, refresh=False):
call = __salt__['cmd.run_all'](cmd, output_loglevel='trace')
doc = dom.parseString(_zypper_check_result(call, xml=True))
for prd in doc.getElementsByTagName('product-list')[0].getElementsByTagName('product'):
product_list = doc.getElementsByTagName('product-list')
if not product_list:
return ret # No products found
for prd in product_list[0].getElementsByTagName('product'):
p_nfo = dict()
for k_p_nfo, v_p_nfo in prd.attributes.items():
p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo in ['true', '1']
p_nfo['eol'] = prd.getElementsByTagName('endoflife')[0].getAttribute('text')
p_nfo['eol_t'] = int(prd.getElementsByTagName('endoflife')[0].getAttribute('time_t'))
eol = prd.getElementsByTagName('endoflife')
if eol:
p_nfo['eol'] = eol[0].getAttribute('text')
p_nfo['eol_t'] = int(eol[0].getAttribute('time_t') or 0)
p_nfo['description'] = " ".join(
[line.strip() for line in _get_first_aggregate_text(
prd.getElementsByTagName('description')

View File

@ -299,6 +299,8 @@ def salt_key():
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
except Exception as err:
sys.stderr.write("Error: {0}\n".format(err.message))
def salt_cp():

View File

@ -145,16 +145,18 @@ def _get_comparison_spec(pkgver):
return oper, verstr
def _fulfills_version_spec(versions, oper, desired_version):
def _fulfills_version_spec(versions, oper, desired_version,
ignore_epoch=False):
'''
Returns True if any of the installed versions match the specified version,
otherwise returns False
'''
normalize = lambda x: x.split(':', 1)[-1] if ignore_epoch else x
cmp_func = __salt__.get('pkg.version_cmp')
for ver in versions:
if salt.utils.compare_versions(ver1=ver,
if salt.utils.compare_versions(ver1=normalize(ver),
oper=oper,
ver2=desired_version,
ver2=normalize(desired_version),
cmp_func=cmp_func):
return True
return False
@ -177,6 +179,7 @@ def _find_remove_targets(name=None,
version=None,
pkgs=None,
normalize=True,
ignore_epoch=False,
**kwargs):
'''
Inspect the arguments to pkg.removed and discover what packages need to
@ -227,7 +230,8 @@ def _find_remove_targets(name=None,
except CommandExecutionError as exc:
problems.append(exc.strerror)
continue
if not _fulfills_version_spec(cver, oper, verstr):
if not _fulfills_version_spec(cver, oper, verstr,
ignore_epoch=ignore_epoch):
log.debug(
'Current version ({0}) did not match desired version '
'specification ({1}), will not remove'
@ -263,6 +267,7 @@ def _find_install_targets(name=None,
skip_suggestions=False,
pkg_verify=False,
normalize=True,
ignore_epoch=False,
reinstall=False,
**kwargs):
'''
@ -471,7 +476,8 @@ def _find_install_targets(name=None,
if not sources and 'allow_updates' in kwargs:
if kwargs['allow_updates']:
oper = '>='
if _fulfills_version_spec(cver, oper, verstr):
if not _fulfills_version_spec(cver, oper, verstr,
ignore_epoch=ignore_epoch):
if reinstall:
to_reinstall[key] = val
elif pkg_verify and oper == '==':
@ -510,7 +516,7 @@ def _find_install_targets(name=None,
return desired, targets, to_unpurge, to_reinstall, altered_files, warnings
def _verify_install(desired, new_pkgs):
def _verify_install(desired, new_pkgs, ignore_epoch=False):
'''
Determine whether or not the installed packages match what was requested in
the SLS file.
@ -541,7 +547,8 @@ def _verify_install(desired, new_pkgs):
ok.append(pkgname)
continue
oper, verstr = _get_comparison_spec(pkgver)
if _fulfills_version_spec(cver, oper, verstr):
if _fulfills_version_spec(cver, oper, verstr,
ignore_epoch=ignore_epoch):
ok.append(pkgname)
else:
failed.append(pkgname)
@ -602,6 +609,7 @@ def installed(
allow_updates=False,
pkg_verify=False,
normalize=True,
ignore_epoch=False,
reinstall=False,
**kwargs):
'''
@ -630,9 +638,29 @@ def installed(
.. code-block:: bash
# salt myminion pkg.latest_version httpd
# salt myminion pkg.latest_version vim-enhanced
myminion:
2.2.15-30.el6.centos
2:7.4.160-1.el7
.. important::
As of version 2015.8.7, for distros which use yum/dnf, packages
which have a version with a nonzero epoch (that is, versions which
start with a number followed by a colon like in the
``pkg.latest_version`` output above) must have the epoch included
when specifying the version number. For example:
.. code-block:: yaml
vim-enhanced:
pkg.installed:
- version: 2:7.4.160-1.el7
In version 2015.8.9, an **ignore_epoch** argument has been added to
:py:mod:`pkg.installed <salt.states.pkg.installed>`,
:py:mod:`pkg.removed <salt.states.pkg.installed>`, and
:py:mod:`pkg.purged <salt.states.pkg.installed>` states, which
causes the epoch to be disregarded when the state checks to see if
the desired version was installed.
Also, while this function is not yet implemented for all pkg frontends,
:mod:`pkg.list_repo_pkgs <salt.modules.yumpkg.list_repo_pkgs>` will
@ -736,6 +764,111 @@ def installed(
.. versionadded:: 2014.1.1
:param bool allow_updates:
Allow the package to be updated outside Salt's control (e.g. auto
updates on Windows). This means a package on the Minion can have a
newer version than the latest available in the repository without
enforcing a re-installation of the package.
.. versionadded:: 2014.7.0
Example:
.. code-block:: yaml
httpd:
pkg.installed:
- fromrepo: mycustomrepo
- skip_verify: True
- skip_suggestions: True
- version: 2.0.6~ubuntu3
- refresh: True
- allow_updates: True
- hold: False
:param bool pkg_verify:
.. versionadded:: 2014.7.0
For requested packages that are already installed and would not be
targeted for upgrade or downgrade, use pkg.verify to determine if any
of the files installed by the package have been altered. If files have
been altered, the reinstall option of pkg.install is used to force a
reinstall. Types to ignore can be passed to pkg.verify (see example
below). Currently, this option is supported for the following pkg
providers: :mod:`yumpkg <salt.modules.yumpkg>`.
Examples:
.. code-block:: yaml
httpd:
pkg.installed:
- version: 2.2.15-30.el6.centos
- pkg_verify: True
.. code-block:: yaml
mypkgs:
pkg.installed:
- pkgs:
- foo
- bar: 1.2.3-4
- baz
- pkg_verify:
- ignore_types: [config,doc]
:param bool normalize:
Normalize the package name by removing the architecture, if the
architecture of the package is different from the architecture of the
operating system. The ability to disable this behavior is useful for
poorly-created packages which include the architecture as an actual
part of the name, such as kernel modules which match a specific kernel
version.
.. versionadded:: 2014.7.0
Example:
.. code-block:: yaml
gpfs.gplbin-2.6.32-279.31.1.el6.x86_64:
pkg.installed:
- normalize: False
:param bool ignore_epoch:
When a package version contains an non-zero epoch (e.g.
``1:3.14.159-2.el7``, and a specific version of a package is desired,
set this option to ``True`` to ignore the epoch when comparing
versions. This allows for the following SLS to be used:
.. code-block:: yaml
# Actual vim-enhanced version: 2:7.4.160-1.el7
vim-enhanced:
pkg.installed:
- version: 7.4.160-1.el7
- ignore_epoch: True
Without this option set to ``True`` in the above example, the package
would be installed, but the state would report as failed because the
actual installed version would be ``2:7.4.160-1.el7``. Alternatively,
this option can be left as ``False`` and the full version string (with
epoch) can be specified in the SLS file:
.. code-block:: yaml
vim-enhanced:
pkg.installed:
- version: 2:7.4.160-1.el7
.. versionadded:: 2015.8.9
|
**MULTIPLE PACKAGE INSTALLATION OPTIONS: (not supported in Windows or
pkgng)**
:param list pkgs:
A list of packages to install from a software repository. All packages
listed under ``pkgs`` will be installed via a single command.
@ -802,9 +935,6 @@ def installed(
- bar: '~>=1.2:slot::overlay[use,-otheruse]'
- baz
**Multiple Package Installation Options: (not supported in Windows or
pkgng)**
:param list sources:
A list of packages to install, along with the source URI or local path
from which to install each package. In the example below, ``foo``,
@ -822,105 +952,8 @@ def installed(
- baz: ftp://someothersite.org/baz.rpm
- qux: /minion/path/to/qux.rpm
:param bool allow_updates:
Allow the package to be updated outside Salt's control (e.g. auto
updates on Windows). This means a package on the Minion can have a
newer version than the latest available in the repository without
enforcing a re-installation of the package.
**PLATFORM-SPECIFIC ARGUMENTS**
.. versionadded:: 2014.7.0
Example:
.. code-block:: yaml
httpd:
pkg.installed:
- fromrepo: mycustomrepo
- skip_verify: True
- skip_suggestions: True
- version: 2.0.6~ubuntu3
- refresh: True
- allow_updates: True
- hold: False
:param bool pkg_verify:
For requested packages that are already installed and would not be
targeted for upgrade or downgrade, use ``pkg.verify`` to determine if
any of the files installed by the package have been altered. If files
have been altered, the reinstall option of ``pkg.install`` is used to
force a reinstall. Types to ignore can be passed to ``pkg.verify`` (see
example below). Currently, this option is supported for the following
pkg providers: :mod:`yumpkg <salt.modules.yumpkg>`.
.. versionadded:: 2014.7.0
.. note::
If ``reinstall`` is set to ``True``, then ``pkg.verify`` will not
be run and any targeted package which is installed and would not be
targeted for upgrade/downgrade will be reinstalled.
Examples:
.. code-block:: yaml
httpd:
pkg.installed:
- version: 2.2.15-30.el6.centos
- pkg_verify: True
.. code-block:: yaml
mypkgs:
pkg.installed:
- pkgs:
- foo
- bar: 1.2.3-4
- baz
- pkg_verify:
- ignore_types: [config,doc]
:param bool normalize:
Normalize the package name by removing the architecture, if the
architecture of the package is different from the architecture of the
operating system. The ability to disable this behavior is useful for
poorly-created packages which include the architecture as an actual
part of the name, such as kernel modules which match a specific kernel
version.
.. versionadded:: 2014.7.0
Example:
.. code-block:: yaml
gpfs.gplbin-2.6.32-279.31.1.el6.x86_64:
pkg.installed:
- normalize: False
:param bool reinstall:
If any of the specified packages are already installed, and this option
is set to ``True``, then these packages will (where supported) be
reinstalled. This is supported in both :mod:`apt <salt.modules.aptpkg>`
and :mod:`yumpkg <salt.modules.yumpkg>`.
.. versionadded:: 2016.3.0
Example:
.. code-block:: yaml
zsh:
pkg.installed:
- reinstall: True
.. note::
Setting and leaving this option as ``True`` will result in
reinstallation every time the state is run, which may not be
desired.
:param kwargs:
These are specific to each OS. If it does not apply to the execution
module for your OS, it is ignored.
@ -1008,6 +1041,7 @@ def installed(
skip_suggestions=skip_suggestions,
pkg_verify=pkg_verify,
normalize=normalize,
ignore_epoch=ignore_epoch,
reinstall=reinstall,
**kwargs)
@ -1237,12 +1271,9 @@ def installed(
else:
if __grains__['os'] == 'FreeBSD':
kwargs['with_origin'] = True
ok, failed = \
_verify_install(
desired, __salt__['pkg.list_pkgs'](
versions_as_list=True, **kwargs
)
)
new_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
ok, failed = _verify_install(desired, new_pkgs,
ignore_epoch=ignore_epoch)
modified = [x for x in ok if x in targets]
not_modified = [x for x in ok
if x not in targets
@ -1693,6 +1724,7 @@ def _uninstall(
version=None,
pkgs=None,
normalize=True,
ignore_epoch=False,
**kwargs):
'''
Common function for package removal
@ -1715,7 +1747,8 @@ def _uninstall(
'result': False,
'comment': 'An error was encountered while parsing targets: '
'{0}'.format(exc)}
targets = _find_remove_targets(name, version, pkgs, normalize, **kwargs)
targets = _find_remove_targets(name, version, pkgs, normalize,
ignore_epoch=ignore_epoch, **kwargs)
if isinstance(targets, dict) and 'result' in targets:
return targets
elif not isinstance(targets, list):
@ -1779,7 +1812,12 @@ def _uninstall(
'comment': ' '.join(comments)}
def removed(name, version=None, pkgs=None, normalize=True, **kwargs):
def removed(name,
version=None,
pkgs=None,
normalize=True,
ignore_epoch=False,
**kwargs):
'''
Verify that a package is not installed, calling ``pkg.remove`` if necessary
to remove the package.
@ -1791,6 +1829,30 @@ def removed(name, version=None, pkgs=None, normalize=True, **kwargs):
The version of the package that should be removed. Don't do anything if
the package is installed with an unmatching version.
.. important::
As of version 2015.8.7, for distros which use yum/dnf, packages
which have a version with a nonzero epoch (that is, versions which
start with a number followed by a colon like in the example above)
must have the epoch included when specifying the version number.
For example:
.. code-block:: yaml
vim-enhanced:
pkg.installed:
- version: 2:7.4.160-1.el7
In version 2015.8.9, an **ignore_epoch** argument has been added to
:py:mod:`pkg.installed <salt.states.pkg.installed>`,
:py:mod:`pkg.removed <salt.states.pkg.installed>`, and
:py:mod:`pkg.purged <salt.states.pkg.installed>` states, which
causes the epoch to be disregarded when the state checks to see if
the desired version was installed. If **ignore_epoch** was not set
to ``True``, and instead of ``2:7.4.160-1.el7`` a version of
``7.4.160-1.el7`` were used, this state would report success since
the actual installed version includes the epoch, and the specified
version would not match.
normalize : True
Normalize the package name by removing the architecture, if the
architecture of the package is different from the architecture of the
@ -1801,6 +1863,34 @@ def removed(name, version=None, pkgs=None, normalize=True, **kwargs):
.. versionadded:: 2015.8.0
ignore_epoch : False
When a package version contains an non-zero epoch (e.g.
``1:3.14.159-2.el7``, and a specific version of a package is desired,
set this option to ``True`` to ignore the epoch when comparing
versions. This allows for the following SLS to be used:
.. code-block:: yaml
# Actual vim-enhanced version: 2:7.4.160-1.el7
vim-enhanced:
pkg.removed:
- version: 7.4.160-1.el7
- ignore_epoch: True
Without this option set to ``True`` in the above example, the state
would falsely report success since the actual installed version is
``2:7.4.160-1.el7``. Alternatively, this option can be left as
``False`` and the full version string (with epoch) can be specified in
the SLS file:
.. code-block:: yaml
vim-enhanced:
pkg.removed:
- version: 2:7.4.160-1.el7
.. versionadded:: 2015.8.9
Multiple Package Options:
pkgs
@ -1812,7 +1902,8 @@ def removed(name, version=None, pkgs=None, normalize=True, **kwargs):
'''
try:
return _uninstall(action='remove', name=name, version=version,
pkgs=pkgs, normalize=normalize, **kwargs)
pkgs=pkgs, normalize=normalize,
ignore_epoch=ignore_epoch, **kwargs)
except CommandExecutionError as exc:
ret = {'name': name, 'result': False}
if exc.info:
@ -1826,7 +1917,12 @@ def removed(name, version=None, pkgs=None, normalize=True, **kwargs):
return ret
def purged(name, version=None, pkgs=None, normalize=True, **kwargs):
def purged(name,
version=None,
pkgs=None,
normalize=True,
ignore_epoch=False,
**kwargs):
'''
Verify that a package is not installed, calling ``pkg.purge`` if necessary
to purge the package. All configuration files are also removed.
@ -1838,6 +1934,30 @@ def purged(name, version=None, pkgs=None, normalize=True, **kwargs):
The version of the package that should be removed. Don't do anything if
the package is installed with an unmatching version.
.. important::
As of version 2015.8.7, for distros which use yum/dnf, packages
which have a version with a nonzero epoch (that is, versions which
start with a number followed by a colon like in the example above)
must have the epoch included when specifying the version number.
For example:
.. code-block:: yaml
vim-enhanced:
pkg.installed:
- version: 2:7.4.160-1.el7
In version 2015.8.9, an **ignore_epoch** argument has been added to
:py:mod:`pkg.installed <salt.states.pkg.installed>`,
:py:mod:`pkg.removed <salt.states.pkg.installed>`, and
:py:mod:`pkg.purged <salt.states.pkg.installed>` states, which
causes the epoch to be disregarded when the state checks to see if
the desired version was installed. If **ignore_epoch** was not set
to ``True``, and instead of ``2:7.4.160-1.el7`` a version of
``7.4.160-1.el7`` were used, this state would report success since
the actual installed version includes the epoch, and the specified
version would not match.
normalize : True
Normalize the package name by removing the architecture, if the
architecture of the package is different from the architecture of the
@ -1848,6 +1968,34 @@ def purged(name, version=None, pkgs=None, normalize=True, **kwargs):
.. versionadded:: 2015.8.0
ignore_epoch : False
When a package version contains an non-zero epoch (e.g.
``1:3.14.159-2.el7``, and a specific version of a package is desired,
set this option to ``True`` to ignore the epoch when comparing
versions. This allows for the following SLS to be used:
.. code-block:: yaml
# Actual vim-enhanced version: 2:7.4.160-1.el7
vim-enhanced:
pkg.purged:
- version: 7.4.160-1.el7
- ignore_epoch: True
Without this option set to ``True`` in the above example, the state
would falsely report success since the actual installed version is
``2:7.4.160-1.el7``. Alternatively, this option can be left as
``False`` and the full version string (with epoch) can be specified in
the SLS file:
.. code-block:: yaml
vim-enhanced:
pkg.purged:
- version: 2:7.4.160-1.el7
.. versionadded:: 2015.8.9
Multiple Package Options:
pkgs
@ -1859,7 +2007,8 @@ def purged(name, version=None, pkgs=None, normalize=True, **kwargs):
'''
try:
return _uninstall(action='purge', name=name, version=version,
pkgs=pkgs, normalize=normalize, **kwargs)
pkgs=pkgs, normalize=normalize,
ignore_epoch=ignore_epoch, **kwargs)
except CommandExecutionError as exc:
ret = {'name': name, 'result': False}
if exc.info:

View File

@ -650,7 +650,8 @@ def versions_report(include_salt_cloud=False):
info = []
for ver_type in ('Salt Version', 'Dependency Versions', 'System Versions'):
info.append('{0}:'.format(ver_type))
for name in sorted(ver_info[ver_type]):
# List dependencies in alphabetical, case insensitive order
for name in sorted(ver_info[ver_type], cmp=lambda x, y: cmp(x.lower(), y.lower())):
ver = fmt.format(name,
ver_info[ver_type][name] or 'Not Installed',
pad=padding)

View File

@ -86,6 +86,7 @@ class CryptTestCase(TestCase):
@patch('os.umask', MagicMock())
@patch('os.chmod', MagicMock())
@patch('os.chown', MagicMock())
@patch('os.access', MagicMock(return_value=True))
def test_gen_keys(self):
with patch('salt.utils.fopen', mock_open()):
open_priv_wb = call('/keydir/keyname.pem', 'wb+')

View File

@ -21,7 +21,7 @@ ensure_in_syspath('../../')
# Import Salt Libs
from salt.modules import dockerng as dockerng_mod
from salt.exceptions import SaltInvocationError
from salt.exceptions import CommandExecutionError, SaltInvocationError
dockerng_mod.__context__ = {'docker.docker_version': ''}
dockerng_mod.__salt__ = {}
@ -509,6 +509,116 @@ class DockerngTestCase(TestCase):
dockerng_mod.inspect_volume('foo')
client.inspect_volume.assert_called_once_with('foo')
def test_wait_success(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
dockerng_inspect_container = Mock(side_effect=[
{'State': {'Running': True}},
{'State': {'Stopped': True}}])
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo')
self.assertEqual(result, {'result': True,
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'running'}})
def test_wait_fails_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
dockerng_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}},
])
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo')
self.assertEqual(result, {'result': False,
'comment': "Container 'foo' already stopped",
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'stopped'}})
def test_wait_success_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
dockerng_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}},
])
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo', ignore_already_stopped=True)
self.assertEqual(result, {'result': True,
'comment': "Container 'foo' already stopped",
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'stopped'}})
def test_wait_success_absent_container(self):
client = Mock()
client.api_version = '1.21'
dockerng_inspect_container = Mock(side_effect=CommandExecutionError)
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo', ignore_already_stopped=True)
self.assertEqual(result, {'result': True,
'comment': "Container 'foo' absent"})
def test_wait_fails_on_exit_status(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=1)
dockerng_inspect_container = Mock(side_effect=[
{'State': {'Running': True}},
{'State': {'Stopped': True}}])
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo', fail_on_exit_status=True)
self.assertEqual(result, {'result': False,
'exit_status': 1,
'state': {'new': 'stopped',
'old': 'running'}})
def test_wait_fails_on_exit_status_and_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=1)
dockerng_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}}])
with patch.object(dockerng_mod, 'inspect_container',
dockerng_inspect_container):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod._clear_context()
result = dockerng_mod.wait('foo',
ignore_already_stopped=True,
fail_on_exit_status=True)
self.assertEqual(result, {'result': False,
'comment': "Container 'foo' already stopped",
'exit_status': 1,
'state': {'new': 'stopped',
'old': 'stopped'}})
if __name__ == '__main__':
from integration import run_tests

View File

@ -31,7 +31,17 @@
offers common management tools and technology
certifications across the platform, and
each product is enterprise-class.</description></product>
<product name="SUSE_SLES" version="11.3" release="1.201" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Linux Enterprise Server 11 SP3 No EOL" shortname="" flavor="" isbase="0" repo="nu_novell_com:SLES11-SP3-Updates" installed="0">0x7ffdb538e948<description>SUSE Linux Enterprise offers a comprehensive
suite of products built on a single code base.
The platform addresses business needs from
the smallest thin-client devices to the worlds
most powerful high-performance computing
and mainframe servers. SUSE Linux Enterprise
offers common management tools and technology
certifications across the platform, and
each product is enterprise-class.</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="0" repo="nu_novell_com:SUSE-Manager-Server-2.1-Pool" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="manager" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="1" repo="@System" installed="1"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
<product name="SUSE-Manager-Server-Broken-EOL" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="manager" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="1" repo="@System" installed="1"><endoflife wrong="attribute"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
</product-list>
</stream>

View File

@ -24,6 +24,14 @@ provisioning.</description></product>
SUSE Manager Tools provide packages required to connect to a
SUSE Manager Server.
&lt;p&gt;</description></product>
<product name="sle-manager-tools-beta-no-eol" version="12" release="0" epoch="0" arch="x86_64" vendor="obs://build.suse.de/Devel:Galaxy:Manager:Head" summary="SUSE Manager Tools" repo="SUSE-Manager-Head" productline="" registerrelease="" shortname="Manager-Tools" flavor="POOL" isbase="false" installed="false"><registerflavor>extension</registerflavor><description>&lt;p&gt;
SUSE Manager Tools provide packages required to connect to a
SUSE Manager Server.
&lt;p&gt;</description></product>
<product name="sle-manager-tools-beta-broken-eol" version="12" release="0" epoch="0" arch="x86_64" vendor="obs://build.suse.de/Devel:Galaxy:Manager:Head" summary="SUSE Manager Tools" repo="SUSE-Manager-Head" productline="" registerrelease="" shortname="Manager-Tools" flavor="POOL" isbase="false" installed="false"><endoflife wrong="attribute"/><registerflavor>extension</registerflavor><description>&lt;p&gt;
SUSE Manager Tools provide packages required to connect to a
SUSE Manager Server.
&lt;p&gt;</description></product>
<product name="SLES" version="12.1" release="0" epoch="0" arch="x86_64" vendor="SUSE" summary="SUSE Linux Enterprise Server 12 SP1" repo="@System" productline="sles" registerrelease="" shortname="SLES12-SP1" flavor="DVD" isbase="true" installed="true"><endoflife time_t="1730332800" text="2024-10-31T01:00:00+01"/><registerflavor/><description>SUSE Linux Enterprise offers a comprehensive
suite of products built on a single code base.
The platform addresses business needs from

View File

@ -153,24 +153,26 @@ class ZypperTestCase(TestCase):
for filename, test_data in {
'zypper-products-sle12sp1.xml': {
'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy',
'SUSE-Manager-Server', 'sle-manager-tools-beta'],
'SUSE-Manager-Server', 'sle-manager-tools-beta',
'sle-manager-tools-beta-broken-eol', 'sle-manager-tools-beta-no-eol'],
'vendor': 'SUSE LLC <https://www.suse.com/>',
'release': ['0', '0', '0', '0', '0'],
'productline': [False, False, False, False, 'sles'],
'eol_t': [1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
'isbase': [False, False, False, False, True],
'installed': [False, False, False, False, True],
'release': ['0', '0', '0', '0', '0', '0', '0'],
'productline': [False, False, False, False, False, False, 'sles'],
'eol_t': [None, 0, 1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
'isbase': [False, False, False, False, False, False, True],
'installed': [False, False, False, False, False, False, True],
},
'zypper-products-sle11sp3.xml': {
'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server',
'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'],
'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', 'SUSE-Manager-Server-Broken-EOL',
'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'],
'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany',
'release': ['1.138', '1.2', '1.2', '1.201', '1.4'],
'productline': [False, False, False, False, 'manager'],
'eol_t': [0, 0, 0, 0, 0],
'isbase': [False, False, False, False, True],
'installed': [False, False, False, False, True],
'release': ['1.138', '1.2', '1.2', '1.2', '1.201', '1.201', '1.4'],
'productline': [False, False, False, False, False, 'manager', 'manager'],
'eol_t': [None, 0, 0, 0, 0, 0, 0],
'isbase': [False, False, False, False, False, True, True],
'installed': [False, False, False, False, False, True, True],
}}.items():
ref_out = {
'retcode': 0,
'stdout': get_test_data(filename)
@ -178,10 +180,10 @@ class ZypperTestCase(TestCase):
with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
products = zypper.list_products()
self.assertEqual(len(products), 5)
self.assertEqual(len(products), 7)
self.assertIn(test_data['vendor'], [product['vendor'] for product in products])
for kwd in ['name', 'isbase', 'installed', 'release', 'productline', 'eol_t']:
self.assertEqual(test_data[kwd], sorted([prod[kwd] for prod in products]))
self.assertEqual(test_data[kwd], sorted([prod.get(kwd) for prod in products]))
def test_refresh_db(self):
'''

View File

@ -871,6 +871,31 @@ class DockerngTestCase(TestCase):
'removed': 'removed'},
'result': True})
def test_volume_present_wo_existing_volumes(self):
'''
Test dockerng.volume_present without existing volumes.
'''
dockerng_create_volume = Mock(return_value='created')
dockerng_remove_volume = Mock(return_value='removed')
__salt__ = {'dockerng.create_volume': dockerng_create_volume,
'dockerng.remove_volume': dockerng_remove_volume,
'dockerng.volumes': Mock(return_value={'Volumes': None}),
}
with patch.dict(dockerng_state.__dict__,
{'__salt__': __salt__}):
ret = dockerng_state.volume_present(
'volume_foo',
driver='bar',
force=True,
)
dockerng_create_volume.assert_called_with('volume_foo',
driver='bar',
driver_opts=None)
self.assertEqual(ret, {'name': 'volume_foo',
'comment': '',
'changes': {'created': 'created'},
'result': True})
def test_volume_absent(self):
'''
Test dockerng.volume_absent