Merge pull request #33508 from rallytime/merge-2016.3

[2016.3] Merge forward from 2015.8 to 2016.3
This commit is contained in:
Nicole Thomas 2016-05-25 14:34:49 -06:00
commit 9199101ef2
25 changed files with 513 additions and 96 deletions

View file

@ -377,6 +377,10 @@
#include:
# - /etc/salt/extra_config
# - /etc/roles/webserver
# The syndic minion can verify that it is talking to the correct master via the
# key fingerprint of the higher-level master with the "syndic_finger" config.
#syndic_finger: ''
#
#
#

View file

@ -2236,6 +2236,26 @@ configuration is the same as :conf_master:`file_roots`:
prod:
- /srv/pillar/prod
.. conf_master:: pillar_opts
``pillar_opts``
---------------
Default: ``False``
The ``pillar_opts`` option adds the master configuration file data to a dict in
the pillar called ``master``. This can be used to set simple configurations in
the master config file that can then be used on minions.
Note that setting this option to ``True`` means the master config file will be
included in all minion's pillars. While this makes global configuration of services
and systems easy, it may not be desired if sensitive data is stored in the master
configuration.
.. code-block:: yaml
pillar_opts: False
.. _master-configuration-ext-pillar:
.. conf_master:: ext_pillar

View file

@ -909,6 +909,20 @@ what you are doing! Transports are explained in :ref:`Salt Transports
transport: zeromq
.. conf_minion:: syndic_finger
``syndic_finger``
-----------------
Default: ``''``
The key fingerprint of the higher-level master for the syndic to verify it is
talking to the intended master.
.. code-block:: yaml
syndic_finger: 'ab:30:65:2a:d6:9e:20:4f:d8:b2:f3:a7:d4:65:50:10'
Minion Module Management
========================

View file

@ -341,3 +341,13 @@ string with quotes:
ValueError: month must be in 1..12
>>> yaml.safe_load('"4017-16-20"')
'4017-16-20'
Keys Limited to 1024 Characters
===============================
Simple keys are limited to a single line and cannot be longer that 1024 characters.
This is a limitation from PyYaml, as seen in a comment in `PyYAML's code`_, and
applies to anything parsed by YAML in Salt.
.. _PyYAML's code: http://pyyaml.org/browser/pyyaml/trunk/lib/yaml/scanner.py#L91

View file

@ -17,6 +17,20 @@ things:
Otherwise, it will attempt to connect to a master and fail. The salt-call
command stands on its own and does not need the salt-minion daemon.
Minion Configuration
--------------------
Throughout this document there are several references to setting different
options to configure a masterless Minion. Salt Minions are easy to configure
via a configuration file that is located, by default, in ``/etc/salt/minion``.
Note, however, that on FreeBSD systems, the minion configuration file is located
in ``/usr/local/etc/salt/minion``.
You can learn more about minion configuration options in the
:ref:`Configuring the Salt Minion <configuration-salt-minion>` docs.
Telling Salt Call to Run Masterless
===================================
@ -39,7 +53,6 @@ Now the salt-call command will not look for a master and will assume that the
local system has all of the file and pillar resources.
Running States Masterless
=========================
@ -81,6 +94,7 @@ it unnecessary to change the configuration file:
salt-call state.apply --local
External Pillars
================

View file

