Merge remote-tracking branch 'upstream/2014.7' into merge-forward-2015.2

Conflicts:
	doc/conf.py
	doc/man/salt-api.1
	doc/man/salt-call.1
	doc/man/salt-cloud.1
	doc/man/salt-cp.1
	doc/man/salt-key.1
	doc/man/salt-master.1
	doc/man/salt-minion.1
	doc/man/salt-run.1
	doc/man/salt-ssh.1
	doc/man/salt-syndic.1
	doc/man/salt-unity.1
	doc/man/salt.1
	doc/man/salt.7
	salt/cloud/clouds/linode.py
	salt/states/composer.py
	salt/states/win_update.py
This commit is contained in:
Colton Myers 2015-03-11 15:14:56 -06:00
commit ebe7eeafd9
12 changed files with 1517 additions and 261 deletions

View File

@ -151,7 +151,7 @@ copyright = '2015 SaltStack, Inc.'
version = salt.version.__version__
#release = '.'.join(map(str, salt.version.__version_info__))
release = '2015.2'
release = '2014.7.2'
needs_sphinx = '1.3'

View File

@ -1885,7 +1885,7 @@ strategy between different sources. It accepts 3 values:
* aggregate:
instructs aggregation of elements between sources that use the #!yamlex rendered.
instructs aggregation of elements between sources that use the #!yamlex renderer.
For example, these two documents:

View File

