Merge remote-tracking branch 'upstream/2015.5' into merge-forward-develop

Conflicts:
    doc/topics/releases/version_numbers.rst
    salt/cli/batch.py
    salt/minion.py
    salt/state.py
    setup.py
This commit is contained in:
Colton Myers 2015-06-18 11:43:11 -06:00
commit a5c9681672
22 changed files with 395 additions and 77 deletions

View File

@ -339,11 +339,11 @@ communication.
``enable_gpu_grains``
---------------------
Default: ``False``
Default: ``True``
The master can take a while to start up when lspci and/or dmidecode is used
to populate the grains for the master. Enable if you want to see GPU hardware
data for your master.
Enable GPU hardware data for your master. Be aware that the master can
take a while to start up when lspci and/or dmidecode is used to populate the
grains for the master.
.. conf_master:: job_cache

View File

@ -455,21 +455,6 @@ behavior is to have time-frame within all minions try to reconnect.
recon_randomize: True
.. conf_minion:: dns_check
``dns_check``
-------------
Default: ``True``
When healing, a dns_check is run. This is to make sure that the originally
resolved dns has not changed. If this is something that does not happen in your
environment, set this value to ``False``.
.. code-block:: yaml
dns_check: True
.. conf_minion:: cache_sreqs
``cache_sreqs``

View File

@ -1,6 +1,6 @@
========================
salt.states.jboss7
========================
=======================
salt.states.artifactory
=======================
.. automodule:: salt.states.artifactory
:members:

View File

@ -61,6 +61,7 @@ Set up an initial profile at ``/etc/salt/cloud.profiles`` or in the
provider: my-linode-config
size: Linode 1024
image: Arch Linux 2013.06
location: london
Sizes can be obtained using the ``--list-sizes`` option for the ``salt-cloud``
command:
@ -121,6 +122,31 @@ command:
...SNIP...
Locations can be obtained using the ``--list-locations`` option for the ``salt-cloud``
command:
.. code-block:: bash
# salt-cloud --list-locations my-linode-config
my-linode-config:
----------
linode:
----------
Atlanta, GA, USA:
----------
abbreviation:
atlanta
id:
4
Dallas, TX, USA:
----------
abbreviation:
dallas
id:
2
...SNIP...
Cloning
=======

View File

@ -23,7 +23,7 @@ Assigned codenames:
- Hydrogen: ``2014.1.0``
- Helium: ``2014.7.0``
- Lithium: ``2015.5``
- Lithium: ``2015.5.0``
- Beryllium: ``TBD``
- Boron: ``TBD``

View File