@ -55,7 +55,7 @@ RETVAL=0
start() {
echo -n $"Starting salt-minion daemon: "
if [ -f $SUSE_RELEASE ]; then
startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
rc_status -v
elif [ -e $DEBIAN_VERSION ]; then
if [ -f $LOCKFILE ]; then

View file

@ -117,6 +117,10 @@ try:
PAM_AUTHENTICATE.restype = c_int
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
PAM_ACCT_MGMT = LIBPAM.pam_acct_mgmt
PAM_ACCT_MGMT.restype = c_int
PAM_ACCT_MGMT.argtypes = [PamHandle, c_int]
PAM_END = LIBPAM.pam_end
PAM_END.restype = c_int
PAM_END.argtypes = [PamHandle, c_int]
@ -171,6 +175,8 @@ def authenticate(username, password):
return False
retval = PAM_AUTHENTICATE(handle, 0)
if retval == 0:
PAM_ACCT_MGMT(handle, 0)
PAM_END(handle, 0)
return retval == 0

View file

@ -68,8 +68,7 @@ def beacon(config):
'''
ret = []
for diskusage in config:
mount = diskusage.keys()[0]
for mount in config:
try:
_current_usage = psutil.disk_usage(mount)
@ -79,7 +78,7 @@ def beacon(config):
continue
current_usage = _current_usage.percent
monitor_usage = diskusage[mount]
monitor_usage = config[mount]
if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage)

View file

@ -4,8 +4,11 @@ Execution module to work with etcd
:depends: - python-etcd
In order to use an etcd server, a profile should be created in the master
configuration file:
Configuration
-------------
To work with an etcd server you must configure an etcd profile. The etcd config
can be set in either the Salt Minion configuration file or in pillar:
.. code-block:: yaml
@ -21,6 +24,17 @@ or clusters are available.
etcd.host: 127.0.0.1
etcd.port: 4001
.. note::
The etcd configuration can also be set in the Salt Master config file,
but in order to use any etcd configurations defined in the Salt Master
config, the :conf_master:`pillar_opts` must be set to ``True``.
Be aware that setting ``pillar_opts`` to ``True`` has security implications
as this makes all master configuration settings available in all minion's
pillars.
'''
from __future__ import absolute_import

View file

@ -448,6 +448,12 @@ def image_schema(profile=None):
'''
Returns names and descriptions of the schema "image"'s
properties for this profile's instance of glance
CLI Example:
.. code-block:: bash
salt '*' glance.image_schema
'''
return schema_get('image', profile)
@ -459,6 +465,13 @@ def image_update(id=None, name=None, profile=None, **kwargs): # pylint: disable
- min_ram (in MB)
- protected (bool)
- visibility ('public' or 'private')
CLI Example:
.. code-block:: bash
salt '*' glance.image_update id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df
salt '*' glance.image_update name=f16-jeos
'''
if id:
image = image_show(id=id, profile=profile)
@ -512,6 +525,12 @@ def schema_get(name, profile=None):
- images
- member
- members
CLI Example:
.. code-block:: bash
salt '*' glance.schema_get name=f16-jeos
'''
g_client = _auth(profile)
pformat = pprint.PrettyPrinter(indent=4).pformat

View file

@ -562,6 +562,12 @@ def list_(profile=None):
'''
To maintain the feel of the nova command line, this function simply calls
the server_list function.
CLI Example:
.. code-block:: bash
salt '*' nova.list
'''
return server_list(profile=profile)

View file

@ -346,6 +346,27 @@ def psql_query(query, user=None, host=None, port=None, maintenance_db=None,
WITH updated AS (UPDATE pg_authid SET rolconnlimit = 2000 WHERE
rolname = 'rolename' RETURNING rolconnlimit) SELECT * FROM updated;
query
The query string.
user
Database username, if different from config or default.
host
Database host, if different from config or default.
port
Database port, if different from the config or default.
maintenance_db
The database to run the query against.
password
User password, if different from the config or default.
runas
User to run the command as.
CLI Example:
.. code-block:: bash

View file

@ -606,3 +606,29 @@ def version_cmp(ver1, ver2):
log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc))
return salt.utils.version_cmp(ver1, ver2)
def checksum(*paths):
'''
Return if the signature of a RPM file is valid.
CLI Example:
.. code-block:: bash
salt '*' lowpkg.checksum /path/to/package1.rpm
salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm
'''
ret = dict()
if not paths:
raise CommandExecutionError("No package files has been specified.")
for package_file in paths:
ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and
not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file],
ignore_retcode=True,
output_loglevel='trace',
python_shell=False))
return ret

View file

