Merge branch 'carbon' into 'develop'

Conflicts:
  - conf/master
  - doc/ref/configuration/logging/index.rst
  - doc/ref/configuration/master.rst
  - salt/config/__init__.py
  - salt/modules/gpg.py
This commit is contained in:
rallytime 2016-09-30 16:10:33 -06:00
commit 5d7b4e4511
33 changed files with 787 additions and 276 deletions

View File

@ -1022,9 +1022,18 @@
############################################
# Default match type for filtering events tags: startswith, endswith, find, regex, fnmatch
#event_match_type: startswith
# Save runner returns to the job cache
#runner_returns: True
# Permanently include any available Python 3rd party modules into thin and minimal Salt
# when they are generated for Salt-SSH or other purposes.
# The modules should be named by the names they are actually imported inside the Python.
# The value of the parameters can be either one module or a comma separated list of them.
#thin_extra_mods: foo,bar
#min_extra_mods: foo,bar,baz
###### Keepalive settings ######
############################################
# Warning: Failure to set TCP keepalives on the salt-master can result in
@ -1057,9 +1066,3 @@
# /proc/sys/net/ipv4/tcp_keepalive_intvl.
#tcp_keepalive_intvl: -1
# Permanently include any available Python 3rd party modules into thin and minimal Salt
# when they are generated for Salt-SSH or other purposes.
# The modules should be named by the names they are actually imported inside the Python.
# The value of the parameters can be either one module or a comma separated list of them.
#thin_extra_mods: foo,bar
#min_extra_mods: foo,bar,baz

View File

@ -36,22 +36,29 @@ shown in the table below.
<https://docs.python.org/2/library/multiprocessing.html#logging>`_
``subwarning``, 25 and ``subdebug``, 5.
======== ============= ========================================================================
Level Numeric value Description
======== ============= ========================================================================
quiet 1000 Nothing should be logged at this level
critical 50 Critical errors
error 40 Errors
warning 30 Warnings
info 20 Normal log information
profile 15 Profiling information on salt performance
debug 10 Information useful for debugging both salt implementations and salt code
trace 5 More detailed code debugging information
garbage 1 Even more debugging information
all 0 Everything
======== ============= ========================================================================
+----------+---------------+--------------------------------------------------------------------------+
| Level | Numeric value | Description |
+==========+===============+==========================================================================+
| quiet | 1000 | Nothing should be logged at this level |
+----------+---------------+--------------------------------------------------------------------------+
| critical | 50 | Critical errors |
+----------+---------------+--------------------------------------------------------------------------+
| error | 40 | Errors |
+----------+---------------+--------------------------------------------------------------------------+
| warning | 30 | Warnings |
+----------+---------------+--------------------------------------------------------------------------+
| info | 20 | Normal log information |
+----------+---------------+--------------------------------------------------------------------------+
| profile | 15 | Profiling information on salt performance |
+----------+---------------+--------------------------------------------------------------------------+
| debug | 10 | Information useful for debugging both salt implementations and salt code |
+----------+---------------+--------------------------------------------------------------------------+
| trace | 5 | More detailed code debugging information |
+----------+---------------+--------------------------------------------------------------------------+
| garbage | 1 | Even more debugging information |
+----------+---------------+--------------------------------------------------------------------------+
| all | 0 | Everything |
+----------+---------------+--------------------------------------------------------------------------+
Available Configuration Settings
================================

View File

@ -756,7 +756,6 @@ Pass a list of importable Python modules that are typically located in
the `site-packages` Python directory so they will be also always included
into the Salt Thin, once generated.
``min_extra_mods``
------------------
@ -764,6 +763,7 @@ Default: None
Identical as `thin_extra_mods`, only applied to the Salt Minimal.
Master Security Settings
========================

View File

@ -298,8 +298,10 @@ class Minion(parsers.MinionOptionParser, DaemonsMixin): # pylint: disable=no-in
self.action_log_info('An instance is already running. Exiting')
self.shutdown(1)
transport = self.config.get('transport').lower()
# TODO: AIO core is separate from transport
if self.config['transport'].lower() in ('zeromq', 'tcp', 'detect'):
if transport in ('zeromq', 'tcp', 'detect'):
# Late import so logging works correctly
import salt.minion
# If the minion key has not been accepted, then Salt enters a loop
@ -311,11 +313,19 @@ class Minion(parsers.MinionOptionParser, DaemonsMixin): # pylint: disable=no-in
if self.config.get('master_type') == 'func':
salt.minion.eval_master_func(self.config)
self.minion = salt.minion.MinionManager(self.config)
else:
elif transport == 'raet':
import salt.daemons.flo
self.daemonize_if_required()
self.set_pidfile()
self.minion = salt.daemons.flo.IofloMinion(self.config)
else:
log.error(
'The transport \'{0}\' is not supported. Please use one of the following: '
'tcp, '
'raet, '
'or zeromq.'.format(transport)
)
self.shutdown(1)
def start(self):
'''

View File

@ -1473,8 +1473,8 @@ CLOUD_CONFIG_DEFAULTS = {
DEFAULT_API_OPTS = {
# ----- Salt master settings overridden by Salt-API --------------------->
'api_pidfile': os.path.join(salt.syspaths.PIDFILE_DIR, 'salt-api.pid'),
'api_logfile': os.path.join(salt.syspaths.LOGS_DIR, 'api'),
'pidfile': '/var/run/salt-api.pid',
'logfile': '/var/log/salt/api',
'rest_timeout': 300,
# <---- Salt master settings overridden by Salt-API ----------------------
}
@ -3377,15 +3377,12 @@ def api_config(path):
Read in the salt master config file and add additional configs that
need to be stubbed out for salt-api
'''
# Let's grab a copy of salt's master opts
opts = client_config(path, defaults=DEFAULT_MASTER_OPTS)
# Let's grab a copy of salt's master default opts
defaults = DEFAULT_MASTER_OPTS
# Let's override them with salt-api's required defaults
api_opts = {
'log_file': opts.get('api_logfile', DEFAULT_API_OPTS['api_logfile']),
'pidfile': opts.get('api_pidfile', DEFAULT_API_OPTS['api_pidfile'])
}
opts.update(api_opts)
return opts
defaults.update(DEFAULT_API_OPTS)
return client_config(path, defaults=defaults)
def spm_config(path):

View File

@ -282,7 +282,8 @@ def latest_version(*names, **kwargs):
cmd.extend(repo)
out = __salt__['cmd.run_all'](cmd,
output_loglevel='trace',
python_shell=False)
python_shell=False,
env={'LC_ALL': 'C', 'LANG': 'C'})
candidate = ''
for line in out['stdout'].splitlines():
if 'Candidate' in line:

View File