@ -33,11 +33,9 @@ configuration management system called ``Salt States``.
Installing Salt
---------------
SaltStack has been made to be very easy to install and get started. Setting up
Salt should be as easy as installing Salt via distribution packages on Linux or
via the Windows installer. The :doc:`installation documents
</topics/installation/index>` cover platform-specific installation in depth.
SaltStack has been made to be very easy to install and get started. The
:doc:`installation documents </topics/installation/index>` contain instructions
for all supported platforms.
Starting Salt
-------------
@ -636,9 +634,8 @@ Getting Deeper Into States
Two more in-depth States tutorials exist, which delve much more deeply into States
functionality.
1. Thomas' original states tutorial, :doc:`How Do I Use Salt
States?</topics/tutorials/starting_states>`, covers much more to get off the
ground with States.
1. :doc:`How Do I Use Salt States? </topics/tutorials/starting_states>`, covers much
more to get off the ground with States.
2. The :doc:`States Tutorial</topics/tutorials/states_pt1>` also provides a
fantastic introduction.

View File

@ -232,7 +232,7 @@ class SaltCMD(parsers.SaltCMDOptionParser):
not_response_minions = []
not_connected_minions = []
for each_minion in ret:
minion_ret = ret[each_minion].get('ret')
minion_ret = ret[each_minion]
if (
isinstance(minion_ret, string_types)
and minion_ret.startswith("Minion did not return")

View File

@ -37,6 +37,8 @@ configuration at:
provider: vsphere
user: myuser
password: verybadpass
template_user: root
template_password: mybadVMpassword
url: 'https://10.1.1.1:443'
Note: Your URL may or may not look like any of the following, depending on how
@ -51,17 +53,22 @@ your VMWare installation is configured:
10.1.1.1:443/sdk
folder: Name of the folder that will contain the new VM. If not set, the VM will
be added to the folder the original VM belongs to.
folder
Name of the folder that will contain the new VM. If not set, the VM will be added to
the folder the original VM belongs to.
resourcepool: MOR of the resourcepool to be used for the new vm. If not set, it
uses the same resourcepool than the original vm.
resourcepool
MOR of the resourcepool to be used for the new vm. If not set, it uses the same
resourcepool than the original vm.
datastore: MOR of the datastore where the virtual machine should be located. If
not specified, the current datastore is used.
datastore
MOR of the datastore where the virtual machine should be located. If not specified,
the current datastore is used.
host: MOR of the host where the virtual machine should be registered.
IF not specified:
host
MOR of the host where the virtual machine should be registered.
Id not specified:
* if resourcepool is not specified, current host is used.
* if resourcepool is specified, and the target pool represents a
stand-alone host, the host is used.
@ -70,8 +77,25 @@ host: MOR of the host where the virtual machine should be registered.
* if resourcepool is specified and the target pool represents a cluster
without DRS enabled, an InvalidArgument exception will be thrown.
template: Specifies whether or not the new virtual machine should be marked as a
template. Default is False.
template
Specifies whether or not the new virtual machine should be marked as a template.
Default is False.
template_user
Specifies the user to access the VM. Should be
template_password
The password with which to access the VM.
sudo
The user to access the VM with sudo privileges.
.. versionadded:: 2015.5.2
sudo_password
The password corresponding to the sudo user to access the VM with sudo privileges.
.. versionadded:: 2015.5.2
'''
from __future__ import absolute_import

View File

@ -731,7 +731,7 @@ class MWorker(multiprocessing.Process):
if 'cmd' not in data:
log.error('Received malformed command {0}'.format(data))
return {}
log.info('AES payload received with command {0}'.format(data['cmd']))
log.trace('AES payload received with command {0}'.format(data['cmd']))
if data['cmd'].startswith('__'):
return False
return self.aes_funcs.run_func(data['cmd'], data)

View File

@ -3687,6 +3687,9 @@ def manage_file(name,
This file is then grabbed and if it has template set, it renders the file to be placed
into the correct place on the system using salt.files.utils.copyfile()
ret
The initial state return data structure. Pass in ``None`` to use the default structure.
source
file reference on the master

View File

@ -317,7 +317,7 @@ def cloud_init_interface(name, vm_=None, **kwargs):
Use snapshot when cloning the container source
vgname
if using LVM: vgname
lgname
lvname
if using LVM: lvname
ip
ip for the primary nic
@ -1954,7 +1954,7 @@ def create(name,
cmd += ' -B {0}'.format(backing)
if backing in ('lvm',):
if lvname:
cmd += ' --lvname {0}'.format(vgname)
cmd += ' --lvname {0}'.format(lvname)
if vgname:
cmd += ' --vgname {0}'.format(vgname)
if backing not in ('dir', 'overlayfs'):

View File

@ -158,15 +158,16 @@ def removegroup(name, group):
return ret['retcode'] == 0
def chhome(name, home):
def chhome(name, home, persist=False):
'''
Change the home directory of the user
Change the home directory of the user, pass True for persist to move files
to the new home directory if the old home directory exist.
CLI Example:
.. code-block:: bash
salt '*' user.chhome foo \\\\fileserver\\home\\foo
salt '*' user.chhome foo \\\\fileserver\\home\\foo True
'''
pre_info = info(name)
@ -180,6 +181,11 @@ def chhome(name, home):
name, home)) != 0:
return False
if persist and home is not None and pre_info['home'] is not None:
cmd = 'move /Y {0} {1}'.format(pre_info['home'], home)
if __salt__['cmd.retcode'](cmd, python_shell=False) != 0:
log.debug('Failed to move the contents of the Home Directory')
post_info = info(name)
if post_info['home'] != pre_info['home']:
return post_info['home'] == home

View File

@ -1174,9 +1174,12 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
salt '*' pkg.hold <package name>
salt '*' pkg.hold pkgs='["foo", "bar"]'
'''
if 'yum-plugin-versionlock' not in list_pkgs():
on_redhat_5 = __grains__.get('osmajorrelease', None) == '5'
lock_pkg = 'yum-versionlock' if on_redhat_5 else 'yum-plugin-versionlock'
if lock_pkg not in list_pkgs():
raise SaltInvocationError(
'Packages cannot be held, yum-plugin-versionlock is not installed.'
'Packages cannot be held, {0} is not installed.'.format(lock_pkg)
)
if not name and not pkgs and not sources:
raise SaltInvocationError(
@ -1267,9 +1270,12 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
salt '*' pkg.unhold <package name>
salt '*' pkg.unhold pkgs='["foo", "bar"]'
'''
if 'yum-plugin-versionlock' not in list_pkgs():
on_redhat_5 = __grains__.get('osmajorrelease', None) == '5'
lock_pkg = 'yum-versionlock' if on_redhat_5 else 'yum-plugin-versionlock'
if lock_pkg not in list_pkgs():
raise SaltInvocationError(
'Packages cannot be unheld, yum-plugin-versionlock is not installed.'
'Packages cannot be unheld, {0} is not installed.'.format(lock_pkg)
)
if not name and not pkgs and not sources:
raise SaltInvocationError(

View File

@ -1215,32 +1215,24 @@ class State(object):
for arg in run:
update = False
for hind in range(len(high[name][state])):
if (isinstance(arg, six.string_types) and
isinstance(high[name][state][hind], six.string_types)):
if isinstance(arg, six.string_types) and isinstance(high[name][state][hind], six.string_types):
# replacing the function, replace the index
high[name][state].pop(hind)
high[name][state].insert(hind, arg)
update = True
continue
if (isinstance(arg, dict) and
isinstance(high[name][state][hind], dict)):
if isinstance(arg, dict) and isinstance(high[name][state][hind], dict):
# It is an option, make sure the options match
argfirst = next(iter(arg))
if (argfirst ==
next(iter(high[name][state][hind]))):
# They match, check if the option is a
# watch or require, append, otherwise
# replace
if (argfirst == 'require' or
argfirst == 'watch'):
# Extend the list
(high[name][state][hind][argfirst]
.extend(arg[argfirst]))
update = True
if argfirst == next(iter(high[name][state][hind])):
# If argfirst is a requisite then we must merge
# our requisite with that of the target state
if argfirst in STATE_REQUISITE_KEYWORDS:
high[name][state][hind][argfirst].extend(arg[argfirst])
# otherwise, its not a requisite and we are just extending (replacing)
else:
# Replace the value
high[name][state][hind] = arg
update = True
update = True
if (argfirst == 'name' and
next(iter(high[name][state][hind])) == 'names'):
# If names are overwritten by name use the name

View File

@ -55,8 +55,15 @@ def change(name, context=None, changes=None, lens=None, **kwargs):
State name
context
The context to use. Set this to a file path, prefixed by ``/files``, to
avoid redundancy, e.g.:
A file path, prefixed by ``/files``. Should resolve to an actual file
(not an arbitrary augeas path). This is used to avoid duplicating the
file name for each item in the changes list (for example, ``set bind 0.0.0.0``
in the example below operates on the file specified by ``context``). If
``context`` is not specified, a file path prefixed by ``/files`` should be
included with the ``set`` command.
The file path is examined to determine if the
specified changes are already present.
.. code-block:: yaml

View File

@ -115,7 +115,7 @@ def _changes(name,
change['home'] = home
if createhome:
newhome = home if home else lusr['home']
if not os.path.isdir(newhome):
if newhome is not None and not os.path.isdir(newhome):
change['homeDoesNotExist'] = newhome
if shell:

View File

@ -551,6 +551,7 @@ def dependency_information(include_salt_cloud=False):
('RAET', 'raet', '__version__'),
('ZMQ', 'zmq', 'zmq_version'),
('Mako', 'mako', '__version__'),
('Tornado', 'tornado', 'version'),
]
if include_salt_cloud:

View File

@ -123,6 +123,10 @@ class PkgModuleTest(integration.ModuleCase,
os_major_release = self.run_function('grains.item', ['osmajorrelease'])['osmajorrelease']
available = self.run_function('sys.doc', ['pkg.hold'])
if os_family == 'RedHat':
if os_major_release == '5':
self.skipTest('`yum versionlock` does not seem to work on RHEL/CentOS 5')
if available:
if os_family == 'RedHat':
lock_pkg = 'yum-versionlock' if os_major_release == '5' else 'yum-plugin-versionlock'

View File

@ -848,11 +848,9 @@ class StateModuleTest(integration.ModuleCase,
+ ' foobar: C\n'
)
# issue #8211, chaining complex prereq & prereq_in
# TODO: Actually this test fails
#ret = self.run_function('state.sls', mods='requisites.prereq_complex')
#result = self.normalize_ret(ret)
#self.assertEqual(expected_result_complex, result)
ret = self.run_function('state.sls', mods='requisites.prereq_complex')
result = self.normalize_ret(ret)
self.assertEqual(expected_result_complex, result)
# issue #8210 : prereq recursion undetected
# TODO: this test fails

View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import skipIf, TestCase
from salttesting.mock import (
NO_MOCK,
NO_MOCK_REASON,
MagicMock,
patch
)
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import Salt Libs
from salt.states import postgres_database
postgres_database.__opts__ = {}
postgres_database.__salt__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PostgresDatabaseTestCase(TestCase):
'''
Test cases for salt.states.postgres_database
'''
# 'present' function tests: 1
def test_present(self):
'''
Test to ensure that the named database is present
with the specified properties.
'''
name = 'frank'
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
mock_t = MagicMock(return_value=True)
mock = MagicMock(return_value={name: {}})
with patch.dict(postgres_database.__salt__,
{'postgres.db_list': mock,
'postgres.db_alter': mock_t}):
comt = ('Database {0} is already present'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(postgres_database.present(name), ret)
comt = ("Database frank has wrong parameters "
"which couldn't be changed on fly.")
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(postgres_database.present(name, tablespace='A',
lc_collate=True),
ret)
with patch.dict(postgres_database.__opts__, {'test': True}):
comt = ('Database frank exists, '
'but parameters need to be changed')
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(postgres_database.present(name,
tablespace='A'),
ret)
with patch.dict(postgres_database.__opts__, {'test': False}):
comt = ('Parameters for database frank have been changed')
ret.update({'comment': comt, 'result': True,
'changes': {name: 'Parameters changed'}})
self.assertDictEqual(postgres_database.present(name,
tablespace='A'),
ret)
# 'absent' function tests: 1
def test_absent(self):
'''
Test to ensure that the named database is absent.
'''
name = 'frank'
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
mock_t = MagicMock(return_value=True)
mock = MagicMock(side_effect=[True, True, False])
with patch.dict(postgres_database.__salt__,
{'postgres.db_exists': mock,
'postgres.db_remove': mock_t}):
with patch.dict(postgres_database.__opts__, {'test': True}):
comt = ('Database {0} is set to be removed'.format(name))
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(postgres_database.absent(name), ret)
with patch.dict(postgres_database.__opts__, {'test': False}):
comt = ('Database {0} has been removed'.format(name))
ret.update({'comment': comt, 'result': True,
'changes': {name: 'Absent'}})
self.assertDictEqual(postgres_database.absent(name), ret)
comt = ('Database {0} is not present, so it cannot be removed'
.format(name))
ret.update({'comment': comt, 'result': True, 'changes': {}})
self.assertDictEqual(postgres_database.absent(name), ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(PostgresDatabaseTestCase, needs_daemon=False)

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import skipIf, TestCase
from salttesting.mock import (
NO_MOCK,
NO_MOCK_REASON,
MagicMock,
patch
)
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import Salt Libs
from salt.states import serverdensity_device
serverdensity_device.__salt__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ServerdensityDeviceTestCase(TestCase):
'''
Test cases for salt.states.serverdensity_device
'''
# 'monitored' function tests: 1
def test_monitored(self):
'''
Test to device is monitored with Server Density.
'''
name = 'my_special_server'
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
mock_dict = MagicMock(return_value={'id': name})
mock_t = MagicMock(side_effect=[True, {'agentKey': True},
[{'agentKey': True}]])
mock_sd = MagicMock(side_effect=[['sd-agent'], [], True])
with patch.dict(serverdensity_device.__salt__,
{'status.all_status': mock_dict,
'grains.items': mock_dict,
'serverdensity_device.ls': mock_t,
'pkg.list_pkgs': mock_sd,
'serverdensity_device.install_agent': mock_sd}):
comt = ('Such server name already exists in this'
' Server Density account. And sd-agent is installed')
ret.update({'comment': comt})
self.assertDictEqual(serverdensity_device.monitored(name), ret)
comt = ('Successfully installed agent and created'
' device in Server Density db.')
ret.update({'comment': comt, 'changes': {'created_device':
{'agentKey': True},
'installed_agent': True}})
self.assertDictEqual(serverdensity_device.monitored(name), ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(ServerdensityDeviceTestCase, needs_daemon=False)

View File

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import skipIf, TestCase
from salttesting.mock import (
NO_MOCK,
NO_MOCK_REASON,
MagicMock,
patch
)
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import Salt Libs
from salt.states import slack
slack.__salt__ = {}
slack.__opts__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class SlackTestCase(TestCase):
'''
Test cases for salt.states.slack
'''
# 'post_message' function tests: 1
def test_post_message(self):
'''
Test to send a message to a Slack channel.
'''
name = 'slack-message'
channel = '#general'
from_name = 'SuperAdmin'
message = 'This state was executed successfully.'
ret = {'name': name,
'changes': {},
'result': None,
'comment': ''}
with patch.dict(slack.__opts__, {'test': True}):
comt = ('The following message is to be sent to Slack: {0}'
.format(message))
ret.update({'comment': comt})
self.assertDictEqual(slack.post_message(name, channel, from_name,
message), ret)
with patch.dict(slack.__opts__, {'test': False}):
comt = ('Slack channel is missing: None')
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(slack.post_message(name, None, from_name,
message), ret)
comt = ('Slack from name is missing: None')
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(slack.post_message(name, channel, None,
message), ret)
comt = ('Slack message is missing: None')
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(slack.post_message(name, channel, from_name,
None), ret)
mock = MagicMock(return_value=True)
with patch.dict(slack.__salt__, {'slack.post_message': mock}):
comt = ('Sent message: slack-message')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(slack.post_message(name, channel,
from_name, message),
ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(SlackTestCase, needs_daemon=False)