@ -52,7 +52,7 @@ import salt.utils.process
import salt.utils.url
import salt.wheel
from salt.exceptions import (
SaltReqTimeoutError, SaltRenderError, CommandExecutionError
SaltReqTimeoutError, SaltRenderError, CommandExecutionError, SaltInvocationError
)
__proxyenabled__ = ['*']
@ -1046,7 +1046,7 @@ def runner(_fun, **kwargs):
return rclient.cmd(_fun, kwarg=kwargs)
def wheel(_fun, **kwargs):
def wheel(_fun, *args, **kwargs):
'''
Execute a wheel module (this function must be run on the master)
@ -1054,6 +1054,13 @@ def wheel(_fun, **kwargs):
name
The name of the function to run
args
Any positional arguments to pass to the wheel function. A common example
of this would be the ``match`` arg needed for key functions.
.. versionadded:: v2015.8.11
kwargs
Any keyword arguments to pass to the wheel function
@ -1061,10 +1068,33 @@ def wheel(_fun, **kwargs):
.. code-block:: bash
salt '*' saltutil.wheel key.accept match=jerry
salt '*' saltutil.wheel key.accept jerry
'''
wclient = salt.wheel.WheelClient(__opts__)
return wclient.cmd(_fun, kwarg=kwargs)
if __opts__['__role'] == 'minion':
master_config = os.path.join(os.path.dirname(__opts__['conf_file']),
'master')
master_opts = salt.config.client_config(master_config)
wheel_client = salt.wheel.WheelClient(master_opts)
else:
wheel_client = salt.wheel.WheelClient(__opts__)
# The WheelClient cmd needs args, kwargs, and pub_data separated out from
# the "normal" kwargs structure, which at this point contains __pub_x keys.
pub_data = {}
valid_kwargs = {}
for key, val in six.iteritems(kwargs):
if key.startswith('__'):
pub_data[key] = val
else:
valid_kwargs[key] = val
try:
ret = wheel_client.cmd(_fun, arg=args, pub_data=pub_data, kwarg=valid_kwargs)
except SaltInvocationError:
raise CommandExecutionError('This command can only be executed on a minion '
'that is located on the master.')
return ret
# this is the only way I could figure out how to get the REAL file_roots

View file

@ -207,7 +207,22 @@ def low(data, queue=False, **kwargs):
return ret
def high(data, test=False, queue=False, **kwargs):
def _get_test_value(test=None, **kwargs):
'''
Determine the correct value for the test flag.
'''
ret = True
if test is None:
if salt.utils.test_mode(test=test, **kwargs):
ret = True
else:
ret = __opts__.get('test', None)
else:
ret = test
return ret
def high(data, test=None, queue=False, **kwargs):
'''
Execute the compound calls stored in a single set of high data
@ -225,12 +240,7 @@ def high(data, test=False, queue=False, **kwargs):
return conflict
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
elif test is not None:
opts['test'] = test
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
pillar = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -662,13 +672,7 @@ def highstate(test=None,
opts = _get_opts(kwargs.get('localconfig'))
if test is None:
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
else:
opts['test'] = test
opts['test'] = _get_test_value(test, **kwargs)
if 'env' in kwargs:
salt.utils.warn_until(
@ -876,12 +880,7 @@ def sls(mods,
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
elif test is not None:
opts['test'] = test
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
pillar = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1003,10 +1002,7 @@ def top(topfn,
return err
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
pillar = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1119,10 +1115,7 @@ def sls_id(
return conflict
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
if 'pillarenv' in kwargs:
opts['pillarenv'] = kwargs['pillarenv']
st_ = salt.state.HighState(opts)
@ -1183,10 +1176,7 @@ def show_low_sls(mods,
return conflict
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
if 'pillarenv' in kwargs:
opts['pillarenv'] = kwargs['pillarenv']
st_ = salt.state.HighState(opts)
@ -1239,10 +1229,7 @@ def show_sls(mods, saltenv='base', test=None, queue=False, env=None, **kwargs):
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
pillar = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1339,10 +1326,7 @@ def single(fun, name, test=None, queue=False, **kwargs):
'name': name})
orig_test = __opts__.get('test', None)
opts = _get_opts(kwargs.get('localconfig'))
if salt.utils.test_mode(test=test, **kwargs):
opts['test'] = True
else:
opts['test'] = __opts__.get('test', None)
opts['test'] = _get_test_value(test, **kwargs)
pillar = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1398,7 +1382,7 @@ def clear_cache():
return ret
def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs):
def pkg(pkg_path, pkg_sum, hash_type, test=None, **kwargs):
'''
Execute a packaged state run, the packaged state run will exist in a
tarball available locally. This packaged state
@ -1442,10 +1426,7 @@ def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs):
popts = _get_opts(kwargs.get('localconfig'))
popts['fileclient'] = 'local'
popts['file_roots'] = {}
if salt.utils.test_mode(test=test, **kwargs):
popts['test'] = True
else:
popts['test'] = __opts__.get('test', None)
popts['test'] = _get_test_value(test, **kwargs)
envs = os.listdir(root)
for fn_ in envs:
full = os.path.join(root, fn_)

View file