@ -41,7 +41,7 @@ def __virtual__():
commands = ('tar', 'gzip', 'gunzip', 'zip', 'unzip', 'rar', 'unrar')
# If none of the above commands are in $PATH this module is a no-go
if not any(salt.utils.which(cmd) for cmd in commands):
return (False, 'The archive module could not be loaded: unable to find commands tar,gzip,gunzip,zip,unzip,rar,unrar')
return (False, 'Unable to find commands tar,gzip,gunzip,zip,unzip,rar,unrar')
return True
@ -135,7 +135,7 @@ def tar(options, tarfile, sources=None, dest=None,
@salt.utils.decorators.which('gzip')
def gzip(sourcefile, template=None, runas=None):
def gzip(sourcefile, template=None, runas=None, options=None):
'''
Uses the gzip command to create gzip files
@ -147,14 +147,27 @@ def gzip(sourcefile, template=None, runas=None):
salt '*' archive.gzip template=jinja /tmp/{{grains.id}}.txt
runas : None
The user with which to run the gzip command line
options : None
Pass any additional arguments to gzip
.. versionadded:: 2016.3.4
CLI Example:
.. code-block:: bash
# Create /tmp/sourcefile.txt.gz
salt '*' archive.gzip /tmp/sourcefile.txt
salt '*' archive.gzip /tmp/sourcefile.txt options='-9 --verbose'
'''
cmd = ['gzip', '{0}'.format(sourcefile)]
cmd = ['gzip']
if options:
cmd.append(options)
cmd.append('{0}'.format(sourcefile))
return __salt__['cmd.run'](cmd,
template=template,
runas=runas,
@ -162,7 +175,7 @@ def gzip(sourcefile, template=None, runas=None):
@salt.utils.decorators.which('gunzip')
def gunzip(gzipfile, template=None, runas=None):
def gunzip(gzipfile, template=None, runas=None, options=None):
'''
Uses the gunzip command to unpack gzip files
@ -174,14 +187,27 @@ def gunzip(gzipfile, template=None, runas=None):
salt '*' archive.gunzip template=jinja /tmp/{{grains.id}}.txt.gz
runas : None
The user with which to run the gzip command line
options : None
Pass any additional arguments to gzip
.. versionadded:: 2016.3.4
CLI Example:
.. code-block:: bash
# Create /tmp/sourcefile.txt
salt '*' archive.gunzip /tmp/sourcefile.txt.gz
salt '*' archive.gunzip /tmp/sourcefile.txt options='--verbose'
'''
cmd = ['gunzip', '{0}'.format(gzipfile)]
cmd = ['gunzip']
if options:
cmd.append(options)
cmd.append('{0}'.format(gzipfile))
return __salt__['cmd.run'](cmd,
template=template,
runas=runas,
@ -344,7 +370,7 @@ def zip_(zip_file, sources, template=None, cwd=None, runas=None):
try:
exc = None
archived_files = []
with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zfile:
with contextlib.closing(zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED)) as zfile:
for src in sources:
if cwd:
src = os.path.join(cwd, src)
@ -467,6 +493,7 @@ def cmd_unzip(zip_file, dest, excludes=None,
return _trim_files(files, trim_output)
@salt.utils.decorators.depends('zipfile', fallback_function=cmd_unzip)
def unzip(zip_file, dest, excludes=None, options=None, template=None,
runas=None, trim_output=False, password=None, extract_perms=False):
'''

View File

@ -21,6 +21,7 @@ import traceback
import fnmatch
import base64
import re
import tempfile
# Import salt libs
import salt.utils
@ -28,7 +29,8 @@ import salt.utils.timed_subprocess
import salt.grains.extra
import salt.ext.six as six
from salt.utils import vt
from salt.exceptions import CommandExecutionError, TimedProcTimeoutError
from salt.exceptions import CommandExecutionError, TimedProcTimeoutError, \
SaltInvocationError
from salt.log import LOG_LEVELS
from salt.ext.six.moves import range, zip
from salt.ext.six.moves import shlex_quote as _cmd_quote
@ -2041,8 +2043,8 @@ def script(source,
def _cleanup_tempfile(path):
try:
os.remove(path)
except (IOError, OSError) as exc:
__salt__['file.remove'](path)
except (SaltInvocationError, CommandExecutionError) as exc:
log.error(
'cmd.script: Unable to clean tempfile \'{0}\': {1}'.format(
path,
@ -2059,6 +2061,12 @@ def script(source,
)
kwargs.pop('__env__')
if salt.utils.is_windows() and runas and cwd is None:
cwd = tempfile.mkdtemp(dir=__opts__['cachedir'])
__salt__['win_dacl.add_ace'](
cwd, 'File', runas, 'READ&EXECUTE', 'ALLOW',
'FOLDER&SUBFOLDERS&FILES')
path = salt.utils.mkstemp(dir=cwd, suffix=os.path.splitext(source)[1])
if template:
@ -2071,7 +2079,10 @@ def script(source,
saltenv,
**kwargs)
if not fn_:
_cleanup_tempfile(path)
if salt.utils.is_windows() and runas:
_cleanup_tempfile(cwd)
else:
_cleanup_tempfile(path)
return {'pid': 0,
'retcode': 1,
'stdout': '',
@ -2080,7 +2091,10 @@ def script(source,
else:
fn_ = __salt__['cp.cache_file'](source, saltenv)
if not fn_:
_cleanup_tempfile(path)
if salt.utils.is_windows() and runas:
_cleanup_tempfile(cwd)
else:
_cleanup_tempfile(path)
return {'pid': 0,
'retcode': 1,
'stdout': '',
@ -2107,7 +2121,10 @@ def script(source,
bg=bg,
password=password,
**kwargs)
_cleanup_tempfile(path)
if salt.utils.is_windows() and runas:
_cleanup_tempfile(cwd)
else:
_cleanup_tempfile(path)
return ret
@ -2647,54 +2664,59 @@ def shells():
def shell_info(shell):
'''
Provides information about a shell or languages often use #!
The values returned are dependant on the shell or languages all return the
``installed``, ``path``, ``version``, ``version_major`` and
``version_major_minor`` e.g. 5.0 or 6-3.
The shell must be within the exeuctable search path.
Provides information about a shell or script languages which often use ``#!``.
The values returned are dependant on the shell or scripting languages all return the
``installed``, ``path``, ``version``, ``version_raw``
:param str shell: Name of the shell.
Support are bash, cmd, perl, php, powershell, python, ruby and zsh
:return: Properies of the shell specifically its and other information if
available.
:param str shell: Name of the shell. Support shells/script languages include
bash, cmd, perl, php, powershell, python, ruby and zsh
:return: Properties of the shell specifically its and other information if available.
:rtype: dict
.. code-block:: cfg
{'version': '<version string>',
'version_major': '<version string',
'version_minor': '<version string>',
.. code-block:: python
{'version': '<2 or 3 numeric components dot-separated>',
'version_raw': '<full version string>',
'path': '<full path to binary>',
'installed': <True, False or None>,
'<attribute>': '<attribute value>'}
``installed`` is always returned, if None or False also returns error and
may also return stdout for diagnostics.
..
.. versionadded:: 2016.9.0
.. note::
* ``installed`` is always returned, if ``None`` or ``False`` also returns error and may also return ``stdout`` for diagnostics.
* ``version`` is for use in determine if a shell/script language has a particular feature set, not for package management.
* The shell must be within the exeuctable search path.
.. versionadded:: Carbon
CLI Example:
CLI Example::
.. code-block:: bash
salt '*' cmd.shell bash
salt '*' cmd.shell powershell
..
:codeauthor: Damon Atkins <https://github.com/damon-atkins>
'''
regex_shells = {
'bash': [r'version ([-\w.]+)', 'bash', '--version'],
'bash': [r'version (\d\S*)', 'bash', '--version'],
'bash-test-error': [r'versioZ ([-\w.]+)', 'bash', '--version'], # used to test a error result
'bash-test-env': [r'(HOME=.*)', 'bash', '-c', 'declare'], # used to test a error result
'zsh': [r'^zsh ([\d.]+)', 'zsh', '--version'],
'tcsh': [r'^tcsh ([\d.]+)', 'tcsh', '--version'],
'zsh': [r'^zsh (\d\S*)', 'zsh', '--version'],
'tcsh': [r'^tcsh (\d\S*)', 'tcsh', '--version'],
'cmd': [r'Version ([\d.]+)', 'cmd.exe', '/C', 'ver'],
'powershell': [r'PSVersion\s+([\d.]+)', 'powershell', '-NonInteractive', '$PSVersionTable'],
'perl': [r'^([\d.]+)', 'perl', '-e', 'printf "%vd\n", $^V;'],
'python': [r'^Python ([\d.]+)', 'python', '-V'],
'ruby': [r'^ruby ([\d.]+)', 'ruby', '-v'],
'php': [r'^PHP ([\d.]+)', 'php', '-v']
'powershell': [r'PSVersion\s+(\d\S*)', 'powershell', '-NonInteractive', '$PSVersionTable'],
'perl': [r'^(\d\S*)', 'perl', '-e', 'printf "%vd\n", $^V;'],
'python': [r'^Python (\d\S*)', 'python', '-V'],
'ruby': [r'^ruby (\d\S*)', 'ruby', '-v'],
'php': [r'^PHP (\d\S*)', 'php', '-v']
}
# Ensure ret['installed'] always as a value of True, False or None (not sure)
ret = {}
ret['installed'] = None
ret['installed'] = False
if salt.utils.is_windows() and shell == 'powershell':
pw_keys = __salt__['reg.list_keys']('HKEY_LOCAL_MACHINE', 'Software\\Microsoft\\PowerShell')
pw_keys.sort(key=int)
@ -2702,28 +2724,27 @@ def shell_info(shell):
return {
'error': 'Unable to locate \'powershell\' Reason: Cannot be found in registry.',
'installed': False,
'version': None
}
for reg_ver in pw_keys:
install_data = __salt__['reg.read_value']('HKEY_LOCAL_MACHINE', 'Software\\Microsoft\\PowerShell\\{0}'.format(reg_ver), 'Install')
if 'vtype' in install_data and install_data['vtype'] == 'REG_DWORD' and install_data['vdata'] == 1:
details = __salt__['reg.list_values']('HKEY_LOCAL_MACHINE', 'Software\\Microsoft\\PowerShell\\{0}\\PowerShellEngine'.format(reg_ver))
ret = {} # reset data, want the newest version details only as powershell is backwards compatible
ret['installed'] = True
ret['installed'] = None # if all goes well this will become True
ret['path'] = which('powershell.exe')
for attribute in details:
if attribute['vname'].lower() == '(default)':
continue
elif attribute['vname'].lower() == 'powershellversion':
ret['psversion'] = attribute['vdata']
ret['version'] = attribute['vdata']
ret['version_raw'] = attribute['vdata']
elif attribute['vname'].lower() == 'runtimeversion':
ret['crlversion'] = attribute['vdata']
if ret['crlversion'][0] == 'v' or ret['crlversion'][0] == 'V':
ret['crlversion'] = ret['crlversion'][1::]
elif attribute['vname'].lower() == 'pscompatibleversion':
# reg attribute does not end in s, the powershell attibute does
ret['pscompatibleversions'] = attribute['vdata']
ret['pscompatibleversions'] = attribute['vdata'].replace(' ', '').split(',')
else:
# keys are lower case as python is case sensitive the registry is not
ret[attribute['vname'].lower()] = attribute['vdata']
@ -2756,7 +2777,6 @@ def shell_info(shell):
return {
'error': 'Unable to run command \'{0}\' Reason: {1}'.format(' '.join(shell_data), exc),
'installed': False,
'version': None
}
try:
proc.run()
@ -2764,28 +2784,29 @@ def shell_info(shell):
return {
'error': 'Unable to run command \'{0}\' Reason: Timed out.'.format(' '.join(shell_data)),
'installed': False,
'version': None
}
ret['installed'] = True
ret['path'] = which(shell_data[0])
pattern_result = re.search(pattern, proc.stdout, flags=re.IGNORECASE)
# only set version if we find it, so code later on can deal with it
if pattern_result:
ret['version'] = pattern_result.group(1)
ret['version_raw'] = pattern_result.group(1)
if 'version_raw' in ret:
version_results = re.match(r'(\d[\d.]*)', ret['version_raw'])
if version_results:
ret['installed'] = True
ver_list = version_results.group(1).split('.')[:3]
if len(ver_list) == 1:
ver_list.append('0')
ret['version'] = '.'.join(ver_list[:3])
else:
ret['installed'] = None # Have an unexpect result
if 'version' not in ret:
ret['error'] = 'The version regex pattern for shell {0}, could not find the version string'.format(shell)
ret['version'] = None
ret['stdout'] = proc.stdout # include stdout so they can see the issue
log.error(ret['error'])
else:
major_result = re.match('(\\d+)', ret['version'])
if major_result:
ret['version_major'] = major_result.group(1)
major_minor_result = re.match('(\\d+[-.]\\d+)', ret['version'])
if major_minor_result:
ret['version_major_minor'] = major_minor_result.group(1)
return ret

View File

@ -2,11 +2,14 @@
'''
Support for the Mercurial SCM
'''
# Import Python libs
from __future__ import absolute_import
import logging
# Import salt libs
from salt import utils
from salt.exceptions import CommandExecutionError
import salt.utils
log = logging.getLogger(__name__)
@ -15,7 +18,7 @@ def __virtual__():
'''
Only load if hg is installed
'''
if utils.which('hg') is None:
if salt.utils.which('hg') is None:
return (False,
'The hg execution module cannot be loaded: hg unavailable.')
else:
@ -187,7 +190,14 @@ def pull(cwd, opts=None, user=None, identity=None, repository=None):
cmd.append(opt)
if repository is not None:
cmd.append(repository)
return __salt__['cmd.run'](cmd, cwd=cwd, runas=user, python_shell=False)
ret = __salt__['cmd.run_all'](cmd, cwd=cwd, runas=user, python_shell=False)
if ret['retcode'] != 0:
raise CommandExecutionError(
'Hg command failed: {0}'.format(ret.get('stderr', ret['stdout']))
)
return ret['stdout']
def update(cwd, rev, force=False, user=None):
@ -215,7 +225,14 @@ def update(cwd, rev, force=False, user=None):
cmd = ['hg', 'update', '{0}'.format(rev)]
if force:
cmd.append('-C')
return __salt__['cmd.run'](cmd, cwd=cwd, runas=user, python_shell=False)
ret = __salt__['cmd.run_all'](cmd, cwd=cwd, runas=user, python_shell=False)
if ret['retcode'] != 0:
raise CommandExecutionError(
'Hg command failed: {0}'.format(ret.get('stderr', ret['stdout']))
)
return ret['stdout']
def clone(cwd, repository, opts=None, user=None, identity=None):
@ -251,7 +268,14 @@ def clone(cwd, repository, opts=None, user=None, identity=None):
cmd.append('{0}'.format(opt))
if identity:
cmd.extend(_ssh_flag(identity))
return __salt__['cmd.run'](cmd, runas=user, python_shell=False)
ret = __salt__['cmd.run_all'](cmd, runas=user, python_shell=False)
if ret['retcode'] != 0:
raise CommandExecutionError(
'Hg command failed: {0}'.format(ret.get('stderr', ret['stdout']))
)
return ret['stdout']
def status(cwd, opts=None, user=None):
@ -298,7 +322,7 @@ def status(cwd, opts=None, user=None):
ret[t].append(f)
return ret
if utils.is_iter(cwd):
if salt.utils.is_iter(cwd):
return dict((cwd, _status(cwd)) for cwd in cwd)
else:
return _status(cwd)

View File

@ -389,7 +389,7 @@ def lvcreate(lvname,
'monitor', 'ignoremonitoring', 'permission', 'poolmetadatasize',
'readahead', 'regionsize', 'type',
'virtualsize', 'zero')
no_parameter = ('noudevsync', 'ignoremonitoring', )
no_parameter = ('noudevsync', 'ignoremonitoring', 'thin', )
extra_arguments = []
if kwargs:

View File

@ -16,7 +16,11 @@ import salt.utils.mac_utils
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
__virtualname__ = "xattr"
__func_alias__ = {
'list_': 'list',
}
def __virtual__():
@ -28,7 +32,7 @@ def __virtual__():
return False
def list(path, hex=False):
def list_(path, **kwargs):
'''
List all of the extended attributes on the given file/directory
@ -49,7 +53,12 @@ def list(path, hex=False):
salt '*' xattr.list /path/to/file
salt '*' xattr.list /path/to/file hex=True
'''
cmd = 'xattr "{0}"'.format(path)
kwargs = salt.utils.clean_kwargs(**kwargs)
hex_ = kwargs.pop('hex', False)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cmd = ['xattr', path]
try:
ret = salt.utils.mac_utils.execute_return_result(cmd)
except CommandExecutionError as exc:
@ -63,13 +72,13 @@ def list(path, hex=False):
attrs_ids = ret.split("\n")
attrs = {}
for id in attrs_ids:
attrs[id] = read(path, id, hex)
for id_ in attrs_ids:
attrs[id_] = read(path, id_, **{'hex': hex_})
return attrs
def read(path, attribute, hex=False):
def read(path, attribute, **kwargs):
'''
Read the given attributes on the given file/directory
@ -92,11 +101,15 @@ def read(path, attribute, hex=False):
salt '*' xattr.read /path/to/file com.test.attr
salt '*' xattr.read /path/to/file com.test.attr hex=True
'''
hex_flag = ""
if hex:
hex_flag = "-x"
kwargs = salt.utils.clean_kwargs(**kwargs)
hex_ = kwargs.pop('hex', False)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cmd = 'xattr -p {0} "{1}" "{2}"'.format(hex_flag, attribute, path)
cmd = ['xattr', '-p']
if hex_:
cmd.append('-x')
cmd.extend([attribute, path])
try:
ret = salt.utils.mac_utils.execute_return_result(cmd)
@ -110,7 +123,7 @@ def read(path, attribute, hex=False):
return ret
def write(path, attribute, value, hex=False):
def write(path, attribute, value, **kwargs):
'''
Causes the given attribute name to be assigned the given value
@ -134,11 +147,16 @@ def write(path, attribute, value, hex=False):
salt '*' xattr.write /path/to/file "com.test.attr" "value"
'''
hex_flag = ""
if hex:
hex_flag = "-x"
kwargs = salt.utils.clean_kwargs(**kwargs)
hex_ = kwargs.pop('hex', False)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cmd = ['xattr', '-w']
if hex_:
cmd.append('-x')
cmd.extend([attribute, value, path])
cmd = 'xattr -w {0} "{1}" "{2}" "{3}"'.format(hex_flag, attribute, value, path)
try:
salt.utils.mac_utils.execute_return_success(cmd)
except CommandExecutionError as exc:
@ -146,7 +164,7 @@ def write(path, attribute, value, hex=False):
raise CommandExecutionError('File not found: {0}'.format(path))
raise CommandExecutionError('Unknown Error: {0}'.format(exc.strerror))
return read(path, attribute, hex) == value
return read(path, attribute, **{'hex': hex_}) == value
def delete(path, attribute):
@ -180,7 +198,7 @@ def delete(path, attribute):
raise CommandExecutionError('Attribute not found: {0}'.format(attribute))
raise CommandExecutionError('Unknown Error: {0}'.format(exc.strerror))
return attribute not in list(path)
return attribute not in list_(path)
def clear(path):
@ -207,4 +225,4 @@ def clear(path):
raise CommandExecutionError('File not found: {0}'.format(path))
raise CommandExecutionError('Unknown Error: {0}'.format(exc.strerror))
return list(path) == {}
return list_(path) == {}

View File

@ -71,6 +71,23 @@ def _canonical_unit_name(name):
return '%s.service' % name
def _check_available(name):
'''
Returns boolean telling whether or not the named service is available
'''
out = _systemctl_status(name).lower()
for line in salt.utils.itertools.split(out, '\n'):
match = re.match(r'\s+loaded:\s+(\S+)', line)
if match:
ret = match.group(1) != 'not-found'
break
else:
raise CommandExecutionError(
'Failed to get information on unit \'%s\'' % name
)
return ret
def _check_for_unit_changes(name):
'''
Check for modified/updated unit files, and run a daemon-reload if any are
@ -298,7 +315,7 @@ def _untracked_custom_unit_found(name):
'''
unit_path = os.path.join('/etc/systemd/system',
_canonical_unit_name(name))
return os.access(unit_path, os.R_OK) and not available(name)
return os.access(unit_path, os.R_OK) and not _check_available(name)
def _unit_file_changed(name):
@ -520,17 +537,8 @@ def available(name):
salt '*' service.available sshd
'''
out = _systemctl_status(name).lower()
for line in salt.utils.itertools.split(out, '\n'):
match = re.match(r'\s+loaded:\s+(\S+)', line)
if match:
ret = match.group(1) != 'not-found'
break
else:
raise CommandExecutionError(
'Failed to get information on unit \'%s\'' % name
)
return ret
_check_for_unit_changes(name)
return _check_available(name)
def missing(name):

View File

@ -36,14 +36,13 @@ def __virtual__():
powershell_info = __salt__['cmd.shell_info']('powershell')
if (
powershell_info['installed'] and
('version_major' in powershell_info) and
(distutils.version.LooseVersion(powershell_info['version_major']) < distutils.version.LooseVersion('5'))
powershell_info['installed'] and
(distutils.version.StrictVersion(powershell_info['version']) >= distutils.version.StrictVersion('5.0'))
):
return __virtualname__
else:
return (False, 'Module DSC: Module only works with PowerShell 5 or newer.')
return __virtualname__
def _pshell(cmd, cwd=None, json_depth=2):
'''
@ -122,9 +121,9 @@ def psversion():
'replaced by \'cmd.shell_info\'.'
)
powershell_info = __salt__['cmd.shell_info']('powershell')
if powershell_info['installed'] and 'version_major' in powershell_info:
if powershell_info['installed']:
try:
return int(powershell_info['version_major'])
return int(powershell_info['version'].split('.')[0])
except ValueError:
pass
return 0

View File

@ -37,13 +37,12 @@ def __virtual__():
powershell_info = __salt__['cmd.shell_info']('powershell')
if (
powershell_info['installed'] and
('version_major' in powershell_info) and
(distutils.version.LooseVersion(powershell_info['version_major']) < distutils.version.LooseVersion('5'))
(distutils.version.StrictVersion(powershell_info['version']) >= distutils.version.StrictVersion('5.0'))
):
return __virtualname__
else:
return (False, 'Module PSGet: Module only works with PowerShell 5 or newer.')
return __virtualname__
def _pshell(cmd, cwd=None, json_depth=2):
'''
@ -89,9 +88,9 @@ def psversion():
'replaced by \'cmd.shell_info\'.'
)
powershell_info = __salt__['cmd.shell_info']('powershell')
if powershell_info['installed'] and 'version_major' in powershell_info:
if powershell_info['installed']:
try:
return int(powershell_info['version_major'])
return int(powershell_info['version'].split('.')[0])
except ValueError:
pass
return 0

View File

@ -3123,7 +3123,8 @@ def replace(name,
prepend_if_not_found=False,
not_found_content=None,
backup='.bak',
show_changes=True):
show_changes=True,
ignore_if_missing=False):
r'''
Maintain an edit in a file.
@ -3205,6 +3206,13 @@ def replace(name,
diff. This may not normally be a concern, but could impact
performance if used with large files.
ignore_if_missing : False
.. versionadded:: 2016.3.5
Controls what to do if the file is missing. If set to ``False``, the
state will display an error raised by the execution module. If set to
``True``, the state will simply report no changes.
For complex regex patterns, it can be useful to avoid the need for complex
quoting and escape sequences by making use of YAML's multiline string
syntax.
@ -3248,7 +3256,8 @@ def replace(name,
not_found_content=not_found_content,
backup=backup,
dry_run=__opts__['test'],
show_changes=show_changes)
show_changes=show_changes,
ignore_if_missing=ignore_if_missing)
if changes:
ret['pchanges']['diff'] = changes

View File

@ -13,15 +13,16 @@ in ~/.ssh/known_hosts, and the remote host has this host's public key.
- rev: tip
- target: /tmp/example_repo
'''
from __future__ import absolute_import
# Import python libs
from __future__ import absolute_import
import logging
import os
import shutil
# Import salt libs
import salt.utils
from salt.exceptions import CommandExecutionError
from salt.states.git import _fail, _neutral_test
log = logging.getLogger(__name__)
@ -130,12 +131,27 @@ def _update_repo(ret, name, target, clean, user, identity, rev, opts):
ret,
test_result)
pull_out = __salt__['hg.pull'](target, user=user, identity=identity, opts=opts, repository=name)
try:
pull_out = __salt__['hg.pull'](target, user=user, identity=identity, opts=opts, repository=name)
except CommandExecutionError as err:
ret['result'] = False
ret['comment'] = err
return ret
if rev:
__salt__['hg.update'](target, rev, force=clean, user=user)
try:
__salt__['hg.update'](target, rev, force=clean, user=user)
except CommandExecutionError as err:
ret['result'] = False
ret['comment'] = err
return ret
else:
__salt__['hg.update'](target, 'tip', force=clean, user=user)
try:
__salt__['hg.update'](target, 'tip', force=clean, user=user)
except CommandExecutionError as err:
ret['result'] = False
ret['comment'] = err
return ret
new_rev = __salt__['hg.revision'](cwd=target, user=user, rev='.')
@ -172,13 +188,23 @@ def _handle_existing(ret, target, force):
def _clone_repo(ret, target, name, user, identity, rev, opts):
result = __salt__['hg.clone'](target, name, user=user, identity=identity, opts=opts)
try:
result = __salt__['hg.clone'](target, name, user=user, identity=identity, opts=opts)
except CommandExecutionError as err:
ret['result'] = False
ret['comment'] = err
return ret
if not os.path.isdir(target):
return _fail(ret, result)
if rev:
__salt__['hg.update'](target, rev, user=user)
try:
__salt__['hg.update'](target, rev, user=user)
except CommandExecutionError as err:
ret['result'] = False
ret['comment'] = err
return ret
new_rev = __salt__['hg.revision'](cwd=target, user=user)
message = 'Repository {0} cloned to {1}'.format(name, target)

View File

@ -110,6 +110,9 @@ def _enable(name, started, result=True, **kwargs):
ret['comment'] = exc.strerror
return ret
# Set default expected result
ret['result'] = result
# Check to see if this minion supports enable
if 'service.enable' not in __salt__ or 'service.enabled' not in __salt__:
if started is True:
@ -148,10 +151,9 @@ def _enable(name, started, result=True, **kwargs):
return ret
if __salt__['service.enable'](name, **kwargs):
after_toggle_enable_status = __salt__['service.enabled'](name,
**kwargs)
# Service has been enabled
ret['changes'] = {}
after_toggle_enable_status = __salt__['service.enabled'](name, **kwargs)
# on upstart, certain services like apparmor will always return
# False, even if correctly activated
# do not trigger a change
@ -169,16 +171,14 @@ def _enable(name, started, result=True, **kwargs):
return ret
# Service failed to be enabled
ret['result'] = False
if started is True:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,'
' but the service is running').format(name)
elif started is None:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,'
' but the service was already running').format(name)
else:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,'
' and the service is dead').format(name)
return ret
@ -200,6 +200,9 @@ def _disable(name, started, result=True, **kwargs):
ret['comment'] = exc.strerror
return ret
# Set default expected result
ret['result'] = result
# is enable/disable available?
if 'service.disable' not in __salt__ or 'service.disabled' not in __salt__:
if started is True:
@ -214,8 +217,8 @@ def _disable(name, started, result=True, **kwargs):
' service {0} is dead').format(name)
return ret
before_toggle_disable_status = __salt__['service.disabled'](name)
# Service can be disabled
before_toggle_disable_status = __salt__['service.disabled'](name)
if before_toggle_disable_status:
# Service is disabled
if started is True:
@ -241,8 +244,6 @@ def _disable(name, started, result=True, **kwargs):
# Service has been disabled
ret['changes'] = {}
after_toggle_disable_status = __salt__['service.disabled'](name)
# Service has been disabled
ret['changes'] = {}
# on upstart, certain services like apparmor will always return
# False, even if correctly activated
# do not trigger a change
@ -268,7 +269,6 @@ def _disable(name, started, result=True, **kwargs):
ret['comment'] = ('Failed when setting service {0} to not start'
' at boot, but the service was already running'
).format(name)
return ret
else:
ret['comment'] = ('Failed when setting service {0} to not start'
' at boot, and the service is dead').format(name)
@ -373,12 +373,10 @@ def running(name, enable=None, sig=None, init_delay=None, **kwargs):
ret.update(_enable(name, False, result=False, **kwargs))
elif enable is False:
ret.update(_disable(name, False, result=False, **kwargs))
else:
ret['comment'] = 'Started Service {0}'.format(name)
if enable is True:
ret.update(_enable(name, True, **kwargs))
elif enable is False:
ret.update(_disable(name, True, **kwargs))
return ret
if init_delay:
time.sleep(init_delay)
# only force a change state if we have explicitly detected them
after_toggle_status = __salt__['service.status'](name)
@ -387,17 +385,27 @@ def running(name, enable=None, sig=None, init_delay=None, **kwargs):
else:
after_toggle_enable_status = True
if (
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
) and not ret.get('changes', {}):
ret['changes'][name] = func_ret
ret['changes'][name] = after_toggle_status
if after_toggle_status:
ret['comment'] = 'Started Service {0}'.format(name)
else:
ret['comment'] = 'Service {0} failed to start'.format(name)
if enable is True:
ret.update(_enable(name, after_toggle_status, result=after_toggle_status, **kwargs))
elif enable is False:
ret.update(_disable(name, after_toggle_status, result=after_toggle_status, **kwargs))
if init_delay:
time.sleep(init_delay)
ret['comment'] = (
'{0}\nDelayed return for {1} seconds'
.format(ret['comment'], init_delay)
)
return ret
@ -428,7 +436,6 @@ def dead(name, enable=None, sig=None, **kwargs):
# Check if the service is available
try:
if not _available(name, ret):
ret['result'] = True
return ret
except CommandExecutionError as exc:
ret['result'] = False
@ -442,6 +449,8 @@ def dead(name, enable=None, sig=None, **kwargs):
before_toggle_enable_status = __salt__['service.enabled'](name)
else:
before_toggle_enable_status = True
# See if the service is already dead
if not before_toggle_status:
ret['comment'] = 'The service {0} is already dead'.format(name)
if enable is True and not before_toggle_enable_status:
@ -450,12 +459,12 @@ def dead(name, enable=None, sig=None, **kwargs):
ret.update(_disable(name, None, **kwargs))
return ret
# Run the tests
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Service {0} is set to be killed'.format(name)
return ret
# be sure to stop, in case we mis detected in the check
func_ret = __salt__['service.stop'](name)
if not func_ret:
ret['result'] = False
@ -464,12 +473,8 @@ def dead(name, enable=None, sig=None, **kwargs):
ret.update(_enable(name, True, result=False, **kwargs))
elif enable is False:
ret.update(_disable(name, True, result=False, **kwargs))
else:
ret['comment'] = 'Service {0} was killed'.format(name)
if enable is True:
ret.update(_enable(name, False, **kwargs))
elif enable is False:
ret.update(_disable(name, False, **kwargs))
return ret
# only force a change state if we have explicitly detected them
after_toggle_status = __salt__['service.status'](name)
if 'service.enabled' in __salt__:
@ -477,10 +482,23 @@ def dead(name, enable=None, sig=None, **kwargs):
else:
after_toggle_enable_status = True
if (
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
) and not ret.get('changes', {}):
ret['changes'][name] = func_ret
ret['changes'][name] = after_toggle_status
# be sure to stop, in case we mis detected in the check
if after_toggle_status:
ret['result'] = False
ret['comment'] = 'Service {0} failed to die'.format(name)
else:
ret['comment'] = 'Service {0} was killed'.format(name)
if enable is True:
ret.update(_enable(name, after_toggle_status, result=not after_toggle_status, **kwargs))
elif enable is False:
ret.update(_disable(name, after_toggle_status, result=not after_toggle_status, **kwargs))
return ret