@ -6,8 +6,28 @@ Linode is a public cloud provider with a focus on Linux instances.
Dependencies
============
* linode-python >= 1.1
OR
* Libcloud >= 0.13.2
This driver supports accessing Linode via linode-python or Apache Libcloud.
Linode-python is recommended, it is more full-featured than Libcloud. In
particular using linode-python enables stopping, starting, and cloning
machines.
Driver selection is automatic. If linode-python is present it will be used.
If it is absent, salt-cloud will fall back to Libcloud. If neither are present
salt-cloud will abort.
NOTE: linode-python 1.1 or later is recommended. As of this publication it is
not yet on PyPi. Earlier versions of linode-python should work but can leak
sensitive information into the debug logs.
Linode-python can be downloaded from
https://github.com/tjfontaine/linode-python.
Configuration
=============
Linode requires a single API key, but the default root password for new
@ -100,3 +120,34 @@ command:
uuid:
8457f92eaffc92b7666b6734a96ad7abe1a8a6dd
...SNIP...
Cloning
=======
When salt-cloud accesses Linode via linode-python it can clone machines.
It is safest to clone a stopped machine. To stop a machine run
.. code-block:: bash
salt-cloud -a stop machine_to_clone
To create a new machine based on another machine, add an entry to your linode
cloud profile that looks like this:
.. code-block:: yaml
li-clone:
provider: linode
clonefrom: machine_to_clone
script_args: -C
Then run salt-cloud as normal, specifying `-p li-clone`. The profile name can
be anything--it doesn't have to be `li-clone`.
`Clonefrom:` is the name of an existing machine in Linode from which to clone.
`Script_args: -C` is necessary to avoid re-deploying Salt via salt-bootstrap.
`-C` will just re-deploy keys so the new minion will not have a duplicate key
or minion_id on the master.

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,11 @@ from __future__ import absolute_import
# Import python libs
import logging
import os.path
# Import salt libs
import salt.utils
from salt.exceptions import CommandExecutionError
from salt.exceptions import CommandExecutionError, CommandNotFoundError, SaltInvocationError
log = logging.getLogger(__name__)
@ -35,6 +36,162 @@ def _valid_composer(composer):
return False
def did_composer_install(dir):
'''
Test to see if the vendor directory exists in this directory
dir
Directory location of the composer.json file
CLI Example:
.. code-block:: bash
salt '*' composer.did_composer_install /var/www/application
'''
lockFile = "{0}/vendor".format(dir)
if os.path.exists(lockFile):
return True
return False
def _run_composer(action,
dir=None,
composer=None,
php=None,
runas=None,
prefer_source=None,
prefer_dist=None,
no_scripts=None,
no_plugins=None,
optimize=None,
no_dev=None,
quiet=False,
composer_home='/root',
extra_flags=None):
'''
Run PHP's composer with a specific action.
If composer has not been installed globally making it available in the
system PATH & making it executable, the ``composer`` and ``php`` parameters
will need to be set to the location of the executables.
action
The action to pass to composer ('install', 'update', 'selfupdate', etc).
dir
Directory location of the composer.json file. Required except when
action='selfupdate'
composer
Location of the composer.phar file. If not set composer will
just execute "composer" as if it is installed globally.
(i.e. /path/to/composer.phar)
php
Location of the php executable to use with composer.
(i.e. /usr/bin/php)
runas
Which system user to run composer as.
prefer_source
--prefer-source option of composer.
prefer_dist
--prefer-dist option of composer.
no_scripts
--no-scripts option of composer.
no_plugins
--no-plugins option of composer.
optimize
--optimize-autoloader option of composer. Recommended for production.
no_dev
--no-dev option for composer. Recommended for production.
quiet
--quiet option for composer. Whether or not to return output from composer.
composer_home
$COMPOSER_HOME environment variable
extra_flags
None, or a string containing extra flags to pass to composer.
'''
if composer is not None:
if php is None:
php = 'php'
else:
composer = 'composer'
# Validate Composer is there
if not _valid_composer(composer):
raise CommandNotFoundError('\'composer.{0}\' is not available. Couldn\'t find {1!r}.'
.format(action, composer))
# Don't need a dir for the 'selfupdate' action; all other actions do need a dir
if dir is None and action != 'selfupdate':
raise SaltInvocationError('{0!r} is required for \'composer.{1}\''
.format('dir', action))
if action is None:
raise SaltInvocationError('{0!r} is required for {1!r}'
.format('action', 'composer._run_composer'))
# Base Settings
cmd = '{0} {1} {2}'.format(composer, action, '--no-interaction --no-ansi')
if extra_flags is not None:
cmd = '{0} {1}'.format(cmd, extra_flags)
# If php is set, prepend it
if php is not None:
cmd = php + ' ' + cmd
# Add Working Dir
if dir is not None:
cmd += ' --working-dir=' + dir
# Other Settings
if quiet is True:
cmd += ' --quiet'
if no_dev is True:
cmd += ' --no-dev'
if prefer_source is True:
cmd += ' --prefer-source'
if prefer_dist is True:
cmd += ' --prefer-dist'
if no_scripts is True:
cmd += ' --no-scripts'
if no_plugins is True:
cmd += ' --no-plugins'
if optimize is True:
cmd += ' --optimize-autoloader'
result = __salt__['cmd.run_all'](cmd,
runas=runas,
env={'COMPOSER_HOME': composer_home},
python_shell=False)
if result['retcode'] != 0:
raise CommandExecutionError(result['stderr'])
if quiet is True:
return True
return result
def install(dir,
composer=None,
php=None,
@ -102,60 +259,150 @@ def install(dir,
salt '*' composer.install /var/www/application \
no_dev=True optimize=True
'''
if composer is not None:
if php is None:
php = 'php'
else:
composer = 'composer'
result = _run_composer('install',
dir=dir,
composer=composer,
php=php,
runas=runas,
prefer_source=prefer_source,
prefer_dist=prefer_dist,
no_scripts=no_scripts,
no_plugins=no_plugins,
optimize=optimize,
no_dev=no_dev,
quiet=quiet,
composer_home=composer_home)
return result
# Validate Composer is there
if not _valid_composer(composer):
return '{0!r} is not available. Couldn\'t find {1!r}.'.format('composer.install', composer)
if dir is None:
return '{0!r} is required for {1!r}'.format('dir', 'composer.install')
def update(dir,
composer=None,
php=None,
runas=None,
prefer_source=None,
prefer_dist=None,
no_scripts=None,
no_plugins=None,
optimize=None,
no_dev=None,
quiet=False,
composer_home='/root'):
'''
Update composer dependencies for a directory.
# Base Settings
cmd = composer + ' install --no-interaction'
If `composer install` has not yet been run, this runs `composer install`
instead.
# If php is set, prepend it
if php is not None:
cmd = php + ' ' + cmd
If composer has not been installed globally making it available in the
system PATH & making it executable, the ``composer`` and ``php`` parameters
will need to be set to the location of the executables.
# Add Working Dir
cmd += ' --working-dir=' + dir
dir
Directory location of the composer.json file.
# Other Settings
if quiet is True:
cmd += ' --quiet'
composer
Location of the composer.phar file. If not set composer will
just execute "composer" as if it is installed globally.
(i.e. /path/to/composer.phar)
if no_dev is True:
cmd += ' --no-dev'
php
Location of the php executable to use with composer.
(i.e. /usr/bin/php)
if prefer_source is True:
cmd += ' --prefer-source'
runas
Which system user to run composer as.
if prefer_dist is True:
cmd += ' --prefer-dist'
prefer_source
--prefer-source option of composer.
if no_scripts is True:
cmd += ' --no-scripts'
prefer_dist
--prefer-dist option of composer.
if no_plugins is True:
cmd += ' --no-plugins'
no_scripts
--no-scripts option of composer.
if optimize is True:
cmd += ' --optimize-autoloader'
no_plugins
--no-plugins option of composer.
result = __salt__['cmd.run_all'](cmd,
runas=runas,
env={'COMPOSER_HOME': composer_home},
python_shell=False)
optimize
--optimize-autoloader option of composer. Recommended for production.
if result['retcode'] != 0:
raise CommandExecutionError(result['stderr'])
no_dev
--no-dev option for composer. Recommended for production.
if quiet is True:
return True
quiet
--quiet option for composer. Whether or not to return output from composer.
return result['stdout']
composer_home
$COMPOSER_HOME environment variable
CLI Example:
.. code-block:: bash
salt '*' composer.update /var/www/application
salt '*' composer.update /var/www/application \
no_dev=True optimize=True
'''
result = _run_composer('update',
extra_flags='--no-progress',
dir=dir,
composer=composer,
php=php,
runas=runas,
prefer_source=prefer_source,
prefer_dist=prefer_dist,
no_scripts=no_scripts,
no_plugins=no_plugins,
optimize=optimize,
no_dev=no_dev,
quiet=quiet,
composer_home=composer_home)
return result
def selfupdate(composer=None,
php=None,
runas=None,
quiet=False,
composer_home='/root'):
'''
Update composer itself.
If composer has not been installed globally making it available in the
system PATH & making it executable, the ``composer`` and ``php`` parameters
will need to be set to the location of the executables.
composer
Location of the composer.phar file. If not set composer will
just execute "composer" as if it is installed globally.
(i.e. /path/to/composer.phar)
php
Location of the php executable to use with composer.
(i.e. /usr/bin/php)
runas
Which system user to run composer as.
quiet
--quiet option for composer. Whether or not to return output from composer.
composer_home
$COMPOSER_HOME environment variable
CLI Example:
.. code-block:: bash
salt '*' composer.selfupdate
'''
result = _run_composer('selfupdate',
extra_flags='--no-progress',
composer=composer,
php=php,
runas=runas,
quiet=quiet,
composer_home=composer_home)
return result

View File

@ -303,7 +303,7 @@ def disable(name, **kwargs):
salt '*' service.disable <service name>
'''
cmd = ['sc', 'config', name, 'start=', 'demand']
cmd = ['sc', 'config', name, 'start=', 'disabled']
return not __salt__['cmd.retcode'](cmd, python_shell=False)

View File

@ -2804,7 +2804,7 @@ class BaseHighState(object):
if state:
self.merge_included_states(highstate, state, errors)
for i, error in enumerate(errors[:]):
if 'is not available on the salt master' in error:
if 'is not available' in error:
# match SLS foobar in environment
this_sls = 'SLS {0} in saltenv'.format(
sls_match)

View File

@ -40,7 +40,7 @@ the location of composer in the state.
from __future__ import absolute_import
# Import salt libs
from salt.exceptions import CommandExecutionError, CommandNotFoundError
from salt.exceptions import SaltException
def __virtual__():
@ -60,10 +60,138 @@ def installed(name,
no_plugins=None,
optimize=None,
no_dev=None,
composer_home='/root'):
quiet=False,
composer_home='/root',
always_check=True):
'''
Verify that composer has installed the latest packages give a
``composer.json`` and ``composer.lock`` file in a directory.
Verify that the correct versions of composer dependencies are present.
dir
Directory location of the composer.json file.
composer
Location of the composer.phar file. If not set composer will
just execute "composer" as if it is installed globally.
(i.e. /path/to/composer.phar)
php
Location of the php executable to use with composer.
(i.e. /usr/bin/php)
user
Which system user to run composer as.
.. versionadded:: 2014.1.4
prefer_source
--prefer-source option of composer.
prefer_dist
--prefer-dist option of composer.
no_scripts
--no-scripts option of composer.
no_plugins
--no-plugins option of composer.
optimize
--optimize-autoloader option of composer. Recommended for production.
no_dev
--no-dev option for composer. Recommended for production.
quiet
--quiet option for composer. Whether or not to return output from composer.
composer_home
$COMPOSER_HOME environment variable
always_check
If True, _always_ run `composer install` in the directory. This is the
default behavior. If False, only run `composer install` if there is no
vendor directory present.
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
did_install = __salt__['composer.did_composer_install'](name)
# Check if composer.lock exists, if so we already ran `composer install`
# and we don't need to do it again
if always_check is False and did_install:
ret['result'] = True
ret['comment'] = 'Composer already installed this directory'
return ret
# The state of the system does need to be changed. Check if we're running
# in ``test=true`` mode.
if __opts__['test'] is True:
if did_install is True:
install_status = ""
else:
install_status = "not "
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
ret['changes'] = {
'old': 'composer install has {0}been run in {1}'.format(install_status, name),
'new': 'composer install will be run in {0}'.format(name)
}
ret['result'] = None
return ret
try:
call = __salt__['composer.install'](
name,
composer=composer,
php=php,
runas=user,
prefer_source=prefer_source,
prefer_dist=prefer_dist,
no_scripts=no_scripts,
no_plugins=no_plugins,
optimize=optimize,
no_dev=no_dev,
quiet=quiet,
composer_home=composer_home
)
except (SaltException) as err:
ret['result'] = False
ret['comment'] = 'Error executing composer in \'{0!r}\': {1!r}'.format(name, err)
return ret
# If composer retcode != 0 then an exception was thrown and we dealt with it.
# Any other case is success, regardless of what composer decides to output.
ret['result'] = True
if quiet is True:
ret['comment'] = 'Composer install completed successfully, output silenced by quiet flag'
else:
ret['comment'] = 'Composer install completed successfully'
ret['changes'] = {
'stderr': call['stderr'],
'stdout': call['stdout']
}
return ret
def update(name,
composer=None,
php=None,
user=None,
prefer_source=None,
prefer_dist=None,
no_scripts=None,
no_plugins=None,
optimize=None,
no_dev=None,
quiet=False,
composer_home='/root'):
'''
Composer update the directory to ensure we have the latest versions
of all project dependencies.
dir
Directory location of the composer.json file.
@ -108,8 +236,26 @@ def installed(name,
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
# Check if composer.lock exists, if so we already ran `composer install`
is_installed = __salt__['composer.did_composer_install'](name)
if is_installed:
old_status = "composer install has not yet been run in {0}".format(name)
else:
old_status = "composer install has been run in {0}".format(name)
# The state of the system does need to be changed. Check if we're running
# in ``test=true`` mode.
if __opts__['test'] is True:
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
ret['changes'] = {
'old': old_status,
'new': 'composer install/update will be run in {0}'.format(name)
}
ret['result'] = None
return ret
try:
call = __salt__['composer.install'](
call = __salt__['composer.update'](
name,
composer=composer,
php=php,
@ -120,22 +266,26 @@ def installed(name,
no_plugins=no_plugins,
optimize=optimize,
no_dev=no_dev,
quiet=False,
quiet=quiet,
composer_home=composer_home
)
except (CommandNotFoundError, CommandExecutionError) as err:
except (SaltException) as err:
ret['result'] = False
ret['comment'] = 'Error executing composer in \'{0!r}\': {1!r}'.format(name, err)
return ret
if call or isinstance(call, list) or isinstance(call, dict):
ret['result'] = True
if call.find('Nothing to install or update') < 0:
ret['changes']['stdout'] = call
# If composer retcode != 0 then an exception was thrown and we dealt with it.
# Any other case is success, regardless of what composer decides to output.
ret['comment'] = 'Composer ran, nothing changed in {0!r}'.format(name)
ret['result'] = True
if quiet is True:
ret['comment'] = 'Composer update completed successfully, output silenced by quiet flag'
else:
ret['result'] = False
ret['comment'] = 'Could not run composer'
ret['comment'] = 'Composer update completed successfully'
ret['changes'] = {
'stderr': call['stderr'],
'stdout': call['stdout']
}
return ret

View File

@ -45,7 +45,7 @@ The following example installs all driver updates that don't require a reboot:
.. code-block:: yaml
gryffindor:
win_update.install:
win_update.installed:
- includes:
- driver: True
- software: False

View File

@ -221,7 +221,7 @@ def query(key, keyid, method='GET', params=None, headers=None,
if return_url is True:
return ret, requesturl
else:
if method == 'GET' or method == 'HEAD':
if result.status_code != requests.codes.ok:
return
ret = {'headers': []}
for header in result.headers:

View File

@ -11,6 +11,7 @@ import os
import re
import sys
import stat
import errno
import socket
import logging
@ -162,8 +163,11 @@ def verify_files(files, user):
for fn_ in files:
dirname = os.path.dirname(fn_)
try:
if not os.path.isdir(dirname):
try:
os.makedirs(dirname)
except OSError as err:
if err.errno != errno.EEXIST:
raise
if not os.path.isfile(fn_):
with salt.utils.fopen(fn_, 'w+') as fp_:
fp_.write('')

View File

@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Rupesh Tare <rupesht@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
# Import Salt Libs
from salt.modules import composer
from salt.exceptions import CommandExecutionError, CommandNotFoundError, SaltInvocationError
# Globals
composer.__grains__ = {}
composer.__salt__ = {}
composer.__context__ = {}
composer.__opts__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ComposerTestCase(TestCase):
'''
Test cases for salt.modules.composer
'''
def test_install(self):
'''
Test for Install composer dependencies for a directory.
'''
# Test _valid_composer=False throws exception
mock = MagicMock(return_value=False)
with patch.object(composer, '_valid_composer', mock):
self.assertRaises(CommandNotFoundError, composer.install, 'd')
# Test no directory specified throws exception
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
self.assertRaises(SaltInvocationError, composer.install, None)
# Test `composer install` exit status != 0 throws exception
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value={'retcode': 1, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertRaises(CommandExecutionError, composer.install, 'd')
# Test success with quiet=True returns True
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value={'retcode': 0, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertTrue(composer.install('dir', None, None, None, None,
None, None, None, None, None,
True))
# Test success with quiet=False returns object
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
rval = {'retcode': 0, 'stderr': 'A', 'stdout': 'B'}
mock = MagicMock(return_value=rval)
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertEqual(composer.install('dir'), rval)
def test_update(self):
'''
Test for Update composer dependencies for a directory.
'''
# Test _valid_composer=False throws exception
mock = MagicMock(return_value=False)
with patch.object(composer, '_valid_composer', mock):
self.assertRaises(CommandNotFoundError, composer.update, 'd')
# Test no directory specified throws exception
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=True)
with patch.object(composer, 'did_composer_install', mock):
self.assertRaises(SaltInvocationError, composer.update, None)
# Test update with error exit status throws exception
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=True)
with patch.object(composer, 'did_composer_install', mock):
mock = MagicMock(return_value={'retcode': 1, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertRaises(CommandExecutionError, composer.update, 'd')
# Test update with existing vendor directory and quiet=True
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=True)
with patch.object(composer, 'did_composer_install', mock):
mock = MagicMock(return_value={'retcode': 0, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertTrue(composer.update('dir', None, None, None, None,
None, None, None, None, None,
True))
# Test update with no vendor directory and quiet=True
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=False)
with patch.object(composer, 'did_composer_install', mock):
mock = MagicMock(return_value={'retcode': 0, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertTrue(composer.update('dir', None, None, None, None,
None, None, None, None, None,
True))
# Test update with existing vendor directory
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=True)
with patch.object(composer, 'did_composer_install', mock):
rval = {'retcode': 0, 'stderr': 'A', 'stdout': 'B'}
mock = MagicMock(return_value=rval)
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertEqual(composer.update('dir'), rval)
# Test update with no vendor directory
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value=False)
with patch.object(composer, 'did_composer_install', mock):
rval = {'retcode': 0, 'stderr': 'A', 'stdout': 'B'}
mock = MagicMock(return_value=rval)
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertEqual(composer.update('dir'), rval)
def test_selfupdate(self):
'''
Test for Composer selfupdate
'''
mock = MagicMock(return_value=False)
with patch.object(composer, '_valid_composer', mock):
self.assertRaises(CommandNotFoundError, composer.selfupdate)
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value={'retcode': 1, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertRaises(CommandExecutionError, composer.selfupdate)
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
mock = MagicMock(return_value={'retcode': 0, 'stderr': 'A'})
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertTrue(composer.selfupdate(quiet=True))
mock = MagicMock(return_value=True)
with patch.object(composer, '_valid_composer', mock):
rval = {'retcode': 0, 'stderr': 'A', 'stdout': 'B'}
mock = MagicMock(return_value=rval)
with patch.dict(composer.__salt__, {'cmd.run_all': mock}):
self.assertEqual(composer.selfupdate(), rval)
if __name__ == '__main__':
from integration import run_tests
run_tests(ComposerTestCase, needs_daemon=False)