@ -769,6 +769,7 @@ def mod_repo(repo, **kwargs):
# Modify added or existing repo according to the options
cmd_opt = []
global_cmd_opt = []
if 'enabled' in kwargs:
cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable')
@ -784,18 +785,18 @@ def mod_repo(repo, **kwargs):
if 'gpgcheck' in kwargs:
cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck')
if kwargs.get('gpgautoimport') is True:
cmd_opt.append('--gpg-auto-import-keys')
if 'priority' in kwargs:
cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY)))
if 'humanname' in kwargs:
cmd_opt.append("--name='{0}'".format(kwargs.get('humanname')))
if kwargs.get('gpgautoimport') is True:
global_cmd_opt.append('--gpg-auto-import-keys')
if cmd_opt:
cmd_opt.append(repo)
__zypper__.refreshable.xml.call('mr', *cmd_opt)
cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo]
__zypper__.refreshable.xml.call(*cmd_opt)
# If repo nor added neither modified, error should be thrown
if not added and not cmd_opt:
@ -1583,6 +1584,9 @@ def download(*packages, **kwargs):
pkg_ret[key] = pkg_info
if pkg_ret:
failed = [pkg for pkg in packages if pkg not in pkg_ret]
if failed:
pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed)))
return pkg_ret
raise CommandExecutionError(

View file

@ -349,9 +349,13 @@ def list_jobs(ext_source=None,
if search_target and _match:
_match = False
if 'Target' in ret[item]:
for key in salt.utils.split_input(search_target):
if fnmatch.fnmatch(ret[item]['Target'], key):
_match = True
targets = ret[item]['Target']
if isinstance(targets, six.string_types):
targets = [targets]
for target in targets:
for key in salt.utils.split_input(search_target):
if fnmatch.fnmatch(target, key):
_match = True
if search_function and _match:
_match = False
@ -488,6 +492,33 @@ def print_job(jid, ext_source=None, outputter=None):
return ret
def exit_success(jid, ext_source=None):
'''
Check if a job has been executed and exit successfully
jid
The jid to look up.
ext_source
The external job cache to use. Default: `None`.
CLI Example:
.. code-block:: bash
salt-run jobs.exit_success 20160520145827701627
'''
ret = dict()
data = lookup_jid(
jid,
ext_source=ext_source
)
for minion in data:
if "retcode" in data[minion]:
ret[minion] = True if not data[minion]['retcode'] else False
return ret
def last_run(ext_source=None,
outputter=None,
metadata=None,

View file

@ -10,11 +10,11 @@ Manage etcd Keys
This state module supports setting and removing keys from etcd.
Salt Master Configuration
-------------------------
Configuration
-------------
To work with an etcd server you must configure an etcd profile in the Salt
Master configuration, for example:
To work with an etcd server you must configure an etcd profile. The etcd config
can be set in either the Salt Minion configuration file or in pillar:
.. code-block:: yaml
@ -22,14 +22,25 @@ Master configuration, for example:
etcd.host: 127.0.0.1
etcd.port: 4001
You can also configure etcd without a profile however it is recommended that
you use profiles:
It is technically possible to configure etcd without using a profile, but this
is not considered to be a best practice, especially when multiple etcd servers
or clusters are available.
.. code-block:: yaml
etcd.host: 127.0.0.1
etcd.port: 4001
.. note::
The etcd configuration can also be set in the Salt Master config file,
but in order to use any etcd configurations defined in the Salt Master
config, the :conf_master:`pillar_opts` must be set to ``True``.
Be aware that setting ``pillar_opts`` to ``True`` has security implications
as this makes all master configuration settings available in all minion's
pillars.
Available Functions
-------------------

View file

@ -10,16 +10,19 @@ file on the minions, by default at: /etc/salt/grains
Note: This does NOT override any grains set in the minion file.
'''
# Import Python libs
from __future__ import absolute_import
from salt.defaults import DEFAULT_TARGET_DELIM
import re
# Import Salt libs
from salt.defaults import DEFAULT_TARGET_DELIM
def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
'''
Ensure that a grain is set
.. versionchanged:: 2016.3.0
.. versionchanged:: v2015.8.2
name
The grain name
@ -27,14 +30,16 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
value
The value to set on the grain
:param force: If force is True, the existing grain will be overwritten
force
If force is True, the existing grain will be overwritten
regardless of its existing or provided value type. Defaults to False
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
:param delimiter: A delimiter different from the default can be provided.
delimiter
A delimiter different from the default can be provided.
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
It is now capable to set a grain to a complex value (ie. lists and dicts)
and supports nested grains as well.
@ -59,11 +64,11 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
- name: icinga:Apache SSL
- value:
- command: check_https
- params: -H localhost -p 443 -S
- params: -H localhost -p 443 -S
with,a,custom,delimiter:
grains.present:
- value: yay
- value: yay
- delimiter: ,
'''
name = re.sub(delimiter, DEFAULT_TARGET_DELIM, name)
@ -106,9 +111,10 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM):
value
The value is present in the list type grain.
:param delimiter: A delimiter different from the default ``:`` can be provided.
delimiter
A delimiter different from the default ``:`` can be provided.
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
The grain should be `list type <http://docs.python.org/2/tutorial/datastructures.html#data-structures>`_
@ -198,9 +204,10 @@ def list_absent(name, value, delimiter=DEFAULT_TARGET_DELIM):
value
The value to delete from the grain list.
:param delimiter: A delimiter different from the default ``:`` can be provided.
delimiter
A delimiter different from the default ``:`` can be provided.
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
The grain should be `list type <http://docs.python.org/2/tutorial/datastructures.html#data-structures>`_
@ -273,19 +280,23 @@ def absent(name,
name
The grain name
:param destructive: If destructive is True, delete the entire grain. If
destructive
If destructive is True, delete the entire grain. If
destructive is False, set the grain's value to None. Defaults to False.
:param force: If force is True, the existing grain will be overwritten
force
If force is True, the existing grain will be overwritten
regardless of its existing or provided value type. Defaults to False
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
:param delimiter: A delimiter different from the default can be provided.
delimiter
A delimiter different from the default can be provided.
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
.. versionchanged:: v2015.8.2
.. versionchanged:: 2016.3.0
This state now support nested grains and complex values. It is also more
conservative: if a grain has a value that is a list or a dict, it will
not be removed unless the `force` parameter is True.
@ -367,13 +378,15 @@ def append(name, value, convert=False,
value
The value to append
:param convert: If convert is True, convert non-list contents into a list.
convert
If convert is True, convert non-list contents into a list.
If convert is False and the grain contains non-list contents, an error
is given. Defaults to False.
:param delimiter: A delimiter different from the default can be provided.
delimiter
A delimiter different from the default can be provided.
.. versionadded:: 2016.3.0
.. versionadded:: v2015.8.2
.. code-block:: yaml

View file

@ -274,6 +274,11 @@ def managed(name, ppa=None, **kwargs):
ret['result'] = False
ret['comment'] = 'You may not use both "keyid"/"keyserver" and ' \
'"key_url" argument.'
if 'repo' in kwargs:
ret['result'] = False
ret['comment'] = ('\'repo\' is not a supported argument for this '
'state. The \'name\' argument is probably what was '
'intended.')
return ret
repo = name

View file

@ -65,13 +65,21 @@ import time
# Import Salt libs
from salt.exceptions import CommandExecutionError
__virtualname__ = 'service'
def __virtual__():
'''
Only make these states available if a service provider has been detected or
assigned for this minion
'''
return 'service.start' in __salt__
if 'service.start' in __salt__:
return __virtualname__
else:
return (False, 'No service execution module loaded: '
'check support for service management on {0} '
''.format(__grains__.get('osfinger', __grains__['os']))
)
def _enabled_used_error(ret):
@ -283,7 +291,7 @@ def _available(name, ret):
def running(name, enable=None, sig=None, init_delay=None, **kwargs):
'''
Verify that the service is running
Ensure that the service is running
name
The name of the init or rc script used to manage the service
@ -473,7 +481,7 @@ def dead(name, enable=None, sig=None, **kwargs):
def enabled(name, **kwargs):
'''
Verify that the service is enabled on boot, only use this state if you
Ensure that the service is enabled on boot, only use this state if you
don't want to manage the running process, remember that if you want to
enable a running service to use the enable: True option for the running
or dead function.
@ -492,7 +500,7 @@ def enabled(name, **kwargs):
def disabled(name, **kwargs):
'''
Verify that the service is disabled on boot, only use this state if you
Ensure that the service is disabled on boot, only use this state if you
don't want to manage the running process, remember that if you want to
disable a service to use the enable: False option for the running or dead
function.

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
'''
Integration tests for the saltutil module.
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing libs
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import Salt libs
import integration
class SaltUtilModuleTest(integration.ModuleCase):
'''
Testcase for the saltutil execution module
'''
# Tests for the wheel function
def test_wheel_just_function(self):
'''
Tests using the saltutil.wheel function when passing only a function.
'''
ret = self.run_function('saltutil.wheel', ['minions.connected'])
self.assertEqual(ret, ['minion', 'sub_minion'])
def test_wheel_with_arg(self):
'''
Tests using the saltutil.wheel function when passing a function and an arg.
'''
ret = self.run_function('saltutil.wheel', ['key.list', 'minion'])
self.assertEqual(ret, {})
def test_wheel_no_arg_raise_error(self):
'''
Tests using the saltutil.wheel function when passing a function that requires
an arg, but one isn't supplied.
'''
self.assertRaises(TypeError, 'saltutil.wheel', ['key.list'])
def test_wheel_with_kwarg(self):
'''
Tests using the saltutil.wheel function when passing a function and a kwarg.
This function just generates a key pair, but doesn't do anything with it. We
just need this for testing purposes.
'''
ret = self.run_function('saltutil.wheel', ['key.gen'], keysize=1024)
self.assertIn('pub', ret)
self.assertIn('priv', ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(SaltUtilModuleTest)

View file

@ -66,6 +66,7 @@ class SysModuleTest(integration.ModuleCase):
'runtests_decorators.depends_will_fallback',
'runtests_decorators.missing_depends',
'runtests_decorators.missing_depends_will_fallback',
'swift.head',
'yumpkg.expand_repo_def',
'yumpkg5.expand_repo_def',
'container_resource.run',

View file

@ -9,6 +9,7 @@ from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import (
Mock,
MagicMock,
patch,
NO_MOCK,
@ -413,6 +414,39 @@ class ZypperTestCase(TestCase):
self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update')
self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update')
def test_modify_repo_gpg_auto_import_keys_parameter_position(self):
'''
Tests if when modifying a repo, --gpg-auto-import-keys is a global option
:return:
'''
zypper_patcher = patch.multiple(
'salt.modules.zypper',
**{
'_get_configured_repos': Mock(
**{
'return_value.sections.return_value': ['mock-repo-name']
}
),
'__zypper__': Mock(),
'get_repo': Mock()
}
)
with zypper_patcher:
zypper.mod_repo(
'mock-repo-name',
**{
'disabled': False,
'url': 'http://repo.url/some/path',
'gpgkey': 'http://repo.key',
'refresh': True,
'gpgautoimport': True
}
)
zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
'--gpg-auto-import-keys', 'mr', '--refresh', 'mock-repo-name'
)
if __name__ == '__main__':
from integration import run_tests
run_tests(ZypperTestCase, needs_daemon=False)

View file

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
'''
unit tests for the jobs runner
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import skipIf, TestCase
from salttesting.helpers import ensure_in_syspath
from salttesting.mock import (
NO_MOCK,
NO_MOCK_REASON,
patch
)
ensure_in_syspath('../../')
# Import Salt Libs
from salt.runners import jobs
import salt.minion
jobs.__opts__ = {'ext_job_cache': None, 'master_job_cache': 'local_cache'}
jobs.__salt__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class JobsTest(TestCase):
'''
Validate the jobs runner
'''
def test_list_jobs_with_search_target(self):
'''
test jobs.list_jobs runner with search_target args
'''
mock_jobs_cache = {
'20160524035503086853': {'Arguments': [],
'Function': 'test.ping',
'StartTime': '2016, May 24 03:55:03.086853',
'Target': 'node-1-1.com',
'Target-type': 'glob',
'User': 'root'},
'20160524035524895387': {'Arguments': [],
'Function': 'test.ping',
'StartTime': '2016, May 24 03:55:24.895387',
'Target': ['node-1-2.com', 'node-1-1.com'],
'Target-type': 'list',
'User': 'sudo_ubuntu'}
}
def return_mock_jobs():
return mock_jobs_cache
class MockMasterMinion(object):
returners = {'local_cache.get_jids': return_mock_jobs}
def __init__(self, *args, **kwargs):
pass
returns = {'all': mock_jobs_cache,
'node-1-1.com': mock_jobs_cache,
'node-1-2.com': {'20160524035524895387':
mock_jobs_cache['20160524035524895387']},
'non-existant': {}}
with patch.object(salt.minion, 'MasterMinion', MockMasterMinion):
self.assertEqual(jobs.list_jobs(), returns['all'])
self.assertEqual(jobs.list_jobs(search_target=['node-1-1*',
'node-1-2*']),
returns['all'])
self.assertEqual(jobs.list_jobs(search_target='node-1-1.com'),
returns['node-1-1.com'])
self.assertEqual(jobs.list_jobs(search_target='node-1-2.com'),
returns['node-1-2.com'])
self.assertEqual(jobs.list_jobs(search_target='non-existant'),
returns['non-existant'])
if __name__ == '__main__':
from integration import run_tests
run_tests(JobsTest, needs_daemon=False)