View File

@ -674,6 +674,17 @@ class GitProvider(object):
'''
raise NotImplementedError()
def get_checkout_target(self):
'''
Resolve dynamically-set branch
'''
if self.branch == '__env__':
target = self.opts.get('environment') or 'base'
return self.opts['{0}_base'.format(self.role)] \
if target == 'base' \
else target
return self.branch
def get_tree(self, tgt_env):
'''
This function must be overridden in a sub-class
@ -727,6 +738,7 @@ class GitPython(GitProvider):
GitPython when running these functions vary in different versions of
GitPython.
'''
tgt_ref = self.get_checkout_target()
try:
head_sha = self.repo.rev_parse('HEAD').hexsha
except Exception:
@ -734,11 +746,11 @@ class GitPython(GitProvider):
# we fetch first before ever checking anything out.
head_sha = None
# 'origin/' + self.branch ==> matches a branch head
# 'tags/' + self.branch + '@{commit}' ==> matches tag's commit
# 'origin/' + tgt_ref ==> matches a branch head
# 'tags/' + tgt_ref + '@{commit}' ==> matches tag's commit
for rev_parse_target, checkout_ref in (
('origin/' + self.branch, 'origin/' + self.branch),
('tags/' + self.branch + '@{commit}', 'tags/' + self.branch)):
('origin/' + tgt_ref, 'origin/' + tgt_ref),
('tags/' + tgt_ref, 'tags/' + tgt_ref)):
try:
target_sha = self.repo.rev_parse(rev_parse_target).hexsha
except Exception:
@ -782,7 +794,7 @@ class GitPython(GitProvider):
return self.check_root()
log.error(
'Failed to checkout %s from %s remote \'%s\': remote ref does '
'not exist', self.branch, self.role, self.id
'not exist', tgt_ref, self.role, self.id
)
return None
@ -1038,9 +1050,10 @@ class Pygit2(GitProvider):
'''
Checkout the configured branch/tag
'''
local_ref = 'refs/heads/' + self.branch
remote_ref = 'refs/remotes/origin/' + self.branch
tag_ref = 'refs/tags/' + self.branch
tgt_ref = self.get_checkout_target()
local_ref = 'refs/heads/' + tgt_ref
remote_ref = 'refs/remotes/origin/' + tgt_ref
tag_ref = 'refs/tags/' + tgt_ref
try:
local_head = self.repo.lookup_reference('HEAD')
@ -1207,7 +1220,7 @@ class Pygit2(GitProvider):
except Exception as exc:
log.error(
'Failed to checkout {0} from {1} remote \'{2}\': {3}'.format(
self.branch,
tgt_ref,
self.role,
self.id,
exc
@ -1217,7 +1230,7 @@ class Pygit2(GitProvider):
return None
log.error(
'Failed to checkout {0} from {1} remote \'{2}\': remote ref '
'does not exist'.format(self.branch, self.role, self.id)
'does not exist'.format(tgt_ref, self.role, self.id)
)
return None

View File

@ -378,8 +378,8 @@ class SerializerExtension(Extension, object):
def format_yaml(self, value, flow_style=True):
yaml_txt = yaml.dump(value, default_flow_style=flow_style,
Dumper=OrderedDictDumper).strip()
if yaml_txt.endswith('\n...\n'):
yaml_txt = yaml_txt[:len(yaml_txt-5)]
if yaml_txt.endswith('\n...'):
yaml_txt = yaml_txt[:len(yaml_txt)-4]
return Markup(yaml_txt)
def format_yaml_safe(self, value, flow_style=True):

View File

@ -576,7 +576,7 @@ class CkMinions(object):
# Add in possible ip addresses of a locally connected minion
addrs.discard('127.0.0.1')
addrs.discard('0.0.0.0')
addrs.update(set(salt.utils.network.ip_addrs()))
addrs.update(set(salt.utils.network.ip_addrs(include_loopback=include_localhost)))
if subset:
search = subset
for id_ in search:

View File

@ -2822,6 +2822,12 @@ class SaltSSHOptionParser(six.with_metaclass(OptionParserMeta,
dest='min_extra_mods', default=None,
help='One or comma-separated list of extra Python modules'
'to be included into Minimal Salt.')
self.add_option(
'--thin-extra-modules',
dest='thin_extra_mods',
default=None,
help='One or comma-separated list of extra Python modules'
'to be included into Thin Salt.')
self.add_option(
'-v', '--verbose',
default=False,

View File

@ -70,7 +70,7 @@ class GrainsTargetingTest(integration.ShellCase):
# ping disconnected minion and ensure it times out and returns with correct message
try:
ret = ''
for item in self.run_salt('-G \'id:disconnected\' test.ping', timeout=40):
for item in self.run_salt('-t 1 -G \'id:disconnected\' test.ping', timeout=40):
if item != 'disconnected:':
ret = item.strip()
self.assertEqual(ret, test_ret)

View File

@ -1,13 +1,9 @@
{{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}:
virtualenv:
- managed
- system_site_packages: False
- distribute: True
required_state:
test:
- succeed_without_changes
pep8-pip:
pip:
- installed
- name: pep8
- bin_env: {{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}
requiring_state:
test:
- succeed_without_changes
- require:
- virtualenv: {{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}
- test: required_state

View File

@ -1,11 +1,6 @@
{{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}:
virtualenv.managed:
- system_site_packages: False
- distribute: True
required_state: test.succeed_without_changes
pep8-pip:
pip.installed:
- name: pep8
- bin_env: {{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}
requiring_state:
test.succeed_without_changes:
- require:
- virtualenv: {{ salt['runtests_helpers.get_sys_temp_dir_for_path']('issue-2068-template-str') }}
- test: required_state

View File

@ -0,0 +1,269 @@
# -*- coding: utf-8 -*-
'''
Tests for the archive state
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
import textwrap
# Import Salt Testing libs
from salttesting import skipIf
from salttesting.helpers import (
destructiveTest,
ensure_in_syspath
)
ensure_in_syspath('../../')
# Import salt libs
import integration
import salt.utils
# Import 3rd party libs
try:
import zipfile # pylint: disable=W0611
HAS_ZIPFILE = True
except ImportError:
HAS_ZIPFILE = False
@destructiveTest
class ArchiveTest(integration.ModuleCase):
'''
Validate the archive module
'''
# Base path used for test artifacts
base_path = os.path.join(integration.TMP, 'modules', 'archive')
def _set_artifact_paths(self, arch_fmt):
'''
Define the paths for the source, archive, and destination files
:param str arch_fmt: The archive format used in the test
'''
self.src = os.path.join(self.base_path, '{0}_src_dir'.format(arch_fmt))
self.src_file = os.path.join(self.src, 'file')
self.arch = os.path.join(self.base_path, 'archive.{0}'.format(arch_fmt))
self.dst = os.path.join(self.base_path, '{0}_dst_dir'.format(arch_fmt))
def _set_up(self, arch_fmt):
'''
Create source file tree and destination directory
:param str arch_fmt: The archive format used in the test
'''
# Setup artifact paths
self._set_artifact_paths(arch_fmt)
# Remove the artifacts if any present
if any([os.path.exists(f) for f in (self.src, self.arch, self.dst)]):
self._tear_down()
# Create source
os.makedirs(self.src)
with salt.utils.fopen(os.path.join(self.src, 'file'), 'w') as theorem:
theorem.write(textwrap.dedent(r'''\
Compression theorem of computational complexity theory:
Given a Gödel numbering $φ$ of the computable functions and a
Blum complexity measure $Φ$ where a complexity class for a
boundary function $f$ is defined as
$\mathrm C(f) := \{φ_i \mathbb R^{(1)} | (^ x) Φ_i(x) f(x)\}$.
Then there exists a total computable function $f$ so that for
all $i$
$\mathrm{Dom}(φ_i) = \mathrm{Dom}(φ_{f(i)})$
and
$\mathrm C(φ_i) \mathrm{C}(φ_{f(i)})$.
'''))
# Create destination
os.makedirs(self.dst)
def _tear_down(self):
'''
Remove source file tree, archive, and destination file tree
'''
for f in (self.src, self.arch, self.dst):
if os.path.exists(f):
if os.path.isdir(f):
shutil.rmtree(f, ignore_errors=True)
else:
os.remove(f)
def _assert_artifacts_in_ret(self, ret, file_only=False):
'''
Assert that the artifact source files are printed in the source command
output
'''
# Try to find source directory and file in output lines
dir_in_ret = None
file_in_ret = None
for line in ret:
if self.src.lstrip('/') in line \
and not self.src_file.lstrip('/') in line:
dir_in_ret = True
if self.src_file.lstrip('/') in line:
file_in_ret = True
# Assert number of lines, reporting of source directory and file
self.assertTrue(len(ret) >= 1 if file_only else 2)
if not file_only:
self.assertTrue(dir_in_ret)
self.assertTrue(file_in_ret)
@skipIf(not salt.utils.which('tar'), 'Cannot find tar executable')
def test_tar_pack(self):
'''
Validate using the tar function to create archives
'''
self._set_up(arch_fmt='tar')
# Test create archive
ret = self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not salt.utils.which('tar'), 'Cannot find tar executable')
def test_tar_unpack(self):
'''
Validate using the tar function to extract archives
'''
self._set_up(arch_fmt='tar')
self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)
# Test extract archive
ret = self.run_function('archive.tar', ['-xvf', self.arch], dest=self.dst)
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not salt.utils.which('gzip'), 'Cannot find gzip executable')
def test_gzip(self):
'''
Validate using the gzip function
'''
self._set_up(arch_fmt='gz')
# Test create archive
ret = self.run_function('archive.gzip', [self.src_file], options='-v')
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret, file_only=True)
self._tear_down()
@skipIf(not salt.utils.which('gunzip'), 'Cannot find gunzip executable')
def test_gunzip(self):
'''
Validate using the gunzip function
'''
self._set_up(arch_fmt='gz')
self.run_function('archive.gzip', [self.src_file], options='-v')
# Test extract archive
ret = self.run_function('archive.gunzip', [self.src_file + '.gz'], options='-v')
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret, file_only=True)
self._tear_down()
@skipIf(not salt.utils.which('zip'), 'Cannot find zip executable')
def test_cmd_zip(self):
'''
Validate using the cmd_zip function
'''
self._set_up(arch_fmt='zip')
# Test create archive
ret = self.run_function('archive.cmd_zip', [self.arch, self.src])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not salt.utils.which('zip'), 'Cannot find zip executable')
@skipIf(not salt.utils.which('unzip'), 'Cannot find unzip executable')
def test_cmd_unzip(self):
'''
Validate using the cmd_unzip function
'''
self._set_up(arch_fmt='zip')
self.run_function('archive.cmd_zip', [self.arch, self.src])
# Test create archive
ret = self.run_function('archive.cmd_unzip', [self.arch, self.dst])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not HAS_ZIPFILE, 'Cannot find zip python module')
def test_zip(self):
'''
Validate using the zip function
'''
self._set_up(arch_fmt='zip')
# Test create archive
ret = self.run_function('archive.zip', [self.arch, self.src])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not HAS_ZIPFILE, 'Cannot find zip python module')
def test_unzip(self):
'''
Validate using the unzip function
'''
self._set_up(arch_fmt='zip')
self.run_function('archive.zip', [self.arch, self.src])
# Test create archive
ret = self.run_function('archive.unzip', [self.arch, self.dst])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not salt.utils.which('rar'), 'Cannot find rar executable')
def test_rar(self):
'''
Validate using the rar function
'''
self._set_up(arch_fmt='rar')
# Test create archive
ret = self.run_function('archive.rar', [self.arch, self.src])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
@skipIf(not salt.utils.which_bin(('rar', 'unrar')), 'Cannot find rar or unrar executable')
def test_unrar(self):
'''
Validate using the unrar function
'''
self._set_up(arch_fmt='rar')
self.run_function('archive.rar', [self.arch, self.src])
# Test create archive
ret = self.run_function('archive.unrar', [self.arch, self.dst])
self.assertTrue(isinstance(ret, list))
self._assert_artifacts_in_ret(ret)
self._tear_down()
if __name__ == '__main__':
from integration import run_tests
run_tests(ArchiveTest)

