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

Conflicts:
    salt/cli/salt.py
    salt/client/mixins.py
    salt/renderers/pyobjects.py
This commit is contained in:
Colton Myers 2015-04-22 11:07:01 -06:00
commit 1a38b4b834
29 changed files with 3045 additions and 862 deletions

View file

@ -67,7 +67,7 @@ help:
clean:
rm -rf $(BUILDDIR)/*
test -d 'locale' && find locale/ -name *.mo -exec rm {} \;
test -d 'locale' && find locale/ -name *.mo -exec rm {} \; || true
html: translations
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-API" "1" "March 09, 2015" "2015.2.0rc1-133-g24fa806" "Salt"
.TH "SALT-API" "1" "April 21, 2015" "2015.2.0rc2-143-g32ef5ca" "Salt"
.SH NAME
salt-api \- salt-api Command
.
@ -49,13 +49,57 @@ The Salt API system manages network api connectors for the Salt Master
.SH OPTIONS
.INDENT 0.0
.TP
.B \-h, \-\-help
Print a usage message briefly summarizing these command\-line options.
.B \-\-version
Print the version of Salt that is running.
.UNINDENT
.INDENT 0.0
.TP
.B \-C CONFIG, \-\-config=CONFIG
Specify an alternative location for the salt master configuration file.
.B \-\-versions\-report
Show program\(aqs dependencies and version number, and then exit
.UNINDENT
.INDENT 0.0
.TP
.B \-h, \-\-help
Show the help message and exit
.UNINDENT
.INDENT 0.0
.TP
.B \-c CONFIG_DIR, \-\-config\-dir=CONFIG_dir
The location of the Salt configuration directory. This directory contains
the configuration files for Salt master and minions. The default location
on most systems is \fB/etc/salt\fP\&.
.UNINDENT
.INDENT 0.0
.TP
.B \-d, \-\-daemon
Run the salt\-api as a daemon
.UNINDENT
.INDENT 0.0
.TP
.B \-\-pid\-file=PIDFILE
Specify the location of the pidfile. Default: /var/run/salt\-api.pid
.UNINDENT
.SS Logging Options
.sp
Logging options which override any settings defined on the configuration files.
.INDENT 0.0
.TP
.B \-l LOG_LEVEL, \-\-log\-level=LOG_LEVEL
Console logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
\fBdebug\fP, \fBinfo\fP, \fBwarning\fP, \fBerror\fP, \fBquiet\fP\&. Default:
\fBwarning\fP\&.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-log\-file=LOG_FILE
Log file path. Default: /var/log/salt/api\&.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-log\-file\-level=LOG_LEVEL_LOGFILE
Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
\fBdebug\fP, \fBinfo\fP, \fBwarning\fP, \fBerror\fP, \fBquiet\fP\&. Default:
\fBwarning\fP\&.
.UNINDENT
.SH SEE ALSO
.sp

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-CLOUD" "1" "March 09, 2015" "2015.2.0rc1-133-g24fa806" "Salt"
.TH "SALT-CLOUD" "1" "April 21, 2015" "2015.2.0rc2-143-g32ef5ca" "Salt"
.SH NAME
salt-cloud \- Salt Cloud Command
.
@ -58,22 +58,57 @@ clouds via a cleanly controlled profile and mapping system.
.SH OPTIONS
.INDENT 0.0
.TP
.B \-\-version
Print the version of Salt that is running.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-versions\-report
Show program\(aqs dependencies and version number, and then exit
.UNINDENT
.INDENT 0.0
.TP
.B \-h, \-\-help
Print a usage message briefly summarizing these command\-line options.
Show the help message and exit
.UNINDENT
.INDENT 0.0
.TP
.B \-c CONFIG_DIR, \-\-config\-dir=CONFIG_dir
The location of the Salt configuration directory. This directory contains
the configuration files for Salt master and minions. The default location
on most systems is \fB/etc/salt\fP\&.
.UNINDENT
.SS Execution Options
.INDENT 0.0
.TP
.B \-L LOCATION, \-\-location=LOCATION
Specify which region to connect to.
.UNINDENT
.INDENT 0.0
.TP
.B \-a ACTION, \-\-action=ACTION
Perform an action that may be specific to this cloud provider. This
argument requires one or more instance names to be specified.
.UNINDENT
.INDENT 0.0
.TP
.B \-f <FUNC\-NAME> <PROVIDER>, \-\-function=<FUNC\-NAME> <PROVIDER>
Perform an function that may be specific to this cloud provider, that does
not apply to an instance. This argument requires a provider to be specified
(i.e.: nova).
.UNINDENT
.INDENT 0.0
.TP
.B \-p PROFILE, \-\-profile=PROFILE
Select a single profile to build the named cloud VMs from. The profile
must be defined in the specified profiles file.
Select a single profile to build the named cloud VMs from. The profile must
be defined in the specified profiles file.
.UNINDENT
.INDENT 0.0
.TP
.B \-m MAP, \-\-map=MAP
Specify a map file to use. If used without any other options, this option
will ensure that all of the mapped VMs are created. If VM names are
also passed as arguments, they will be used to filter the map file.
If the named VM already exists then it will be skipped.
will ensure that all of the mapped VMs are created. If the named VM already
exists then it will be skipped.
.UNINDENT
.INDENT 0.0
.TP
@ -112,6 +147,38 @@ in conjunction with \-m to display only information about the specified map.
.UNINDENT
.INDENT 0.0
.TP
.B \-u, \-\-update\-bootstrap
Update salt\-bootstrap to the latest develop version on GitHub.
.UNINDENT
.INDENT 0.0
.TP
.B \-y, \-\-assume\-yes
Default yes in answer to all confirmation questions.
.UNINDENT
.INDENT 0.0
.TP
.B \-k, \-\-keep\-tmp
Do not remove files from /tmp/ after deploy.sh finishes.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-show\-deploy\-args
Include the options used to deploy the minion in the data returned.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-script\-args=SCRIPT_ARGS
Script arguments to be fed to the bootstrap script when deploying the VM.
.UNINDENT
.SS Query Options
.INDENT 0.0
.TP
.B \-Q, \-\-query
Execute a query and return some information about the nodes running on
configured cloud providers
.UNINDENT
.INDENT 0.0
.TP
.B \-F, \-\-full\-query
Execute a query and print out all available information about all cloud VMs.
Can be used in conjunction with \-m to display only information about the
@ -139,58 +206,98 @@ Display a list of configured profiles. Pass in a cloud provider to view
the provider\(aqs associated profiles, such as \fBdigital_ocean\fP, or pass in
\fBall\fP to list all the configured profiles.
.UNINDENT
.SS Cloud Providers Listings
.INDENT 0.0
.TP
.B \-\-list\-images
Display a list of images available in configured cloud providers.
Pass the cloud provider that available images are desired on, aka
"linode", or pass "all" to list images for all configured cloud providers.
.B \-\-list\-locations=LIST_LOCATIONS
Display a list of locations available in configured cloud providers. Pass
the cloud provider that available locations are desired on, aka "linode",
or pass "all" to list locations for all configured cloud providers
.UNINDENT
.INDENT 0.0
.TP
.B \-\-list\-sizes
.B \-\-list\-images=LIST_IMAGES
Display a list of images available in configured cloud providers. Pass the
cloud provider that available images are desired on, aka "linode", or pass
"all" to list images for all configured cloud providers
.UNINDENT
.INDENT 0.0
.TP
.B \-\-list\-sizes=LIST_SIZES
Display a list of sizes available in configured cloud providers. Pass the
cloud provider that available sizes are desired on, aka "aws", or pass
cloud provider that available sizes are desired on, aka "AWS", or pass
"all" to list sizes for all configured cloud providers
.UNINDENT
.SS Cloud Credentials
.INDENT 0.0
.TP
.B \-C CLOUD_CONFIG, \-\-cloud\-config=CLOUD_CONFIG
Specify an alternative location for the salt cloud configuration file.
Default location is /etc/salt/cloud.
.B \-\-set\-password=<USERNAME> <PROVIDER>
Configure password for a cloud provider and save it to the keyring.
PROVIDER can be specified with or without a driver, for example:
"\-\-set\-password bob rackspace" or more specific "\-\-set\-password bob
rackspace:openstack" DEPRECATED!
.UNINDENT
.SS Output Options
.INDENT 0.0
.TP
.B \-\-out
Pass in an alternative outputter to display the return of data. This
outputter can be any of the available outputters:
.INDENT 7.0
.INDENT 3.5
\fBgrains\fP, \fBhighstate\fP, \fBjson\fP, \fBkey\fP, \fBoverstatestage\fP, \fBpprint\fP, \fBraw\fP, \fBtxt\fP, \fByaml\fP
.UNINDENT
.UNINDENT
.sp
Some outputters are formatted only for data returned from specific
functions; for instance, the \fBgrains\fP outputter will not work for non\-grains
data.
.sp
If an outputter is used that does not support the data passed into it, then
Salt will fall back on the \fBpprint\fP outputter and display the return data
using the Python \fBpprint\fP standard library module.
.sp
\fBNOTE:\fP
.INDENT 7.0
.INDENT 3.5
If using \fB\-\-out=json\fP, you will probably want \fB\-\-static\fP as well.
Without the static option, you will get a JSON string for each minion.
This is due to using an iterative outputter. So if you want to feed it
to a JSON parser, use \fB\-\-static\fP as well.
.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B \-M MASTER_CONFIG, \-\-master\-config=MASTER_CONFIG
Specify an alternative location for the salt master configuration file.
The salt master configuration file is used to determine how to handle the
minion RSA keys. Default location is /etc/salt/master.
.B \-\-out\-indent OUTPUT_INDENT, \-\-output\-indent OUTPUT_INDENT
Print the output indented by the provided value in spaces. Negative values
disable indentation. Only applicable in outputters that support
indentation.
.UNINDENT
.INDENT 0.0
.TP
.B \-V VM_CONFIG, \-\-profiles=VM_CONFIG, \-\-vm_config=VM_CONFIG
Specify an alternative location for the salt cloud profiles file.
Default location is /etc/salt/cloud.profiles.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-raw\-out
Print the output from the salt command in raw python
form, this is suitable for re\-reading the output into
an executing python script with eval.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-out=OUTPUT, \-\-output=OUTPUT
Print the output from the salt\-cloud command using the specified outputter. The
builtins are \(aqraw\(aq, \(aqcompact\(aq, \(aqno_return\(aq, \(aqgrains\(aq, \(aqoverstatestage\(aq, \(aqpprint\(aq,
\(aqjson\(aq, \(aqnested\(aq, \(aqyaml\(aq, \(aqhighstate\(aq, \(aqquiet\(aq, \(aqkey\(aq, \(aqtxt\(aq, \(aqnewline_values_only\(aq,
\(aqvirt_query\(aq.
.B \-\-out\-file=OUTPUT_FILE, \-\-output\-file=OUTPUT_FILE
Write the output to the specified file.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-no\-color
Disable all colored output.
Disable all colored output
.UNINDENT
.INDENT 0.0
.TP
.B \-\-force\-color
Force colored output
.sp
\fBNOTE:\fP
.INDENT 7.0
.INDENT 3.5
When using colored output the color codes are as follows:
.sp
\fBgreen\fP denotes success, \fBred\fP denotes failure, \fBblue\fP denotes
changes and success and \fByellow\fP denotes a expected future change in configuration.
.UNINDENT
.UNINDENT
.UNINDENT
.SH EXAMPLES
.sp

File diff suppressed because it is too large Load diff

View file

@ -69,7 +69,7 @@ If Exist "%BinDir%\README.txt" del /q "%BinDir%\README.txt"
@ echo Building the installer...
@ echo -------------------------
makensis.exe /DSaltVersion="%Version%" "%InsDir%\Salt-Minion-Setup.nsi"
makensis.exe /DSaltVersion=%Version% "%InsDir%\Salt-Minion-Setup.nsi"
@ echo.
@ echo.

View file

@ -17,7 +17,7 @@ ${StrLoc}
${StrStrAdv}
!ifdef SaltVersion
!define PRODUCT_VERSION ${SaltVersion}
!define PRODUCT_VERSION "${SaltVersion}"
!else
!define PRODUCT_VERSION "Undefined Version"
!endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View file

@ -6,6 +6,7 @@ import os
import sys
# Import Salt libs
import salt.utils.job
from salt.ext.six import string_types
from salt.utils import parsers, print_cli
from salt.exceptions import (
@ -85,7 +86,11 @@ class SaltCMD(parsers.SaltCMDOptionParser):
batch = salt.cli.batch.Batch(self.config, eauth=eauth)
# Printing the output is already taken care of in run() itself
for res in batch.run():
pass
if self.options.failhard:
for ret in res.itervalues():
retcode = salt.utils.job.get_retcode(ret)
if retcode != 0:
sys.exit(retcode)
else:
if self.options.timeout <= 0:
@ -295,8 +300,9 @@ class SaltCMD(parsers.SaltCMDOptionParser):
ret[key] = data['ret']
if 'out' in data:
out = data['out']
if 'retcode' in data:
retcode = data['retcode']
ret_retcode = salt.utils.job.get_retcode(data)
if ret_retcode > retcode:
retcode = ret_retcode
return ret, out, retcode
def _format_error(self, minion_error):

View file

@ -939,6 +939,18 @@ class RemoteClient(Client):
dest is omitted, then the downloaded file will be placed in the minion
cache
'''
# Check if file exists on server, before creating files and
# directories
hash_server = self.hash_file(path, saltenv)
if hash_server == '':
log.debug(
'Could not find file from saltenv {0!r}, {1!r}'.format(
saltenv, path
)
)
return False
if env is not None:
salt.utils.warn_until(
'Boron',
@ -971,7 +983,6 @@ class RemoteClient(Client):
if dest2check and os.path.isfile(dest2check):
hash_local = self.hash_file(dest2check, saltenv)
hash_server = self.hash_file(path, saltenv)
if hash_local == hash_server:
log.info(
'Fetching file from saltenv {0!r}, ** skipped ** '

View file

@ -524,8 +524,6 @@ def grains(opts, force_refresh=False):
# Run the rest of the grains
for key, fun in six.iteritems(funcs):
if '.' not in key:
continue
if key.startswith('core.') or key == '_errors':
continue
try:
@ -719,7 +717,6 @@ class LazyLoader(salt.utils.lazy.LazyDict):
whitelist=None,
virtual_enable=True,
): # pylint: disable=W0231
super(LazyLoader, self).__init__() # init the lazy loader
self.opts = self.__prep_mod_opts(opts)
self.module_dirs = module_dirs
@ -739,19 +736,37 @@ class LazyLoader(salt.utils.lazy.LazyDict):
# names of modules that we don't have (errors, __virtual__, etc.)
self.missing_modules = {} # mapping of name -> error
self.loaded_modules = set() # list of all modules that we have loaded
self.loaded_modules = {} # mapping of module_name -> dict_of_functions
self.loaded_files = set() # TODO: just remove them from file_mapping?
self.disabled = set(self.opts.get('disable_{0}s'.format(self.tag), []))
self.refresh_file_mapping()
super(LazyLoader, self).__init__() # late init the lazy loader
# create all of the import namespaces
_generate_module('{0}.int'.format(self.loaded_base_name))
_generate_module('{0}.int.{1}'.format(self.loaded_base_name, tag))
_generate_module('{0}.ext'.format(self.loaded_base_name))
_generate_module('{0}.ext.{1}'.format(self.loaded_base_name, tag))
def __getattr__(self, mod_name):
'''
Allow for "direct" attribute access-- this allows jinja templates to
access things like `salt.test.ping()`
'''
if mod_name not in self.loaded_modules and not self.loaded:
for name in self._iter_files(mod_name):
if name in self.loaded_files:
continue
# if we got what we wanted, we are done
if self._load_module(name) and mod_name in self.loaded_modules:
break
if mod_name in self.loaded_modules:
return self.loaded_modules[mod_name]
else:
raise AttributeError(mod_name)
def missing_fun_string(self, function_name):
'''
Return the error string for a missing function.
@ -848,7 +863,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
super(LazyLoader, self).clear() # clear the lazy loader
self.loaded_files = set()
self.missing_modules = {}
self.loaded_modules = set()
self.loaded_modules = {}
# if we have been loaded before, lets clear the file mapping since
# we obviously want a re-do
if hasattr(self, 'opts'):
@ -1029,7 +1044,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
module_name
)
)
self._dict[module_name] = salt.utils.odict.OrderedDict()
mod_dict = salt.utils.odict.OrderedDict()
for attr in getattr(mod, '__load__', dir(mod)):
if attr.startswith('_'):
# private functions are skipped
@ -1048,13 +1063,13 @@ class LazyLoader(salt.utils.lazy.LazyDict):
funcname = getattr(mod, '__func_alias__', {}).get(attr, attr)
# Save many references for lookups
self._dict['{0}.{1}'.format(module_name, funcname)] = func
setattr(self._dict[module_name], funcname, func)
self._dict[module_name][funcname] = func
setattr(mod_dict, funcname, func)
mod_dict[funcname] = func
self._apply_outputter(func, mod)
# enforce depends
Depends.enforce_dependencies(self._dict, self.tag)
self.loaded_modules.add(module_name)
self.loaded_modules[module_name] = mod_dict
return True
def _load(self, key):
@ -1062,12 +1077,9 @@ class LazyLoader(salt.utils.lazy.LazyDict):
Load a single item if you have it
'''
# if the key doesn't have a '.' then it isn't valid for this mod dict
if not isinstance(key, six.string_types):
if not isinstance(key, six.string_types) or '.' not in key:
raise KeyError
if '.' not in key:
mod_name = key
else:
mod_name, _ = key.split('.', 1)
mod_name, _ = key.split('.', 1)
if mod_name in self.missing_modules:
return True
# if the modulename isn't in the whitelist, don't bother

View file

@ -1196,6 +1196,9 @@ class AESFuncs(object):
# Register the syndic
syndic_cache_path = os.path.join(self.opts['cachedir'], 'syndics', load['id'])
if not os.path.exists(syndic_cache_path):
path_name = os.path.split(syndic_cache_path)[0]
if not os.path.exists(path_name):
os.makedirs(path_name)
with salt.utils.fopen(syndic_cache_path, 'w') as f:
f.write('')

View file

@ -514,7 +514,7 @@ def install(name=None,
if pkgs is None and kwargs.get('version') and len(pkg_params) == 1:
# Only use the 'version' param if 'name' was not specified as a
# comma-separated list
pkg_params = {name: kwargs.get('version')}
pkg_params = {name: str(kwargs.get('version'))}
targets = []
for param, version_num in six.iteritems(pkg_params):
if version_num is None:

View file

@ -336,9 +336,8 @@ def cache_file(path, saltenv='base', env=None):
if '?env=' in path:
salt.utils.warn_until(
'Boron',
'Passing a salt environment should be done using '
'\'saltenv\' not \'env\'. This functionality will be '
'removed in Salt Boron.'
'Passing a salt environment should be done using \'saltenv\' '
'not \'env\'. This functionality will be removed in Salt Boron.'
)
env_splitter = '?env='
try:

View file

@ -1,6 +1,77 @@
# -*- coding: utf-8 -*-
'''
r'''
Install Python packages with pip to either the system or a virtualenv
Windows Support
===============
.. versionadded:: 2014.7.4
Salt now uses a portable python. As a result the entire pip module is now
functional on the salt installation itself. You can pip install dependencies
for your custom modules. You can even upgrade salt itself using pip. For this
to work properly, you must specify the Current Working Directory (``cwd``) and
the Pip Binary (``bin_env``) salt should use.
For example, the following command will list all software installed using pip
to your current salt environment:
.. code-block:: bat
salt <minion> pip.list cwd='C:\salt\bin\Scripts' bin_env='C:\salt\bin\Scripts\pip.exe'
Specifying the ``cwd`` and ``bin_env`` options ensures you're modifying the
salt environment. If these are omitted, it will default to the local
installation of python. If python is not installed locally it will fail saying
it couldn't find pip.
State File Support
------------------
This functionality works in states as well. If you need to pip install colorama
with a state, for example, the following will work:
.. code-block:: yaml
install_colorama:
pip.installed:
- name: colorama
- cwd: 'C:\salt\bin\scripts'
- bin_env: 'C:\salt\bin\scripts\pip.exe'
- upgrade: True
Upgrading Salt using Pip
------------------------
You can now update salt using pip to any version from the 2014.7 branch
forward. Previous version require recompiling some of the dependencies which is
painful in windows.
To do this you just use pip with git to update to the version you want and then
restart the service. Here is a sample state file that upgrades salt to the head
of the 2015.2 branch:
.. code-block:: yaml
install_salt:
pip.installed:
- cwd: 'C:\salt\bin\scripts'
- bin_env: 'C:\salt\bin\scripts\pip.exe'
- editable: git+https://github.com/saltstack/salt@2015.2#egg=salt
- upgrade: True
restart_service:
service.running:
- name: salt-minion
- enable: True
- watch:
- pip: install_salt
.. note::
If you're having problems, you might try doubling the back slashes. For
example, cwd: 'C:\\salt\\bin\\scripts'. Sometimes python thinks the single
back slash is an escape character.
'''
from __future__ import absolute_import
@ -55,8 +126,39 @@ def _get_pip_bin(bin_env):
return bin_env
def _process_salt_url(path, saltenv):
'''
Process 'salt://' and '?saltenv=' out of `path` and return the stripped
path and the saltenv.
'''
path = path.split('salt://', 1)[-1]
env_splitter = '?saltenv='
if '?env=' in path:
salt.utils.warn_until(
'Boron',
'Passing a salt environment should be done using \'saltenv\' '
'not \'env\'. This functionality will be removed in Salt Boron.'
)
env_splitter = '?env='
try:
path, saltenv = path.split(env_splitter)
except ValueError:
pass
return path, saltenv
def _get_cached_requirements(requirements, saltenv):
'''Get the location of a cached requirements file; caching if necessary.'''
'''
Get the location of a cached requirements file; caching if necessary.
'''
requirements_file, saltenv = _process_salt_url(requirements, saltenv)
if requirements_file not in __salt__['cp.list_master'](saltenv):
# Requirements file does not exist in the given saltenv.
return False
cached_requirements = __salt__['cp.is_cached'](
requirements, saltenv
)

View file

@ -850,7 +850,7 @@ def wheel(fun, **kwargs):
salt '*' saltutil.wheel key.accept match=jerry
'''
wclient = salt.wheel.WheelClient(__opts__)
return wclient.cmd(fun, **kwargs)
return wclient.cmd(fun, kwarg=kwargs)
# this is the only way I could figure out how to get the REAL file_roots

View file

@ -89,4 +89,18 @@ def store_job(opts, load, event=None, mminion=None):
log.error(emsg)
raise KeyError(emsg)
def get_retcode(ret):
'''
Determine a retcode for a given return
'''
retcode = 0
# if there is a dict with retcode, use that
if isinstance(ret, dict) and ret.get('retcode', 0) != 0:
return ret['retcode']
# if its a boolean, False means 1
elif isinstance(ret, bool) and not ret:
return 1
return retcode
# vim:set et sts=4 ts=4 tw=80:

View file

@ -94,22 +94,6 @@ class LazyDict(collections.MutableMapping):
else:
return self._dict[key]
def __getattr__(self, name):
'''
Check if the name is in the dict and return it if it is
'''
if name not in self._dict and not self.loaded:
# load the item
if self._load(name):
log.debug('LazyLoaded {0}'.format(name))
return self._dict[name]
else:
log.debug('Could not LazyLoad {0}'.format(name))
raise KeyError(name)
elif name in self:
return self[name]
raise AttributeError(name)
def __len__(self):
# if not loaded,
if not self.loaded:

View file

@ -1479,6 +1479,12 @@ class SaltCMDOptionParser(six.with_metaclass(OptionParserMeta,
action='store_true',
help=('Display a progress graph')
)
self.add_option(
'--failhard',
default=False,
action='store_true',
help=('Stop batch execution upon first "bad" return')
)
self.add_option(
'--async',
default=False,

View file

@ -842,6 +842,21 @@ class Schedule(object):
self.loop_interval = seconds
run = False
if 'splay' in data:
if 'when' in data:
log.error('Unable to use "splay" with "when" option at this time. Ignoring.')
elif 'cron' in data:
log.error('Unable to use "splay" with "cron" option at this time. Ignoring.')
else:
if '_seconds' not in data:
log.debug('The _seconds parameter is missing, '
'most likely the first run or the schedule '
'has been refreshed refresh.')
if 'seconds' in data:
data['_seconds'] = data['seconds']
else:
data['_seconds'] = 0
if job in self.intervals:
if 'when' in data:
if seconds == 0:
@ -855,17 +870,6 @@ class Schedule(object):
if now - self.intervals[job] >= seconds:
run = True
else:
if 'splay' in data:
if 'when' in data:
log.error('Unable to use "splay" with "when" option at this time. Ignoring.')
elif 'cron' in data:
log.error('Unable to use "splay" with "cron" option at this time. Ignoring.')
else:
if 'seconds' in data:
data['_seconds'] = data['seconds']
else:
data['_seconds'] = 0
if 'when' in data:
if seconds == 0:
if data['_when_run']:

View file

@ -109,8 +109,7 @@ class LazyLoaderVirtualEnabledTest(TestCase):
self.assertEqual(func_globals['__pillar__'], self.opts.get('pillar', {}))
# the opts passed into modules is at least a subset of the whole opts
for key, val in six.iteritems(func_globals['__opts__']):
if key in self.opts:
self.assertEqual(self.opts[key], val)
self.assertEqual(self.opts[key], val)
def test_pack(self):
self.loader.pack['__foo__'] = 'bar'

View file

@ -182,10 +182,11 @@ class PipModuleTest(integration.ModuleCase):
)
try:
self.assertEqual(ret['retcode'], 0)
self.assertIn(
'Successfully installed pep8 Blinker SaltTesting',
ret['stdout']
)
for package in ('Blinker', 'SaltTesting', 'pep8'):
self.assertRegexpMatches(
ret['stdout'],
r'(?:.*)(Successfully installed)(?:.*)({0})(?:.*)'.format(package)
)
except AssertionError:
import pprint
pprint.pprint(ret)

View file

@ -74,8 +74,6 @@ class SysModuleTest(integration.ModuleCase):
)
for fun in docs:
if '.' not in fun:
continue
if fun.startswith('runtests_helpers'):
continue
if fun in allow_failure:

View file

@ -0,0 +1,86 @@
# -*- 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.modules import powerpath
# Globals
powerpath.__salt__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PowerpathTestCase(TestCase):
'''
Test cases for salt.modules.powerpath
'''
@patch('os.path.exists')
def test_has_powerpath(self, mock_exists):
'''
Test for powerpath
'''
mock_exists.return_value = True
self.assertTrue(powerpath.has_powerpath())
mock_exists.return_value = False
self.assertFalse(powerpath.has_powerpath())
def test_list_licenses(self):
'''
Test to returns a list of applied powerpath license keys
'''
with patch.dict(powerpath.__salt__,
{'cmd.run': MagicMock(return_value='A\nB')}):
self.assertListEqual(powerpath.list_licenses(), [])
def test_add_license(self):
'''
Test to add a license
'''
with patch.object(powerpath, 'has_powerpath', return_value=False):
self.assertDictEqual(powerpath.add_license('key'),
{'output': 'PowerPath is not installed',
'result': False, 'retcode': -1})
mock = MagicMock(return_value={'retcode': 1, 'stderr': 'stderr'})
with patch.object(powerpath, 'has_powerpath', return_value=True):
with patch.dict(powerpath.__salt__, {'cmd.run_all': mock}):
self.assertDictEqual(powerpath.add_license('key'),
{'output': 'stderr', 'result': False,
'retcode': 1})
def test_remove_license(self):
'''
Test to remove a license
'''
with patch.object(powerpath, 'has_powerpath', return_value=False):
self.assertDictEqual(powerpath.remove_license('key'),
{'output': 'PowerPath is not installed',
'result': False, 'retcode': -1})
mock = MagicMock(return_value={'retcode': 1, 'stderr': 'stderr'})
with patch.object(powerpath, 'has_powerpath', return_value=True):
with patch.dict(powerpath.__salt__, {'cmd.run_all': mock}):
self.assertDictEqual(powerpath.remove_license('key'),
{'output': 'stderr', 'result': False,
'retcode': 1})
if __name__ == '__main__':
from integration import run_tests
run_tests(PowerpathTestCase, needs_daemon=False)

View file

@ -8,7 +8,6 @@ from __future__ import absolute_import
import os
# Import Salt Testing Libs
import salt.utils
from salttesting import TestCase, skipIf
from salt.exceptions import SaltInvocationError
from salttesting.helpers import ensure_in_syspath
@ -23,6 +22,7 @@ from salttesting.mock import (
ensure_in_syspath('../../')
# Import Salt Libs
import salt.utils
from salt.modules import state
# Globals
@ -548,9 +548,9 @@ class StateTestCase(TestCase):
'''
Test to retrieve the highstate data from the salt master
'''
mock = MagicMock(side_effect=[{"A"}, None, None])
mock = MagicMock(side_effect=["A", None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.show_highstate(), {"A"})
self.assertEqual(state.show_highstate(), "A")
self.assertRaises(SaltInvocationError,
state.show_highstate,
@ -562,7 +562,7 @@ class StateTestCase(TestCase):
'''
Test to list out the low data that will be applied to this minion
'''
mock = MagicMock(side_effect=[{"A"}, None])
mock = MagicMock(side_effect=["A", None])
with patch.object(state, '_check_queue', mock):
self.assertRaises(AssertionError, state.show_lowstate)
@ -573,9 +573,9 @@ class StateTestCase(TestCase):
Test to call a single ID from the
named module(s) and handle all requisites
'''
mock = MagicMock(side_effect=[{"A"}, None, None, None])
mock = MagicMock(side_effect=["A", None, None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.sls_id("apache", "http"), {"A"})
self.assertEqual(state.sls_id("apache", "http"), "A")
with patch.dict(state.__opts__, {"test": "A"}):
mock = MagicMock(return_value={'test': True})
@ -597,9 +597,9 @@ class StateTestCase(TestCase):
'''
Test to display the low data from a specific sls
'''
mock = MagicMock(side_effect=[{"A"}, None, None])
mock = MagicMock(side_effect=["A", None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.show_low_sls("foo"), {"A"})
self.assertEqual(state.show_low_sls("foo"), "A")
with patch.dict(state.__opts__, {"test": "A"}):
mock = MagicMock(return_value={'test': True})
@ -616,9 +616,9 @@ class StateTestCase(TestCase):
'''
Test to display the state data from a specific sls
'''
mock = MagicMock(side_effect=[{"A"}, None, None, None])
mock = MagicMock(side_effect=["A", None, None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.show_sls("foo"), {"A"})
self.assertEqual(state.show_sls("foo"), "A")
with patch.dict(state.__opts__, {"test": "A"}):
mock = MagicMock(return_value={'test': True})
@ -642,9 +642,9 @@ class StateTestCase(TestCase):
Test to execute a specific top file
'''
ret = ['Pillar failed to render with the following messages:', 'E']
mock = MagicMock(side_effect=[{"A"}, None, None, None])
mock = MagicMock(side_effect=["A", None, None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.top("reverse_top.sls"), {"A"})
self.assertEqual(state.top("reverse_top.sls"), "A")
mock = MagicMock(side_effect=[False, True, True])
with patch.object(state, '_check_pillar', mock):
@ -688,9 +688,9 @@ class StateTestCase(TestCase):
'To re-enable, run state.enable highstate',
'result': 'False'})
mock = MagicMock(side_effect=[{"A"}, None, None])
mock = MagicMock(side_effect=["A", None, None])
with patch.object(state, '_check_queue', mock):
self.assertEqual(state.highstate("whitelist=sls1.sls"), {"A"})
self.assertEqual(state.highstate("whitelist=sls1.sls"), "A")
with patch.dict(state.__opts__, {"test": "A"}):
mock = MagicMock(return_value={'test': True})
@ -703,7 +703,7 @@ class StateTestCase(TestCase):
mock = MagicMock(return_value=True)
with patch.dict(state.__salt__,
{'config.option': mock}):
mock = MagicMock(return_value={"A"})
mock = MagicMock(return_value="A")
with patch.object(state, '_filter_running',
mock):
mock = MagicMock(return_value=True)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
'''
# Import Python Libs
@ -99,12 +99,6 @@ class SysmodTestCase(TestCase):
'''
self.assertDictEqual(sysmod.doc(), {})
ret = ("str(object='') -> string\n\nReturn a nice string"
" representation of the object.\nIf the argument is a string,"
" the return value is the same object.")
with patch.dict(sysmod.__salt__, {'sys.doc': ''}):
self.assertDictEqual(sysmod.doc('sys.doc'), {'sys.doc': ret})
# 'state_doc' function tests: 1
def test_state_doc(self):

View file

@ -0,0 +1,101 @@
# -*- 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 boto_asg
boto_asg.__salt__ = {}
boto_asg.__opts__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoAsgTestCase(TestCase):
'''
Test cases for salt.states.boto_asg
'''
# 'present' function tests: 1
def test_present(self):
'''
Test to ensure the autoscale group exists.
'''
name = 'myasg'
launch_config_name = 'mylc'
availability_zones = ['us-east-1a', 'us-east-1b']
min_size = 1
max_size = 1
ret = {'name': name,
'result': None,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[False, {'min_size': 2}, ['']])
with patch.dict(boto_asg.__salt__, {'boto_asg.get_config': mock}):
with patch.dict(boto_asg.__opts__, {'test': True}):
comt = ('Autoscale group set to be created.')
ret.update({'comment': comt})
self.assertDictEqual(boto_asg.present(name, launch_config_name,
availability_zones,
min_size, max_size), ret)
comt = ('Autoscale group set to be updated.')
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(boto_asg.present(name, launch_config_name,
availability_zones,
min_size, max_size), ret)
with patch.dict(boto_asg.__salt__,
{'config.option': MagicMock(return_value={})}):
comt = ('Autoscale group present. ')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(boto_asg.present(name,
launch_config_name,
availability_zones,
min_size, max_size),
ret)
# 'absent' function tests: 1
def test_absent(self):
'''
Test to ensure the named autoscale group is deleted.
'''
name = 'myasg'
ret = {'name': name,
'result': None,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[True, False])
with patch.dict(boto_asg.__salt__, {'boto_asg.get_config': mock}):
with patch.dict(boto_asg.__opts__, {'test': True}):
comt = ('Autoscale group set to be deleted.')
ret.update({'comment': comt})
self.assertDictEqual(boto_asg.absent(name), ret)
comt = ('Autoscale group does not exist.')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(boto_asg.absent(name), ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(BotoAsgTestCase, needs_daemon=False)

View file

@ -0,0 +1,103 @@
# -*- 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 boto_cloudwatch_alarm
boto_cloudwatch_alarm.__salt__ = {}
boto_cloudwatch_alarm.__opts__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudwatchAlarmTestCase(TestCase):
'''
Test cases for salt.states.boto_cloudwatch_alarm
'''
# 'present' function tests: 1
def test_present(self):
'''
Test to ensure the cloudwatch alarm exists.
'''
name = 'my test alarm'
attributes = {'metric': 'ApproximateNumberOfMessagesVisible',
'namespace': 'AWS/SQS'}
ret = {'name': name,
'result': None,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[['ok_actions'], [], []])
mock_bool = MagicMock(return_value=True)
with patch.dict(boto_cloudwatch_alarm.__salt__,
{'boto_cloudwatch.get_alarm': mock,
'boto_cloudwatch.create_or_update_alarm': mock_bool}):
with patch.dict(boto_cloudwatch_alarm.__opts__, {'test': True}):
comt = ('alarm my test alarm is to be created/updated.')
ret.update({'comment': comt})
self.assertDictEqual(boto_cloudwatch_alarm.present(name,
attributes),
ret)
comt = ('alarm my test alarm is to be created/updated.')
ret.update({'comment': comt})
self.assertDictEqual(boto_cloudwatch_alarm.present(name,
attributes),
ret)
with patch.dict(boto_cloudwatch_alarm.__opts__, {'test': False}):
changes = {'new':
{'metric': 'ApproximateNumberOfMessagesVisible',
'namespace': 'AWS/SQS'}}
comt = ('alarm my test alarm is to be created/updated.')
ret.update({'changes': changes, 'comment': '', 'result': True})
self.assertDictEqual(boto_cloudwatch_alarm.present(name,
attributes),
ret)
# 'absent' function tests: 1
def test_absent(self):
'''
Test to ensure the named cloudwatch alarm is deleted.
'''
name = 'my test alarm'
ret = {'name': name,
'result': None,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[True, False])
with patch.dict(boto_cloudwatch_alarm.__salt__,
{'boto_cloudwatch.get_alarm': mock}):
with patch.dict(boto_cloudwatch_alarm.__opts__, {'test': True}):
comt = ('alarm {0} is set to be removed.'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(boto_cloudwatch_alarm.absent(name), ret)
comt = ('my test alarm does not exist in None.')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(boto_cloudwatch_alarm.absent(name), ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(BotoCloudwatchAlarmTestCase, needs_daemon=False)

View file

@ -0,0 +1,119 @@
# -*- 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 boto_dynamodb
boto_dynamodb.__salt__ = {}
boto_dynamodb.__opts__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoDynamodbTestCase(TestCase):
'''
Test cases for salt.states.boto_dynamodb
'''
# 'present' function tests: 1
def test_present(self):
'''
Test to ensure the DynamoDB table exists.
'''
name = 'new_table'
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[True, False, False])
mock_bool = MagicMock(return_value=True)
with patch.dict(boto_dynamodb.__salt__,
{'boto_dynamodb.exists': mock,
'boto_dynamodb.create_table': mock_bool}):
comt = ('DynamoDB table {0} already exists. \
Nothing to change.'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(boto_dynamodb.present(name), ret)
with patch.dict(boto_dynamodb.__opts__, {'test': True}):
comt = ('DynamoDB table {0} is set to be created \
'.format(name))
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(boto_dynamodb.present(name), ret)
changes = {'new': {'global_indexes': None,
'hash_key': (None,),
'hash_key_data_type': None,
'local_indexes': (None,),
'range_key': (None,),
'range_key_data_type': (None,),
'read_capacity_units': (None,),
'table': 'new_table',
'write_capacity_units': (None,)},
'old': None}
with patch.dict(boto_dynamodb.__opts__, {'test': False}):
comt = ('DynamoDB table {0} created successfully \
'.format(name))
ret.update({'comment': comt, 'result': True,
'changes': changes})
self.assertDictEqual(boto_dynamodb.present(name), ret)
# 'absent' function tests: 1
def test_absent(self):
'''
Test to ensure the DynamoDB table does not exist.
'''
name = 'new_table'
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
mock = MagicMock(side_effect=[False, True, True])
mock_bool = MagicMock(return_value=True)
with patch.dict(boto_dynamodb.__salt__,
{'boto_dynamodb.exists': mock,
'boto_dynamodb.delete': mock_bool}):
comt = ('DynamoDB table {0} does not exist'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(boto_dynamodb.absent(name), ret)
with patch.dict(boto_dynamodb.__opts__, {'test': True}):
comt = ('DynamoDB table {0} is set to be deleted \
'.format(name))
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(boto_dynamodb.absent(name), ret)
changes = {'new': 'Table new_table deleted',
'old': 'Table new_table exists'}
with patch.dict(boto_dynamodb.__opts__, {'test': False}):
comt = ('Deleted DynamoDB table {0}'.format(name))
ret.update({'comment': comt, 'result': True,
'changes': changes})
self.assertDictEqual(boto_dynamodb.absent(name), ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(BotoDynamodbTestCase, needs_daemon=False)

View file

@ -5,6 +5,7 @@
# Import Python Libs
from __future__ import absolute_import
import sys
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
@ -57,6 +58,7 @@ class MockGrains(object):
return {'A': 'B'}
@skipIf(sys.version_info < (2, 7), 'This needs to be refactored to work with Python 2.6')
@skipIf(NO_MOCK, NO_MOCK_REASON)
class NetworkTestCase(TestCase):
'''