Merge pull request #46746 from rallytime/merge-2018.3

[2018.3] Merge forward from 2017.7 to 2018.3
This commit is contained in:
Nicole Thomas 2018-03-28 17:13:06 -04:00 committed by GitHub
commit e5b3c8fa91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 4243 additions and 554 deletions

View File

@ -31,7 +31,7 @@ provisioner:
salt_version: latest
salt_bootstrap_url: https://bootstrap.saltstack.com
salt_bootstrap_options: -X -p rsync stable <%= version %>
log_level: info
log_level: debug
sudo: true
require_chef: false
retry_on_exit_code:
@ -189,7 +189,6 @@ suites:
verifier:
name: runtests
sudo: true
verbose: true
run_destructive: true
transport: zeromq
types:

View File

@ -254,17 +254,19 @@ specifying the pillar variable is the same one used for :py:func:`pillar.get
<salt.states.file.managed>` state is only supported in Salt 2015.8.4 and
newer.
.. _faq-restart-salt-minion:
What is the best way to restart a Salt Minion daemon using Salt after upgrade?
------------------------------------------------------------------------------
Updating the ``salt-minion`` package requires a restart of the ``salt-minion``
service. But restarting the service while in the middle of a state run
interrupts the process of the Minion running states and sending results back to
the Master. A common way to workaround that is to schedule restarting of the
Minion service using :ref:`masterless mode <masterless-quickstart>` after all
other states have been applied. This allows the minion to keep Minion to Master
connection alive for the Minion to report the final results to the Master, while
the service is restarting in the background.
the Master. A common way to workaround that is to schedule restarting the
Minion service in the background by issuing a ``salt-call`` command calling
``service.restart`` function. This prevents the Minion being disconnected from
the Master immediately. Otherwise you would get
``Minion did not return. [Not connected]`` message as the result of a state run.
Upgrade without automatic restart
*********************************
@ -328,7 +330,7 @@ The following example works on UNIX-like operating systems:
{%- if grains['os'] != 'Windows' %}
Restart Salt Minion:
cmd.run:
- name: 'salt-call --local service.restart salt-minion'
- name: 'salt-call service.restart salt-minion'
- bg: True
- onchanges:
- pkg: Upgrade Salt Minion
@ -348,9 +350,9 @@ as follows:
Restart Salt Minion:
cmd.run:
{%- if grains['kernel'] == 'Windows' %}
- name: 'C:\salt\salt-call.bat --local service.restart salt-minion'
- name: 'C:\salt\salt-call.bat service.restart salt-minion'
{%- else %}
- name: 'salt-call --local service.restart salt-minion'
- name: 'salt-call service.restart salt-minion'
{%- endif %}
- bg: True
- onchanges:
@ -358,7 +360,13 @@ as follows:
However, it requires more advanced tricks to upgrade from legacy version of
Salt (before ``2016.3.0``) on UNIX-like operating systems, where executing
commands in the background is not supported:
commands in the background is not supported. You also may need to schedule
restarting the Minion service using :ref:`masterless mode
<masterless-quickstart>` after all other states have been applied for Salt
versions earlier than ``2016.11.0``. This allows the Minion to keep the
connection to the Master alive for being able to report the final results back
to the Master, while the service is restarting in the background. This state
should run last or watch for the ``pkg`` state changes:
.. code-block:: jinja
@ -382,8 +390,8 @@ Restart the Minion from the command line:
.. code-block:: bash
salt -G kernel:Windows cmd.run_bg 'C:\salt\salt-call.bat --local service.restart salt-minion'
salt -C 'not G@kernel:Windows' cmd.run_bg 'salt-call --local service.restart salt-minion'
salt -G kernel:Windows cmd.run_bg 'C:\salt\salt-call.bat service.restart salt-minion'
salt -C 'not G@kernel:Windows' cmd.run_bg 'salt-call service.restart salt-minion'
Salting the Salt Master
-----------------------
@ -409,6 +417,10 @@ for salt itself:
https://github.com/saltstack-formulas/salt-formula
Restarting the ``salt-master`` service using execution module or application of
state could be done the same way as for the Salt minion described :ref:`above
<faq-restart-salt-minion>`.
.. _faq-grain-security:
Is Targeting using Grain Data Secure?
@ -443,4 +455,3 @@ the grain and values that you want to change / set.)
You should also `file an issue <https://github.com/saltstack/salt/issues>`_
describing the change so it can be fixed in Salt.

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
.. _all-salt.clouds:
===============================
Full list of Salt Cloud modules
===============================
=============
cloud modules
=============
.. currentmodule:: salt.cloud.clouds

View File

@ -4480,6 +4480,25 @@ Recursively merge lists by aggregating them instead of replacing them.
pillar_merge_lists: False
.. conf_master:: pillar_includes_override_sls
``pillar_includes_override_sls``
********************************
.. versionadded:: 2017.7.6,2018.3.1
Default: ``False``
Prior to version 2017.7.3, keys from :ref:`pillar includes <pillar-include>`
would be merged on top of the pillar SLS. Since 2017.7.3, the includes are
merged together and then the pillar SLS is merged on top of that.
Set this option to ``True`` to return to the old behavior.
.. code-block:: yaml
pillar_includes_override_sls: True
.. _pillar-cache-opts:
Pillar Cache Options

View File

@ -49,17 +49,13 @@ the SaltStack Repository.
Installation from the Community-Maintained Repository
=====================================================
Beginning with version 0.9.4, Salt has been available in `EPEL`_. For
RHEL/CentOS 5, `Fedora COPR`_ is a single community repository that provides
Salt packages due to the removal from EPEL5.
Beginning with version 0.9.4, Salt has been available in `EPEL`_.
.. note::
Packages in these repositories are built by community, and it can
take a little while until the latest stable SaltStack release become
available.
Packages in this repository are built by community, and it can take a little
while until the latest stable SaltStack release become available.
.. _`EPEL`: http://fedoraproject.org/wiki/EPEL
.. _`Fedora COPR`: https://copr.fedorainfracloud.org/coprs/saltstack/salt-el5/
RHEL/CentOS 6 and 7, Scientific Linux, etc.
-------------------------------------------
@ -146,26 +142,13 @@ ZeroMQ 4
========
We recommend using ZeroMQ 4 where available. SaltStack provides ZeroMQ 4.0.5
and pyzmq 14.5.0 in the :ref:`SaltStack Repository <installation-rhel-repo>`
as well as a separate `zeromq4 COPR`_ repository.
.. _`zeromq4 COPR`: http://copr.fedorainfracloud.org/coprs/saltstack/zeromq4/
and ``pyzmq`` 14.5.0 in the :ref:`SaltStack Repository
<installation-rhel-repo>`.
If this repository is added *before* Salt is installed, then installing either
``salt-master`` or ``salt-minion`` will automatically pull in ZeroMQ 4.0.5, and
additional steps to upgrade ZeroMQ and pyzmq are unnecessary.
.. warning:: RHEL/CentOS 5 Users
Using COPR repos on RHEL/CentOS 5 requires that the ``python-hashlib``
package be installed. Not having it present will result in checksum errors
because YUM will not be able to process the SHA256 checksums used by COPR.
.. note::
For RHEL/CentOS 5 installations, if using the SaltStack repo or Fedora COPR
to install Salt (as described :ref:`above <installation-rhel-repo>`),
then it is not necessary to enable the `zeromq4 COPR`_, because those
repositories already include ZeroMQ 4.
Package Management
==================