View File

@ -41,6 +41,12 @@ class PipModuleTest(integration.ModuleCase):
os.makedirs(self.pip_temp)
os.environ['PIP_SOURCE_DIR'] = os.environ['PIP_BUILD_DIR'] = ''
def _check_download_error(self, ret):
'''
Checks to see if a download error looks transitory
'''
return any(w in ret for w in ['URLError'])
def pip_successful_install(self, target, expect=('flake8', 'pep8',)):
'''
isolate regex for extracting `successful install` message from pip
@ -301,6 +307,8 @@ class PipModuleTest(integration.ModuleCase):
'pip.install', requirements=req1_filename, user=this_user,
no_chown=True, bin_env=self.venv_dir
)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
try:
self.assertEqual(ret['retcode'], 0)
self.assertIn('installed pep8', ret['stdout'])
@ -313,6 +321,8 @@ class PipModuleTest(integration.ModuleCase):
# Let's create the testing virtualenv
self.run_function('virtualenv.create', [self.venv_dir])
ret = self.run_function('pip.install', ['pep8'], bin_env=self.venv_dir)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
self.assertEqual(ret['retcode'], 0)
self.assertIn('installed pep8', ret['stdout'])
ret = self.run_function(
@ -332,6 +342,8 @@ class PipModuleTest(integration.ModuleCase):
ret = self.run_function(
'pip.install', ['pep8==1.3.4'], bin_env=self.venv_dir
)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
try:
self.assertEqual(ret['retcode'], 0)
self.assertIn('installed pep8', ret['stdout'])
@ -346,7 +358,8 @@ class PipModuleTest(integration.ModuleCase):
bin_env=self.venv_dir,
upgrade=True
)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
try:
self.assertEqual(ret['retcode'], 0)
self.assertIn('installed pep8', ret['stdout'])
@ -380,6 +393,8 @@ class PipModuleTest(integration.ModuleCase):
editable='{0}'.format(','.join(editables)),
bin_env=self.venv_dir
)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
try:
self.assertEqual(ret['retcode'], 0)
self.assertIn(
@ -403,6 +418,8 @@ class PipModuleTest(integration.ModuleCase):
editable='{0}'.format(','.join(editables)),
bin_env=self.venv_dir
)
if self._check_download_error(ret['stdout']):
self.skipTest('Test skipped due to pip download error')
try:
self.assertEqual(ret['retcode'], 0)
for package in ('Blinker', 'SaltTesting', 'pep8'):

View File

@ -38,6 +38,14 @@ class PkgModuleTest(integration.ModuleCase,
eq = ['0.2.4-0ubuntu1', '0.2.4-0ubuntu1']
gt = ['0.2.4.1-0ubuntu1', '0.2.4-0ubuntu1']
self.assertEqual(self.run_function(func, lt), -1)
self.assertEqual(self.run_function(func, eq), 0)
self.assertEqual(self.run_function(func, gt), 1)
elif os_family == 'Suse':
lt = ['2.3.0-1', '2.3.1-15.1']
eq = ['2.3.1-15.1', '2.3.1-15.1']
gt = ['2.3.2-15.1', '2.3.1-15.1']
self.assertEqual(self.run_function(func, lt), -1)
self.assertEqual(self.run_function(func, eq), 0)
self.assertEqual(self.run_function(func, gt), 1)
@ -59,7 +67,6 @@ class PkgModuleTest(integration.ModuleCase,
uri = 'http://ppa.launchpad.net/otto-kesselgulasch/gimp-edge/ubuntu'
ret = self.run_function('pkg.mod_repo', [repo, 'comps=main'])
self.assertNotEqual(ret, {})
self.assertIn(repo, ret)
ret = self.run_function('pkg.get_repo', [repo])
self.assertEqual(ret['uri'], uri)
elif os_grain == 'CentOS':
@ -191,6 +198,11 @@ class PkgModuleTest(integration.ModuleCase,
if os_family == 'RedHat':
ret = self.run_function(func)
self.assertIn(ret, (True, None))
elif os_family == 'Suse':
ret = self.run_function(func)
if not isinstance(ret, dict):
self.skipTest('Upstream repo did not return coherent results. Skipping test.')
self.assertNotEqual(ret, {})
elif os_family == 'Debian':
ret = self.run_function(func)
if not isinstance(ret, dict):
@ -228,6 +240,50 @@ class PkgModuleTest(integration.ModuleCase,
self.assertIn('less', keys)
self.assertIn('zypper', keys)
@requires_network()
@destructiveTest
def test_pkg_upgrade_has_pending_upgrades(self):
'''
Test running a system upgrade when there are packages that need upgrading
'''
func = 'pkg.upgrade'
os_family = self.run_function('grains.item', ['os_family'])['os_family']
# First make sure that an up-to-date copy of the package db is available
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'])
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_dict = self.run_function('pkg.info_available', ['vim'])
vim_version = 'version=<'+vim_version_dict['vim']['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
# Install a version of vim that should need upgrading
ret = self.run_function('pkg.install', ['vim', vim_version])
# Run a system upgrade, which should catch the fact that Vim needs upgrading, and upgrade it.
ret = self.run_function(func)
# The changes dictionary should not be empty.
self.assertIn('changes', ret)
self.assertIn('vim', ret['changes'])
else:
ret = self.run_function('pkg.list_updates')
if ret == '':
self.skipTest('No updates available for this machine. Skipping pkg.upgrade test.')
else:
ret = self.run_function(func)
# The changes dictionary should not be empty.
self.assertNotEqual(ret, {})
if __name__ == '__main__':
from integration import run_tests

View File

@ -352,38 +352,22 @@ class StateModuleTest(integration.ModuleCase,
with salt.utils.fopen(template_path, 'r') as fp_:
template = fp_.read()
try:
ret = self.run_function(
'state.template_str', [template], timeout=120
)
self.assertSaltTrueReturn(ret)
self.assertTrue(
os.path.isfile(os.path.join(venv_dir, 'bin', 'pep8'))
)
finally:
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
# Now using state.template
try:
ret = self.run_function(
'state.template', [template_path], timeout=120
)
self.assertSaltTrueReturn(ret)
finally:
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
ret = self.run_function(
'state.template', [template_path], timeout=120
)
self.assertSaltTrueReturn(ret)
# Now the problematic #2068 including dot's
try:
ret = self.run_function(
'state.sls', mods='issue-2068-template-str', timeout=120
)
self.assertSaltTrueReturn(ret)
finally:
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
ret = self.run_function(
'state.sls', mods='issue-2068-template-str', timeout=120
)
self.assertSaltTrueReturn(ret)
# Let's load the template from the filesystem. If running this state
# with state.sls works, so should using state.template_str
@ -394,28 +378,16 @@ class StateModuleTest(integration.ModuleCase,
with salt.utils.fopen(template_path, 'r') as fp_:
template = fp_.read()
try:
ret = self.run_function(
'state.template_str', [template], timeout=120
)
self.assertSaltTrueReturn(ret)
self.assertTrue(
os.path.isfile(os.path.join(venv_dir, 'bin', 'pep8'))
)
finally:
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
ret = self.run_function(
'state.template_str', [template], timeout=120
)
self.assertSaltTrueReturn(ret)
# Now using state.template
try:
ret = self.run_function(
'state.template', [template_path], timeout=120
)
self.assertSaltTrueReturn(ret)
finally:
if os.path.isdir(venv_dir):
shutil.rmtree(venv_dir)
ret = self.run_function(
'state.template', [template_path], timeout=120
)
self.assertSaltTrueReturn(ret)
def test_template_invalid_items(self):
TEMPLATE = textwrap.dedent('''\

View File

@ -424,8 +424,6 @@ class CallTest(integration.ShellCase, testprogram.TestProgramCase, integration.S
stat3 = os.stat(output_file)
# Mode must have changed since we're creating a new log file
self.assertNotEqual(stat1.st_mode, stat3.st_mode)
# Data was appended to file
self.assertEqual(stat1.st_size, stat3.st_size)
finally:
if os.path.exists(output_file):
os.unlink(output_file)

View File

@ -59,24 +59,27 @@ class HgTestCase(TestCase):
'''
Test for Perform a pull on the given repository
'''
with patch.dict(hg.__salt__, {'cmd.run':
MagicMock(return_value='A')}):
with patch.dict(hg.__salt__, {'cmd.run_all':
MagicMock(return_value={'retcode': 0,
'stdout': 'A'})}):
self.assertEqual(hg.pull('cwd'), 'A')
def test_update(self):
'''
Test for Update to a given revision
'''
with patch.dict(hg.__salt__, {'cmd.run':
MagicMock(return_value='A')}):
with patch.dict(hg.__salt__, {'cmd.run_all':
MagicMock(return_value={'retcode': 0,
'stdout': 'A'})}):
self.assertEqual(hg.update('cwd', 'rev'), 'A')
def test_clone(self):
'''
Test for Clone a new repository
'''
with patch.dict(hg.__salt__, {'cmd.run':
MagicMock(return_value='A')}):
with patch.dict(hg.__salt__, {'cmd.run_all':
MagicMock(return_value={'retcode': 0,
'stdout': 'A'})}):
self.assertEqual(hg.clone('cwd', 'repository'), 'A')
def test_status_single(self):

View File

@ -30,7 +30,7 @@ class XAttrTestCase(TestCase):
'squidward': 'patrick'}
with patch.object(xattr, 'read', MagicMock(side_effect=['squarepants',
'patrick'])):
self.assertEqual(xattr.list('path/to/file'), expected)
self.assertEqual(xattr.list_('path/to/file'), expected)
@patch('salt.utils.mac_utils.execute_return_result',
MagicMock(side_effect=CommandExecutionError('No such file')))
@ -38,7 +38,7 @@ class XAttrTestCase(TestCase):
'''
Test listing attributes of a missing file
'''
self.assertRaises(CommandExecutionError, xattr.list, '/path/to/file')
self.assertRaises(CommandExecutionError, xattr.list_, '/path/to/file')
@patch('salt.utils.mac_utils.execute_return_result',
MagicMock(return_value='expected results'))
@ -55,9 +55,12 @@ class XAttrTestCase(TestCase):
'''
with patch.object(salt.utils.mac_utils, 'execute_return_result',
MagicMock(return_value='expected results')) as mock:
self.assertEqual(xattr.read('/path/to/file', 'com.attr', True),
'expected results')
mock.assert_called_once_with('xattr -p -x "com.attr" "/path/to/file"')
self.assertEqual(
xattr.read('/path/to/file', 'com.attr', **{'hex': True}),
'expected results'
)
mock.assert_called_once_with(
['xattr', '-p', '-x', 'com.attr', '/path/to/file'])
@patch('salt.utils.mac_utils.execute_return_result',
MagicMock(side_effect=CommandExecutionError('No such file')))
@ -101,7 +104,7 @@ class XAttrTestCase(TestCase):
Test deleting a specific attribute from a file
'''
mock_cmd = MagicMock(return_value={'spongebob': 'squarepants'})
with patch.object(xattr, 'list', mock_cmd):
with patch.object(xattr, 'list_', mock_cmd):
self.assertTrue(xattr.delete('/path/to/file', 'attribute'))
@patch('salt.utils.mac_utils.execute_return_success',
@ -122,7 +125,7 @@ class XAttrTestCase(TestCase):
Test clearing all attributes on a file
'''
mock_cmd = MagicMock(return_value={})
with patch.object(xattr, 'list', mock_cmd):
with patch.object(xattr, 'list_', mock_cmd):
self.assertTrue(xattr.clear('/path/to/file'))
@patch('salt.utils.mac_utils.execute_return_success',

View File

@ -57,7 +57,10 @@ class ServiceTestCase(TestCase):
'result': True},
{'changes': {},
'comment': 'The service salt is already running',
'name': 'salt', 'result': True}]
'name': 'salt', 'result': True},
{'changes': 'saltstack',
'comment': 'Service salt failed to start', 'name': 'salt',
'result': True}]
tmock = MagicMock(return_value=True)
fmock = MagicMock(return_value=False)
@ -98,6 +101,13 @@ class ServiceTestCase(TestCase):
with patch.dict(service.__salt__, {'service.status': fmock}):
self.assertDictEqual(service.running("salt"), ret[3])
with patch.dict(service.__opts__, {'test': False}):
with patch.dict(service.__salt__, {'service.status': MagicMock(side_effect=[False, False]),
'service.enabled': MagicMock(side_effecct=[True, True]),
'service.start': MagicMock(return_value='stack')}):
with patch.object(service, '_enable', MagicMock(return_value={'changes': 'saltstack'})):
self.assertDictEqual(service.running('salt', True), ret[6])
def test_dead(self):
'''
Test to ensure that the named service is dead
@ -113,8 +123,8 @@ class ServiceTestCase(TestCase):
'comment': 'Service salt was killed', 'name': 'salt',
'result': True},
{'changes': {},
'comment': 'Service salt was killed', 'name': 'salt',
'result': True},
'comment': 'Service salt failed to die', 'name': 'salt',
'result': False},
{'changes': 'saltstack',
'comment': 'The service salt is already dead', 'name': 'salt',
'result': True}]
@ -149,7 +159,7 @@ class ServiceTestCase(TestCase):
self.assertDictEqual(service.dead("salt", True), ret[1])
with patch.dict(service.__salt__, {'service.enabled': MagicMock(side_effect=[True, True, False]),
'service.status': MagicMock(side_effect=[True, True, False]),
'service.status': MagicMock(side_effect=[True, False, False]),
'service.stop': MagicMock(return_value="stack")}):
with patch.object(service, '_enable', MagicMock(return_value={'changes': 'saltstack'})):
self.assertDictEqual(service.dead("salt", True), ret[3])

View File

@ -482,6 +482,12 @@ class TestCustomExtensions(TestCase):
rendered = env.from_string('{{ dataset|yaml }}').render(dataset=dataset)
self.assertEqual(dataset, yaml.load(rendered))
def test_serialize_yaml_str(self):
dataset = "str value"
env = Environment(extensions=[SerializerExtension])
rendered = env.from_string('{{ dataset|yaml }}').render(dataset=dataset)
self.assertEqual(dataset, rendered)
def test_serialize_python(self):
dataset = {
"foo": True,