View File

@ -285,6 +285,8 @@ Since both pillar SLS files contained a ``bind`` key which contained a nested
dictionary, the pillar dictionary's ``bind`` key contains the combined contents
of both SLS files' ``bind`` keys.
.. _pillar-include:
Including Other Pillars
=======================

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
===========================
Salt 2017.7.6 Release Notes
===========================
Version 2017.7.6 is a bugfix release for :ref:`2017.7.0 <release-2017-7-0>`.
Option to Return to Previous Pillar Include Behavior
----------------------------------------------------
Prior to version 2017.7.3, keys from :ref:`pillar includes <pillar-include>`
would be merged on top of the pillar SLS. Since 2017.7.3, the includes are
merged together and then the pillar SLS is merged on top of that.
The :conf_master:`pillar_includes_override_sls` option has been added allow
users to switch back to the pre-2017.7.3 behavior.

View File

@ -1056,7 +1056,7 @@ Function AddToPath
# Make sure the new length isn't over the NSIS_MAX_STRLEN
IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
DetailPrint "AddToPath Failed: new length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK \
"You may add C:\salt to the %PATH% for convenience when issuing local salt commands from the command line." \
/SD IDOK

View File

@ -57,7 +57,7 @@ from salt.ext import six
from salt.ext.six.moves import input # pylint: disable=import-error,redefined-builtin
try:
import saltwinshell
HAS_WINSHELL = False
HAS_WINSHELL = True
except ImportError:
HAS_WINSHELL = False
from salt.utils.zeromq import zmq
@ -560,6 +560,19 @@ class SSH(object):
self.targets[host][default] = self.defaults[default]
if 'host' not in self.targets[host]:
self.targets[host]['host'] = host
if self.targets[host].get('winrm') and not HAS_WINSHELL:
returned.add(host)
rets.add(host)
log_msg = 'Please contact sales@saltstack.com for access to the enterprise saltwinshell module.'
log.debug(log_msg)
no_ret = {'fun_args': [],
'jid': None,
'return': log_msg,
'retcode': 1,
'fun': '',
'id': host}
yield {host: no_ret}
continue
args = (
que,
self.opts,

View File

@ -736,6 +736,9 @@ VALID_OPTS = {
# Recursively merge lists by aggregating them instead of replacing them.
'pillar_merge_lists': bool,
# If True, values from included pillar SLS targets will override
'pillar_includes_override_sls': bool,
# How to merge multiple top files from multiple salt environments
# (saltenvs); can be 'merge' or 'same'
'top_file_merging_strategy': six.string_types,
@ -1231,6 +1234,9 @@ DEFAULT_MINION_OPTS = {
'pillarenv': None,
'pillarenv_from_saltenv': False,
'pillar_opts': False,
'pillar_source_merging_strategy': 'smart',
'pillar_merge_lists': False,
'pillar_includes_override_sls': False,
# ``pillar_cache``, ``pillar_cache_ttl`` and ``pillar_cache_backend``
# are not used on the minion but are unavoidably in the code path
'pillar_cache': False,
@ -1607,6 +1613,7 @@ DEFAULT_MASTER_OPTS = {
'pillar_safe_render_error': True,
'pillar_source_merging_strategy': 'smart',
'pillar_merge_lists': False,
'pillar_includes_override_sls': False,
'pillar_cache': False,
'pillar_cache_ttl': 3600,
'pillar_cache_backend': 'disk',

View File

@ -2396,7 +2396,30 @@ def get_server_id():
if salt.utils.platform.is_proxy():
return {}
return {'server_id': abs(hash(__opts__.get('id', '')) % (2 ** 31))}
id_ = __opts__.get('id', '')
id_hash = None
py_ver = sys.version_info[:2]
if py_ver >= (3, 3):
# Python 3.3 enabled hash randomization, so we need to shell out to get
# a reliable hash.
py_bin = 'python{0}.{1}'.format(*py_ver)
id_hash = __salt__['cmd.run'](
[py_bin, '-c', 'print(hash("{0}"))'.format(id_)],
env={'PYTHONHASHSEED': '0'}
)
try:
id_hash = int(id_hash)
except (TypeError, ValueError):
log.debug(
'Failed to hash the ID to get the server_id grain. Result of '
'hash command: %s', id_hash
)
id_hash = None
if id_hash is None:
# Python < 3.3 or error encountered above
id_hash = hash(id_)
return {'server_id': abs(id_hash % (2 ** 31))}
def get_master():

View File

@ -48,9 +48,11 @@ def _search(prefix="latest/"):
Recursively look up all grains in the metadata server
'''
ret = {}
linedata = http.query(os.path.join(HOST, prefix))
linedata = http.query(os.path.join(HOST, prefix), headers=True)
if 'body' not in linedata:
return ret
if linedata['headers'].get('Content-Type', 'text/plain') == 'application/octet-stream':
return linedata['body']
for line in linedata['body'].split('\n'):
if line.endswith('/'):
ret[line[:-1]] = _search(prefix=os.path.join(prefix, line))

View File

@ -2521,6 +2521,7 @@ class Minion(MinionBase):
self.opts,
self.functions,
self.returners,
utils=self.utils,
cleanup=[master_event(type='alive')])
try:

View File

@ -414,10 +414,16 @@ def _run(cmd,
)
try:
# Getting the environment for the runas user
# Use markers to thwart any stdout noise
# There must be a better way to do this.
import uuid
marker = '<<<' + str(uuid.uuid4()) + '>>>'
marker_b = marker.encode(__salt_system_encoding__)
py_code = (
'import sys, os, itertools; '
'sys.stdout.write(\"\\0\".join(itertools.chain(*os.environ.items())))'
'sys.stdout.write(\"' + marker + '\"); '
'sys.stdout.write(\"\\0\".join(itertools.chain(*os.environ.items()))); '
'sys.stdout.write(\"' + marker + '\");'
)
if __grains__['os'] in ['MacOS', 'Darwin']:
env_cmd = ('sudo', '-i', '-u', runas, '--',
@ -431,11 +437,34 @@ def _run(cmd,
env_cmd = ('su', '-', runas, '-c', sys.executable)
else:
env_cmd = ('su', '-s', shell, '-', runas, '-c', sys.executable)
env_bytes = salt.utils.stringutils.to_bytes(subprocess.Popen(
env_bytes, env_encoded_err = subprocess.Popen(
env_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
).communicate(salt.utils.stringutils.to_bytes(py_code))[0])
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE
).communicate(salt.utils.stringutils.to_bytes(py_code))[0]
marker_count = env_bytes.count(marker_b)
if marker_count == 0:
# Possibly PAM prevented the login
log.error(
'Environment could not be retrieved for user \'%s\': '
'stderr=%r stdout=%r',
runas, env_encoded_err, env_bytes
)
# Ensure that we get an empty env_runas dict below since we
# were not able to get the environment.
env_bytes = b''
elif marker_count != 2:
raise CommandExecutionError(
'Environment could not be retrieved for user \'{0}\'',
info={'stderr': repr(env_encoded_err),
'stdout': repr(env_bytes)}
)
else:
# Strip the marker
env_bytes = env_bytes.split(marker_b)[1]
if six.PY2:
import itertools
env_runas = dict(itertools.izip(*[iter(env_bytes.split(b'\0'))]*2))

View File

@ -426,6 +426,9 @@ def build(runas,
# use default /var/cache/pbuilder/result
results_dir = '/var/cache/pbuilder/result'
## ensure clean
__salt__['cmd.run']('rm -fR {0}'.format(results_dir))
# dscs should only contain salt orig and debian tarballs and dsc file
for dsc in dscs:
afile = os.path.basename(dsc)
@ -436,10 +439,10 @@ def build(runas,
try:
__salt__['cmd.run']('chown {0} -R {1}'.format(runas, dbase))
cmd = 'pbuilder --update --override-config'
cmd = 'pbuilder update --override-config'
__salt__['cmd.run'](cmd, runas=runas, python_shell=True)
cmd = 'pbuilder --build {0}'.format(dsc)
cmd = 'pbuilder build --debbuildopts "-sa" {0}'.format(dsc)
__salt__['cmd.run'](cmd, runas=runas, python_shell=True)
# ignore local deps generated package file

View File

@ -1790,8 +1790,10 @@ def grant_exists(grant,
if not target_tokens: # Avoid the overhead of re-calc in loop
target_tokens = _grant_to_tokens(target)
grant_tokens = _grant_to_tokens(grant)
grant_tokens_database = grant_tokens['database'].replace('"', '').replace('\\', '').replace('`', '')
target_tokens_database = target_tokens['database'].replace('"', '').replace('\\', '').replace('`', '')
if grant_tokens['user'] == target_tokens['user'] and \
grant_tokens['database'] == target_tokens['database'] and \
grant_tokens_database == target_tokens_database and \
grant_tokens['host'] == target_tokens['host'] and \
set(grant_tokens['grant']) >= set(target_tokens['grant']):
return True

View File

@ -159,7 +159,7 @@ def install(pkg=None,
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ' '.join(cmd)
result = __salt__['cmd.run_all'](cmd, python_shell=True, cwd=dir, runas=runas, env=env)
@ -238,7 +238,7 @@ def uninstall(pkg, dir=None, runas=None, env=None):
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ['npm', 'uninstall', '"{0}"'.format(pkg)]
if not dir:
@ -297,7 +297,7 @@ def list_(pkg=None, dir=None, runas=None, env=None, depth=None):
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ['npm', 'list', '--json', '--silent']
@ -360,7 +360,7 @@ def cache_clean(path=None, runas=None, env=None, force=False):
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ['npm', 'cache', 'clean']
if path:
@ -407,7 +407,7 @@ def cache_list(path=None, runas=None, env=None):
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ['npm', 'cache', 'ls']
if path:
@ -447,7 +447,7 @@ def cache_path(runas=None, env=None):
if runas:
uid = salt.utils.user.get_uid(runas)
if uid:
env.update({'SUDO_UID': b'{0}'.format(uid), 'SUDO_USER': b''})
env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = 'npm config get cache'

View File

@ -366,6 +366,28 @@ def item(*args, **kwargs):
.. versionadded:: 2015.8.0
pillarenv
If specified, this function will query the master to generate fresh
pillar data on the fly, specifically from the requested pillar
environment. Note that this can produce different pillar data than
executing this function without an environment, as its normal behavior
is just to return a value from minion's pillar data in memory (which
can be sourced from more than one pillar environment).
Using this argument will not affect the pillar data in memory. It will
however be slightly slower and use more resources on the master due to
the need for the master to generate and send the minion fresh pillar
data. This tradeoff in performance however allows for the use case
where pillar data is desired only from a single environment.
.. versionadded:: 2017.7.6,2018.3.1
saltenv
Included only for compatibility with
:conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored.
.. versionadded:: 2017.7.6,2018.3.1
CLI Examples:
.. code-block:: bash
@ -377,11 +399,17 @@ def item(*args, **kwargs):
ret = {}
default = kwargs.get('default', '')
delimiter = kwargs.get('delimiter', DEFAULT_TARGET_DELIM)
pillarenv = kwargs.get('pillarenv', None)
saltenv = kwargs.get('saltenv', None)
pillar_dict = __pillar__ \
if all(x is None for x in (saltenv, pillarenv)) \
else items(saltenv=saltenv, pillarenv=pillarenv)
try:
for arg in args:
ret[arg] = salt.utils.data.traverse_dict_and_list(
__pillar__,
pillar_dict,
arg,
default,
delimiter)

View File

@ -430,16 +430,27 @@ def add_package(package,
Install a package using DISM
Args:
package (str): The package to install. Can be a .cab file, a .msu file,
or a folder
ignore_check (Optional[bool]): Skip installation of the package if the
applicability checks fail
prevent_pending (Optional[bool]): Skip the installation of the package
if there are pending online actions
image (Optional[str]): The path to the root directory of an offline
Windows image. If `None` is passed, the running operating system is
targeted. Default is None.
restart (Optional[bool]): Reboot the machine if required by the install
package (str):
The package to install. Can be a .cab file, a .msu file, or a folder
.. note::
An `.msu` package is supported only when the target image is
offline, either mounted or applied.
ignore_check (Optional[bool]):
Skip installation of the package if the applicability checks fail
prevent_pending (Optional[bool]):
Skip the installation of the package if there are pending online
actions
image (Optional[str]):
The path to the root directory of an offline Windows image. If
``None`` is passed, the running operating system is targeted.
Default is None.
restart (Optional[bool]):
Reboot the machine if required by the install
Returns:
dict: A dictionary containing the results of the command

View File

@ -3440,7 +3440,7 @@ def _processValueItem(element, reg_key, reg_valuename, policy, parent_element,
this_element_value = b''.join([this_element_value.encode('utf-16-le'),
encoded_null])
elif etree.QName(element).localname == 'multiText':
this_vtype = 'REG_MULTI_SZ'
this_vtype = 'REG_MULTI_SZ' if not check_deleted else 'REG_SZ'
if this_element_value is not None:
this_element_value = '{0}{1}{1}'.format(chr(0).join(this_element_value), chr(0))
elif etree.QName(element).localname == 'list':

View File

@ -2792,20 +2792,18 @@ def mod_repo(repo, basedir=None, **kwargs):
filerepos[repo].update(repo_opts)
content = header
for stanza in six.iterkeys(filerepos):
comments = ''
if 'comments' in six.iterkeys(filerepos[stanza]):
comments = salt.utils.pkg.rpm.combine_comments(
filerepos[stanza]['comments'])
del filerepos[stanza]['comments']
content += '\n[{0}]'.format(stanza)
comments = salt.utils.pkg.rpm.combine_comments(
filerepos[stanza].pop('comments', [])
)
content += '[{0}]\n'.format(stanza)
for line in six.iterkeys(filerepos[stanza]):
content += '\n{0}={1}'.format(
content += '{0}={1}\n'.format(
line,
filerepos[stanza][line]
if not isinstance(filerepos[stanza][line], bool)
else _bool_to_str(filerepos[stanza][line])
)
content += '\n{0}\n'.format(comments)
content += comments + '\n'
with salt.utils.files.fopen(repofile, 'w') as fileout:
fileout.write(salt.utils.stringutils.to_str(content))
@ -2834,15 +2832,30 @@ def _parse_repo_file(filename):
section_dict.pop('__name__', None)
config[section] = section_dict
# Try to extract leading comments
# Try to extract header comments, as well as comments for each repo. Read
# from the beginning of the file and assume any leading comments are
# header comments. Continue to read each section header and then find the
# comments for each repo.
headers = ''
with salt.utils.files.fopen(filename, 'r') as rawfile:
for line in rawfile:
section = None
with salt.utils.files.fopen(filename, 'r') as repofile:
for line in repofile:
line = salt.utils.stringutils.to_unicode(line)
if line.strip().startswith('#'):
headers += '{0}\n'.format(line.strip())
else:
break
line = line.strip()
if line.startswith('#'):
if section is None:
headers += line + '\n'
else:
try:
comments = config[section].setdefault('comments', [])
comments.append(line[1:].lstrip())
except KeyError:
log.debug(
'Found comment in %s which does not appear to '
'belong to any repo section: %s', filename, line
)
elif line.startswith('[') and line.endswith(']'):
section = line[1:-1]
return (headers, salt.utils.data.decode(config))

View File

@ -726,6 +726,123 @@ def list_pkgs(versions_as_list=False, **kwargs):
attr)
def list_repo_pkgs(*args, **kwargs):
'''
.. versionadded:: 2017.7.5,2018.3.1
Returns all available packages. Optionally, package names (and name globs)
can be passed and the results will be filtered to packages matching those
names. This is recommended as it speeds up the function considerably.
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
The return data will be a dictionary mapping package names to a list of
version numbers, ordered from newest to oldest. If ``byrepo`` is set to
``True``, then the return dictionary will contain repository names at the
top level, and each repository will map packages to lists of version
numbers. For example:
.. code-block:: python
# With byrepo=False (default)
{
'bash': ['4.3-83.3.1',
'4.3-82.6'],
'vim': ['7.4.326-12.1']
}
{
'OSS': {
'bash': ['4.3-82.6'],
'vim': ['7.4.326-12.1']
},
'OSS Update': {
'bash': ['4.3-83.3.1']
}
}
fromrepo : None
Only include results from the specified repo(s). Multiple repos can be
specified, comma-separated.
byrepo : False
When ``True``, the return data for each package will be organized by
repository.
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_repo_pkgs
salt '*' pkg.list_repo_pkgs foo bar baz
salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
salt '*' pkg.list_repo_pkgs 'python2-*' fromrepo='OSS Updates'
'''
byrepo = kwargs.pop('byrepo', False)
fromrepo = kwargs.pop('fromrepo', '') or ''
ret = {}
targets = [
arg if isinstance(arg, six.string_types) else six.text_type(arg)
for arg in args
]
def _is_match(pkgname):
'''
When package names are passed to a zypper search, they will be matched
anywhere in the package name. This makes sure that only exact or
fnmatch matches are identified.
'''
if not args:
# No package names passed, everyone's a winner!
return True
for target in targets:
if fnmatch.fnmatch(pkgname, target):
return True
return False
for node in __zypper__.xml.call('se', '-s', *targets).getElementsByTagName('solvable'):
pkginfo = dict(node.attributes.items())
try:
if pkginfo['kind'] != 'package':
continue
reponame = pkginfo['repository']
if fromrepo and reponame != fromrepo:
continue
pkgname = pkginfo['name']
pkgversion = pkginfo['edition']
except KeyError:
continue
else:
if _is_match(pkgname):
repo_dict = ret.setdefault(reponame, {})
version_list = repo_dict.setdefault(pkgname, set())
version_list.add(pkgversion)
if byrepo:
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]],
reverse=True
)
ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
return ret
else:
byrepo_ret = {}
for reponame in ret:
for pkgname in ret[reponame]:
byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
for pkgname in byrepo_ret:
sorted_versions = sorted(
[LooseVersion(x) for x in byrepo_ret[pkgname]],
reverse=True
)
byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
return byrepo_ret
def _get_configured_repos():
'''
Get all the info about repositories from the configurations.
@ -1144,6 +1261,15 @@ def install(name=None,
return {}
version_num = Wildcard(__zypper__)(name, version)
if version_num:
if pkgs is None and sources is None:
# Allow "version" to work for single package target
pkg_params = {name: version_num}
else:
log.warning('"version" parameter will be ignored for multiple '
'package targets')
if pkg_type == 'repository':
targets = []
for param, version_num in six.iteritems(pkg_params):

View File

@ -784,11 +784,22 @@ class Pillar(object):
nstate = {
key_fragment: nstate
}
include_states.append(nstate)
if not self.opts.get('pillar_includes_override_sls', False):
include_states.append(nstate)
else:
state = merge(
state,
nstate,
self.merge_strategy,
self.opts.get('renderer', 'yaml'),
self.opts.get('pillar_merge_lists', False))
if err:
errors += err
if include_states:
# merge included state(s) with the current state merged last
if not self.opts.get('pillar_includes_override_sls', False):
# merge included state(s) with the current state
# merged last to ensure that its values are
# authoritative.
include_states.append(state)
state = None
for s in include_states:

View File

@ -441,7 +441,7 @@ def clean_old_jobs():
shutil.rmtree(t_path)
elif os.path.isfile(jid_file):
jid_ctime = os.stat(jid_file).st_ctime
hours_difference = (time.time()- jid_ctime) / 3600.0
hours_difference = (time.time() - jid_ctime) / 3600.0
if hours_difference > __opts__['keep_jobs'] and os.path.exists(t_path):
# Remove the entire t_path from the original JID dir
shutil.rmtree(t_path)

View File

@ -2342,7 +2342,8 @@ class State(object):
if not r_state.startswith('prerequired'):
req_stats.add('pre')
else:
req_stats.add('met')
if run_dict[tag].get('__state_ran__', True):
req_stats.add('met')
if r_state.endswith('_any'):
if 'met' in req_stats or 'change' in req_stats:
if 'fail' in req_stats:
@ -2620,6 +2621,7 @@ class State(object):
'duration': duration,
'start_time': start_time,
'comment': 'State was not run because onfail req did not change',
'__state_ran__': False,
'__run_num__': self.__run_num,
'__sls__': low['__sls__']}
self.__run_num += 1
@ -2630,6 +2632,7 @@ class State(object):
'duration': duration,
'start_time': start_time,
'comment': 'State was not run because none of the onchanges reqs changed',
'__state_ran__': False,
'__run_num__': self.__run_num,
'__sls__': low['__sls__']}
self.__run_num += 1

View File

@ -5333,9 +5333,14 @@ def copy(
subdir=False,
**kwargs):
'''
If the source file exists on the system, copy it to the named file. The
named file will not be overwritten if it already exists unless the force
option is set to True.
If the file defined by the ``source`` option exists on the minion, copy it
to the named path. The file will not be overwritten if it already exists,
unless the ``force`` option is set to ``True``.
.. note::
This state only copies files from one location on a minion to another
location on the same minion. For copying files from the master, use a
:py:func:`file.managed <salt.states.file.managed>` state.
name
The location of the file to copy to

View File

@ -1009,11 +1009,11 @@ def installed(
**WILDCARD VERSIONS**
As of the 2017.7.0 release, this state now supports wildcards in
package versions for SUSE SLES/Leap/Tumbleweed, Debian/Ubuntu, RHEL/CentOS,
Arch Linux, and their derivatives. Using wildcards can be useful for
packages where the release name is built into the version in some way,
such as for RHEL/CentOS which typically has version numbers like
``1.2.34-5.el7``. An example of the usage for this would be:
package versions for SUSE SLES/Leap/Tumbleweed, Debian/Ubuntu,
RHEL/CentOS, Arch Linux, and their derivatives. Using wildcards can be
useful for packages where the release name is built into the version in
some way, such as for RHEL/CentOS which typically has version numbers
like ``1.2.34-5.el7``. An example of the usage for this would be:
.. code-block:: yaml
@ -1021,6 +1021,11 @@ def installed(
pkg.installed:
- version: '1.2.34*'
Keep in mind that using wildcard versions will result in a slower state
run since Salt must gather the available versions of the specified
packages and figure out which of them match the specified wildcard
expression.
:param bool refresh:
This parameter controls whether or not the package repo database is
updated prior to installing the requested package(s).
@ -2668,7 +2673,7 @@ def removed(name,
.. code-block:: yaml
vim-enhanced:
pkg.installed:
pkg.removed:
- version: 2:7.4.160-1.el7
In version 2015.8.9, an **ignore_epoch** argument has been added to
@ -2774,7 +2779,7 @@ def purged(name,
.. code-block:: yaml
vim-enhanced:
pkg.installed:
pkg.purged:
- version: 2:7.4.160-1.el7
In version 2015.8.9, an **ignore_epoch** argument has been added to

View File

@ -12,7 +12,6 @@ import subprocess
# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
log = logging.getLogger(__name__)
@ -122,13 +121,13 @@ def combine_comments(comments):
'''
if not isinstance(comments, list):
comments = [comments]
for idx in range(len(comments)):
if not isinstance(comments[idx], six.string_types):
comments[idx] = six.text_type(comments[idx])
comments[idx] = comments[idx].strip()
if not comments[idx].startswith('#'):
comments[idx] = '#' + comments[idx]
return '\n'.join(comments)
ret = []
for comment in comments:
if not isinstance(comment, six.string_types):
comment = str(comment)
# Normalize for any spaces (or lack thereof) after the #
ret.append('# {0}\n'.format(comment.lstrip('#').lstrip()))
return ''.join(ret)
def version_to_evr(verstring):

View File

@ -75,7 +75,7 @@ class Schedule(object):
'''
instance = None
def __new__(cls, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, standalone=False):
def __new__(cls, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, utils=None, standalone=False):
'''
Only create one instance of Schedule
'''
@ -85,20 +85,21 @@ class Schedule(object):
# it in a WeakValueDictionary-- which will remove the item if no one
# references it-- this forces a reference while we return to the caller
cls.instance = object.__new__(cls)
cls.instance.__singleton_init__(opts, functions, returners, intervals, cleanup, proxy, standalone)
cls.instance.__singleton_init__(opts, functions, returners, intervals, cleanup, proxy, utils, standalone)
else:
log.debug('Re-using Schedule')
return cls.instance
# has to remain empty for singletons, since __init__ will *always* be called
def __init__(self, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, standalone=False):
def __init__(self, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, utils=None, standalone=False):
pass
# an init for the singleton instance to call
def __singleton_init__(self, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, standalone=False):
def __singleton_init__(self, opts, functions, returners=None, intervals=None, cleanup=None, proxy=None, utils=None, standalone=False):
self.opts = opts
self.proxy = proxy
self.functions = functions
self.utils = utils
self.standalone = standalone
self.skip_function = None
self.skip_during_range = None
@ -586,10 +587,11 @@ class Schedule(object):
# This also needed for ZeroMQ transport to reset all functions
# context data that could keep paretns connections. ZeroMQ will
# hang on polling parents connections from the child process.
utils = self.utils or salt.loader.utils(self.opts)
if self.opts['__role'] == 'master':
self.functions = salt.loader.runner(self.opts)
self.functions = salt.loader.runner(self.opts, utils=utils)
else:
self.functions = salt.loader.minion_mods(self.opts, proxy=self.proxy)
self.functions = salt.loader.minion_mods(self.opts, proxy=self.proxy, utils=utils)
self.returners = salt.loader.returners(self.opts, self.functions, proxy=self.proxy)
ret = {'id': self.opts.get('id', 'master'),
'fun': func,
@ -1411,6 +1413,8 @@ class Schedule(object):
self.functions = {}
returners = self.returners
self.returners = {}
utils = self.utils
self.utils = {}
try:
# Job is disabled, continue
if 'enabled' in data and not data['enabled']:
@ -1448,6 +1452,7 @@ class Schedule(object):
# Restore our function references.
self.functions = functions
self.returners = returners
self.utils = utils
def clean_proc_dir(opts):

View File

@ -373,7 +373,14 @@ def render_jinja_tmpl(tmplstr, context, tmplpath=None):
decoded_context[key] = value
continue
decoded_context[key] = salt.utils.locales.sdecode(value)
try:
decoded_context[key] = salt.utils.stringutils.to_unicode(value, encoding=SLS_ENCODING)
except UnicodeDecodeError as ex:
log.debug(
"Failed to decode using default encoding (%s), trying system encoding",
SLS_ENCODING,
)
decoded_context[key] = salt.utils.locales.sdecode(value)
try:
template = jinja_env.from_string(tmplstr)

View File

@ -82,6 +82,9 @@ cp "$busybox" "$rootfsDir/bin/busybox"
unset IFS
for module in "${modules[@]}"; do
# Don't stomp on the busybox binary (newer busybox releases
# include busybox in the --list-modules output)
test "$module" == "bin/busybox" && continue
mkdir -p "$(dirname "$module")"
ln -sf /bin/busybox "$module"
done

View File

@ -0,0 +1,25 @@
a:
cmd.run:
- name: exit 1
b:
cmd.run:
- name: echo b
- onfail:
- cmd: a
c:
cmd.run:
- name: echo c
- onfail:
- cmd: a
- require:
- cmd: b
d:
cmd.run:
- name: echo d
- onfail:
- cmd: a
- require:
- cmd: c

View File

@ -0,0 +1,25 @@
a:
cmd.run:
- name: exit 0
b:
cmd.run:
- name: echo b
- onfail:
- cmd: a
c:
cmd.run:
- name: echo c
- onfail:
- cmd: a
- require:
- cmd: b
d:
cmd.run:
- name: echo d
- onfail:
- cmd: a
- require:
- cmd: c

View File

@ -272,38 +272,53 @@ class PkgModuleTest(ModuleCase, SaltReturnAssertsMixin):
self.run_function('pkg.refresh_db')
if os_family == 'Suse':
# pkg.latest version returns empty if the latest version is already installed
vim_version_dict = self.run_function('pkg.latest_version', ['vim'])
vim_info = self.run_function('pkg.info_available', ['vim'])['vim']
if vim_version_dict == {}:
# Latest version is installed, get its version and construct
# a version selector so the immediately previous version is selected
vim_version = 'version=<'+vim_info['version']
else:
# Vim was not installed, so pkg.latest_version returns the latest one.
# Construct a version selector so immediately previous version is selected
vim_version = 'version=<'+vim_version_dict
# This test assumes that there are multiple possible versions of a
# package available. That makes it brittle if you pick just one
# target, as changes in the available packages will break the test.
# Therefore, we'll choose from several packages to make sure we get
# one that is suitable for this test.
packages = ('hwinfo', 'avrdude', 'diffoscope', 'vim')
# Only install a new version of vim if vim is up-to-date, otherwise we don't
# need this check. (And the test will fail when we check for the empty dict
# since vim gets upgraded in the install step.)
if 'out-of-date' not in vim_info['status']:
# Install a version of vim that should need upgrading
ret = self.run_function('pkg.install', ['vim', vim_version])
if not isinstance(ret, dict):
if ret.startswith('ERROR'):
self.skipTest('Could not install earlier vim to complete test.')
available = self.run_function('pkg.list_repo_pkgs', packages)
versions = self.run_function('pkg.version', packages)
for package in packages:
try:
new, old = available[package][:2]
except (KeyError, ValueError):
# Package not available, or less than 2 versions
# available. This is not a suitable target.
continue
else:
self.assertNotEqual(ret, {})
target = package
current = versions[target]
break
else:
# None of the packages have more than one version available, so
# we need to find new package(s). pkg.list_repo_pkgs can be
# used to get an overview of the available packages. We should
# try to find packages with few dependencies and small download
# sizes, to keep this test from taking longer than necessary.
self.fail('No suitable package found for this test')
# Run a system upgrade, which should catch the fact that Vim needs upgrading, and upgrade it.
# Make sure we have the 2nd-oldest available version installed
ret = self.run_function('pkg.install', [target], version=old)
if not isinstance(ret, dict):
if ret.startswith('ERROR'):
self.skipTest(
'Could not install older {0} to complete '
'test.'.format(target)
)
# Run a system upgrade, which should catch the fact that the
# targeted package needs upgrading, and upgrade it.
ret = self.run_function(func)
# The changes dictionary should not be empty.
if 'changes' in ret:
self.assertIn('vim', ret['changes'])
self.assertIn(target, ret['changes'])
else:
self.assertIn('vim', ret)
self.assertIn(target, ret)
else:
ret = self.run_function('pkg.list_upgrades')
if ret == '' or ret == {}:

View File

@ -1464,6 +1464,56 @@ class StateModuleTest(ModuleCase, SaltReturnAssertsMixin):
test_data = state_run['cmd_|-test_non_failing_state_|-echo "Should not run"_|-run']
self.assertIn('duration', test_data)
def test_multiple_onfail_requisite_with_required(self):
'''
test to ensure multiple states are run
when specified as onfails for a single state.
This is a test for the issue:
https://github.com/saltstack/salt/issues/46552
'''
state_run = self.run_function('state.sls', mods='requisites.onfail_multiple_required')
retcode = state_run['cmd_|-b_|-echo b_|-run']['changes']['retcode']
self.assertEqual(retcode, 0)
retcode = state_run['cmd_|-c_|-echo c_|-run']['changes']['retcode']
self.assertEqual(retcode, 0)
retcode = state_run['cmd_|-d_|-echo d_|-run']['changes']['retcode']
self.assertEqual(retcode, 0)
stdout = state_run['cmd_|-b_|-echo b_|-run']['changes']['stdout']
self.assertEqual(stdout, 'b')
stdout = state_run['cmd_|-c_|-echo c_|-run']['changes']['stdout']
self.assertEqual(stdout, 'c')
stdout = state_run['cmd_|-d_|-echo d_|-run']['changes']['stdout']
self.assertEqual(stdout, 'd')
def test_multiple_onfail_requisite_with_required_no_run(self):
'''
test to ensure multiple states are not run
when specified as onfails for a single state
which fails.
This is a test for the issue:
https://github.com/saltstack/salt/issues/46552
'''
state_run = self.run_function('state.sls', mods='requisites.onfail_multiple_required_no_run')
expected = 'State was not run because onfail req did not change'
stdout = state_run['cmd_|-b_|-echo b_|-run']['comment']
self.assertEqual(stdout, expected)
stdout = state_run['cmd_|-c_|-echo c_|-run']['comment']
self.assertEqual(stdout, expected)
stdout = state_run['cmd_|-d_|-echo d_|-run']['comment']
self.assertEqual(stdout, expected)
# listen tests
def test_listen_requisite(self):

View File

@ -59,12 +59,18 @@ class AuthTest(ShellCase):
def setUp(self):
for user in (self.userA, self.userB):
try:
if salt.utils.is_darwin() and user not in str(self.run_call('user.list_users')):
# workaround for https://github.com/saltstack/salt-jenkins/issues/504
raise KeyError
pwd.getpwnam(user)
except KeyError:
self.run_call('user.add {0} createhome=False'.format(user))
# only put userB into the group for the group auth test
try:
if salt.utils.is_darwin() and self.group not in str(self.run_call('group.info {0}'.format(self.group))):
# workaround for https://github.com/saltstack/salt-jenkins/issues/504
raise KeyError
grp.getgrnam(self.group)
except KeyError:
self.run_call('group.add {0}'.format(self.group))

View File

@ -6,12 +6,14 @@
'''
# Import Python libs
from __future__ import absolute_import, unicode_literals, print_function
import os
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.unit import skipIf
from tests.support.helpers import destructiveTest, requires_network
from tests.support.mixins import SaltReturnAssertsMixin
from tests.support.runtests import RUNTIME_VARS
# Import salt libs
import salt.modules.cmdmod as cmd
@ -42,10 +44,19 @@ class NpmStateTest(ModuleCase, SaltReturnAssertsMixin):
'''
Determine if URL-referenced NPM module can be successfully installed.
'''
ret = self.run_state('npm.installed', name='request/request#v2.81.1')
if LooseVersion(cmd.run('npm -v')) >= LooseVersion(MAX_NPM_VERSION):
user = os.environ.get('SUDO_USER', 'root')
npm_dir = os.path.join(RUNTIME_VARS.TMP, 'git-install-npm')
self.run_state('file.directory', name=npm_dir, user=user, dir_mode='755')
else:
user = None
npm_dir = None
ret = self.run_state('npm.installed', name='request/request#v2.81.1', runas=user, dir=npm_dir)
self.assertSaltTrueReturn(ret)
ret = self.run_state('npm.removed', name='git://github.com/request/request')
ret = self.run_state('npm.removed', name='git://github.com/request/request', runas=user, dir=npm_dir)
self.assertSaltTrueReturn(ret)
if npm_dir is not None:
self.run_state('file.absent', name=npm_dir)
@requires_network()
@destructiveTest

View File

@ -14,6 +14,7 @@ import os
import pwd
import glob
import shutil
import sys
# Import Salt Testing libs
from tests.support.mixins import SaltReturnAssertsMixin
@ -525,6 +526,7 @@ class PipStateTest(ModuleCase, SaltReturnAssertsMixin):
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
@skipIf(sys.version_info[:2] >= (3, 6), 'Old version of virtualenv too old for python3.6')
def test_46127_pip_env_vars(self):
'''
Test that checks if env_vars passed to pip.installed are also passed

View File

@ -22,16 +22,16 @@ import salt.utils.platform
from salt.ext import six
@destructiveTest
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
class PkgrepoTest(ModuleCase, SaltReturnAssertsMixin):
'''
pkgrepo state tests
'''
@destructiveTest
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkgrepo_01_managed(self, grains):
'''
This is a destructive test as it adds a repository.
Test adding a repo
'''
os_grain = self.run_function('grains.item', ['os'])['os']
os_release_info = tuple(self.run_function('grains.item', ['osrelease_info'])['osrelease_info'])
@ -56,12 +56,9 @@ class PkgrepoTest(ModuleCase, SaltReturnAssertsMixin):
for state_id, state_result in six.iteritems(ret):
self.assertSaltTrueReturn(dict([(state_id, state_result)]))
@destructiveTest
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
def test_pkgrepo_02_absent(self):
'''
This is a destructive test as it removes the repository added in the
above test.
Test removing the repo from the above test
'''
os_grain = self.run_function('grains.item', ['os'])['os']
os_release_info = tuple(self.run_function('grains.item', ['osrelease_info'])['osrelease_info'])
@ -78,3 +75,56 @@ class PkgrepoTest(ModuleCase, SaltReturnAssertsMixin):
self.assertReturnNonEmptySaltType(ret)
for state_id, state_result in six.iteritems(ret):
self.assertSaltTrueReturn(dict([(state_id, state_result)]))
@requires_system_grains
def test_pkgrepo_03_with_comments(self, grains):
'''
Test adding a repo with comments
'''
os_family = grains['os_family'].lower()
if os_family in ('redhat', 'suse'):
kwargs = {
'name': 'examplerepo',
'baseurl': 'http://example.com/repo',
'enabled': False,
'comments': ['This is a comment']
}
elif os_family in ('debian',):
self.skipTest('Debian/Ubuntu test case needed')
else:
self.skipTest("No test case for os_family '{0}'".format(os_family))
try:
# Run the state to add the repo
ret = self.run_state('pkgrepo.managed', **kwargs)
self.assertSaltTrueReturn(ret)
# Run again with modified comments
kwargs['comments'].append('This is another comment')
ret = self.run_state('pkgrepo.managed', **kwargs)
self.assertSaltTrueReturn(ret)
ret = ret[next(iter(ret))]
self.assertEqual(
ret['changes'],
{
'comments': {
'old': ['This is a comment'],
'new': ['This is a comment',
'This is another comment']
}
}
)
# Run a third time, no changes should be made
ret = self.run_state('pkgrepo.managed', **kwargs)
self.assertSaltTrueReturn(ret)
ret = ret[next(iter(ret))]
self.assertFalse(ret['changes'])
self.assertEqual(
ret['comment'],
"Package repo '{0}' already configured".format(kwargs['name'])
)
finally:
# Clean up
self.run_state('pkgrepo.absent', name=kwargs['name'])

View File

@ -736,6 +736,7 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
continue
results = self.run_suite('', name, suffix='test_*.py', load_from_name=True)
status.append(results)
return status
for suite in TEST_SUITES:
if suite != 'unit' and getattr(self.options, suite):
status.append(self.run_integration_suite(**TEST_SUITES[suite]))

View File

@ -303,7 +303,7 @@ class CMDMODTestCase(TestCase, LoaderModuleMockMixin):
environment = os.environ.copy()
popen_mock.return_value = Mock(
communicate=lambda *args, **kwags: ['{}', None],
communicate=lambda *args, **kwags: [b'', None],
pid=lambda: 1,
retcode=0
)

View File

@ -10,6 +10,7 @@ Unit tests for the Default Job Cache (local_cache).
from __future__ import absolute_import, print_function, unicode_literals
import os
import shutil
import time
import logging
import tempfile
import time
@ -82,6 +83,11 @@ class LocalCacheCleanOldJobsTestCase(TestCase, LoaderModuleMockMixin):
# Call clean_old_jobs function, patching the keep_jobs value with a
# very small value to force the call to clean the job.
with patch.dict(local_cache.__opts__, {'keep_jobs': 0.00000001}):
# Sleep on Windows because time.time is only precise to 3 decimal
# points, and therefore subtracting the jid_ctime from time.time
# will result in a negative number
if salt.utils.platform.is_windows():
time.sleep(0.25)
local_cache.clean_old_jobs()
# Assert that the JID dir was removed
@ -149,6 +155,11 @@ class LocalCacheCleanOldJobsTestCase(TestCase, LoaderModuleMockMixin):
# Call clean_old_jobs function, patching the keep_jobs value with a
# very small value to force the call to clean the job.
with patch.dict(local_cache.__opts__, {'keep_jobs': 0.00000001}):
# Sleep on Windows because time.time is only precise to 3 decimal
# points, and therefore subtracting the jid_ctime from time.time
# will result in a negative number
if salt.utils.platform.is_windows():
time.sleep(0.25)
local_cache.clean_old_jobs()
# Assert that the JID dir was removed

View File

@ -66,8 +66,8 @@ class SMTPReturnerTestCase(TestCase, LoaderModuleMockMixin):
'renderer': 'jinja|yaml',
'renderer_blacklist': [],
'renderer_whitelist': [],
'file_roots': [],
'pillar_roots': [],
'file_roots': {},
'pillar_roots': {},
'cachedir': '/'}), \
patch('salt.returners.smtp_return.gnupg'), \
patch('salt.returners.smtp_return.smtplib.SMTP') as mocked_smtplib:
@ -79,8 +79,8 @@ class SMTPReturnerTestCase(TestCase, LoaderModuleMockMixin):
'renderer': 'jinja|yaml',
'renderer_blacklist': [],
'renderer_whitelist': [],
'file_roots': [],
'pillar_roots': [],
'file_roots': {},
'pillar_roots': {},
'cachedir': '/'}), \
patch('salt.returners.smtp_return.smtplib.SMTP') as mocked_smtplib:
self._test_returner(mocked_smtplib)

View File

@ -433,6 +433,66 @@ class PillarTestCase(TestCase):
({'foo': 'bar', 'nested': {'level': {'foo': 'bar2'}}}, [])
)
def test_includes_override_sls(self):
opts = {
'renderer': 'json',
'renderer_blacklist': [],
'renderer_whitelist': [],
'state_top': '',
'pillar_roots': [],
'file_roots': [],
'extension_modules': ''
}
grains = {
'os': 'Ubuntu',
'os_family': 'Debian',
'oscodename': 'raring',
'osfullname': 'Ubuntu',
'osrelease': '13.04',
'kernel': 'Linux'
}
with patch('salt.pillar.compile_template') as compile_template:
# Test with option set to True
opts['pillar_includes_override_sls'] = True
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
# Mock getting the proper template files
pillar.client.get_state = MagicMock(
return_value={
'dest': '/path/to/pillar/files/foo.sls',
'source': 'salt://foo.sls'
}
)
compile_template.side_effect = [
{'foo': 'bar', 'include': ['blah']},
{'foo': 'bar2'}
]
self.assertEqual(
pillar.render_pillar({'base': ['foo.sls']}),
({'foo': 'bar2'}, [])
)
# Test with option set to False
opts['pillar_includes_override_sls'] = False
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
# Mock getting the proper template files
pillar.client.get_state = MagicMock(
return_value={
'dest': '/path/to/pillar/files/foo.sls',
'source': 'salt://foo.sls'
}
)
compile_template.side_effect = [
{'foo': 'bar', 'include': ['blah']},
{'foo': 'bar2'}
]
self.assertEqual(
pillar.render_pillar({'base': ['foo.sls']}),
({'foo': 'bar'}, [])
)
def test_topfile_order(self):
with patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) as get_file_client, \
patch('salt.pillar.salt.minion.Matcher') as Matcher: # autospec=True disabled due to py3 mock bug