Merge branch '2016.3' into 'develop'

Conflicts:
  - conf/master
  - doc/ref/configuration/master.rst
  - salt/cli/batch.py
  - salt/cli/daemons.py
  - salt/config/__init__.py
  - salt/minion.py
  - salt/modules/aptpkg.py
  - salt/modules/beacons.py
  - salt/states/archive.py
  - salt/states/cmd.py
  - salt/utils/gitfs.py
  - tests/unit/states/cmd_test.py
This commit is contained in:
rallytime 2016-04-26 14:42:26 -06:00
Родитель a4e4ff257b 395b7ad747
Коммит cbf42a8407
78 изменённых файлов: 1240 добавлений и 269 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -65,6 +65,9 @@ tags
# Allow a user to set their own _version.py for testing
_version.py
# Ignore auto generated _syspaths.py file
_syspaths.py
# Ignore grains file written out during tests
tests/integration/files/conf/grains
/salt/_syspaths.py

Просмотреть файл

@ -591,7 +591,7 @@
# and the first repo to have the file will return it.
# When using the git backend branches and tags are translated into salt
# environments.
# Note: file:// repos will be treated as a remote, so refs you want used must
# Note: file:// repos will be treated as a remote, so refs you want used must
# exist in that repo as *local* refs.
#gitfs_remotes:
# - git://github.com/saltstack/salt-states.git
@ -723,19 +723,20 @@
# A master can cache pillars locally to bypass the expense of having to render them
# for each minion on every request. This feature should only be enabled in cases
# where pillar rendering time is known to be unsatisfactory and any attendent security
# where pillar rendering time is known to be unsatisfactory and any attendant security
# concerns about storing pillars in a master cache have been addressed.
#
# When enabling this feature, be certain to read through the additional pillar_cache_*
# configuration options to fully understand the tuneable parameters and their implications.
# When enabling this feature, be certain to read through the additional ``pillar_cache_*``
# configuration options to fully understand the tunable parameters and their implications.
#
# Note: setting ``pillar_cache: True`` has no effect on targeting Minions with Pillars.
# See https://docs.saltstack.com/en/latest/topics/targeting/pillar.html
#pillar_cache: False
# If and only if a master has set `pillar_cache: True`, the cache TTL controls the amount
# If and only if a master has set ``pillar_cache: True``, the cache TTL controls the amount
# of time, in seconds, before the cache is considered invalid by a master and a fresh
# pillar is recompiled and stored.
#
# pillar_cache_ttl: 3600
#pillar_cache_ttl: 3600
# If and only if a master has set `pillar_cache: True`, one of several storage providers
# can be utililzed.
@ -745,20 +746,18 @@
# Note that pillars are stored UNENCRYPTED. Ensure that the master cache
# has permissions set appropriately. (Same defaults are provided.)
#
#`memory`: [EXPERIMENTAL] An optional backend for pillar caches which uses a pure-Python
# in-memory data structure for maximal performance. There are several cavaets,
# however. First, because each master worker contains its own in-memory cache,
# there is no guarantee of cache consistency between minion requests. This
# works best in situations where the pillar rarely if ever changes. Secondly,
# and perhaps more importantly, this means that unencrypted pillars will
# be accessible to any process which can examine the memory of the salt-master!
# This may represent a substantial security risk.
# memory: [EXPERIMENTAL] An optional backend for pillar caches which uses a pure-Python
# in-memory data structure for maximal performance. There are several caveats,
# however. First, because each master worker contains its own in-memory cache,
# there is no guarantee of cache consistency between minion requests. This
# works best in situations where the pillar rarely if ever changes. Secondly,
# and perhaps more importantly, this means that unencrypted pillars will
# be accessible to any process which can examine the memory of the ``salt-master``!
# This may represent a substantial security risk.
#
#pillar_cache_backend: disk
##### Syndic settings #####
##########################################
# The Salt syndic is used to pass commands through a master from a higher

Просмотреть файл

@ -89,7 +89,7 @@ Execution Options
.. option:: -u, --update-bootstrap
Update salt-bootstrap to the latest develop version on GitHub.
Update salt-bootstrap to the latest stable bootstrap release.
.. option:: -y, --assume-yes

Просмотреть файл

@ -10,7 +10,7 @@ of the Salt system each have a respective configuration file. The
:command:`salt-minion` is configured via the minion configuration file.
.. seealso::
:ref:`example master configuration file <configuration-examples-master>`
:ref:`Example master configuration file <configuration-examples-master>`.
The configuration file for the salt-master is located at
:file:`/etc/salt/master` by default. A notable exception is FreeBSD, where the
@ -20,7 +20,6 @@ options are as follows:
Primary Master Configuration
============================
.. conf_master:: interface
``interface``
@ -1610,7 +1609,6 @@ authenticate is protected by a passphrase.
gitfs_passphrase: mypassphrase
hg: Mercurial Remote File Server Backend
----------------------------------------
@ -2135,13 +2133,13 @@ configuration is the same as :conf_master:`file_roots`:
prod:
- /srv/pillar/prod
.. _master-configuration-ext-pillar:
.. conf_master:: ext_pillar
``ext_pillar``
--------------
.. _master-configuration-ext-pillar:
The ext_pillar option allows for any number of external pillar interfaces to be
called when populating pillar data. The configuration is based on ext_pillar
functions. The available ext_pillar functions can be found herein:
@ -2198,7 +2196,7 @@ pillar_roots_override_ext_pillar option and will be removed in future releases.
ext_pillar_first: False
.. _git_pillar-config-opts:
.. _git-pillar-config-opts:
Git External Pillar (git_pillar) Configuration Options
------------------------------------------------------
@ -2392,6 +2390,7 @@ they were created by a different master.
.. __: http://www.gluster.org/
.. _git-ext-pillar-auth-opts:
Git External Pillar Authentication Options
******************************************
@ -2499,10 +2498,15 @@ authenticate is protected by a passphrase.
git_pillar_passphrase: mypassphrase
.. _pillar-merging-opts:
Pillar Merging Options
----------------------
.. conf_master:: pillar_source_merging_strategy
``pillar_source_merging_strategy``
----------------------------------
**********************************
.. versionadded:: 2014.7.0
@ -2511,7 +2515,7 @@ Default: ``smart``
The pillar_source_merging_strategy option allows you to configure merging
strategy between different sources. It accepts 4 values:
* recurse:
* ``recurse``:
it will merge recursively mapping of data. For example, theses 2 sources:
@ -2537,7 +2541,7 @@ strategy between different sources. It accepts 4 values:
element2: True
baz: quux
* aggregate:
* ``aggregate``:
instructs aggregation of elements between sources that use the #!yamlex renderer.
@ -2572,7 +2576,7 @@ strategy between different sources. It accepts 4 values:
- quux
- quux2
* overwrite:
* ``overwrite``:
Will use the behaviour of the 2014.1 branch and earlier.
@ -2602,14 +2606,14 @@ strategy between different sources. It accepts 4 values:
third_key: blah
fourth_key: blah
* smart (default):
* ``smart`` (default):
Guesses the best strategy based on the "renderer" setting.
.. conf_master:: pillar_merge_lists
``pillar_merge_lists``
----------------------
**********************
.. versionadded:: 2015.8.0
@ -2621,6 +2625,83 @@ Recursively merge lists by aggregating them instead of replacing them.
pillar_merge_lists: False
.. _pillar-cache-opts:
Pillar Cache Options
--------------------
.. conf_master:: pillar_cache
``pillar_cache``
****************
.. versionadded:: 2015.8.8
Default: ``False``
A master can cache pillars locally to bypass the expense of having to render them
for each minion on every request. This feature should only be enabled in cases
where pillar rendering time is known to be unsatisfactory and any attendant security
concerns about storing pillars in a master cache have been addressed.
When enabling this feature, be certain to read through the additional ``pillar_cache_*``
configuration options to fully understand the tunable parameters and their implications.
.. code-block:: yaml
pillar_cache: False
.. note::
Setting ``pillar_cache: True`` has no effect on
:ref:`targeting minions with pillar <targeting-pillar>`.
.. conf_master:: pillar_cache_ttl
``pillar_cache_ttl``
********************
.. versionadded:: 2015.8.8
Default: ``3600``
If and only if a master has set ``pillar_cache: True``, the cache TTL controls the amount
of time, in seconds, before the cache is considered invalid by a master and a fresh
pillar is recompiled and stored.
.. conf_master:: pillar_cache_backend
``pillar_cache_backend``
************************
.. versionadded:: 2015.8.8
Default: ``disk``
If an only if a master has set ``pillar_cache: True``, one of several storage providers
can be utilized:
* ``disk`` (default):
The default storage backend. This caches rendered pillars to the master cache.
Rendered pillars are serialized and deserialized as ``msgpack`` structures for speed.
Note that pillars are stored UNENCRYPTED. Ensure that the master cache has permissions
set appropriately (sane defaults are provided).
* ``memory`` [EXPERIMENTAL]:
An optional backend for pillar caches which uses a pure-Python
in-memory data structure for maximal performance. There are several caveats,
however. First, because each master worker contains its own in-memory cache,
there is no guarantee of cache consistency between minion requests. This
works best in situations where the pillar rarely if ever changes. Secondly,
and perhaps more importantly, this means that unencrypted pillars will
be accessible to any process which can examine the memory of the ``salt-master``!
This may represent a substantial security risk.
.. code-block:: yaml
pillar_cache_backend: disk
Syndic Server Settings
======================

Просмотреть файл

@ -16,6 +16,7 @@ Full list of builtin renderer modules
hjson
jinja
json
json5
mako
msgpack
py

Просмотреть файл

@ -0,0 +1,6 @@
====================
salt.renderers.json5
====================
.. automodule:: salt.renderers.json5
:members:

Просмотреть файл

@ -1,5 +1,5 @@
========================
OS Support for Cloud VMs
Cloud deployment scripts
========================
Salt Cloud works primarily by executing a script on the virtual machines as
@ -14,31 +14,39 @@ script. A stable version is included with each release tarball starting with
https://github.com/saltstack/salt-bootstrap
If you do not specify a script argument, this script will be used at the
default.
Note that, somewhat counter-intuitively, this script is referenced as
``bootstrap-salt`` in the configuration.
If the Salt Bootstrap script does not meet your needs, you may write your own.
The script should be written in bash and is a Jinja template. Deploy scripts
need to execute a number of functions to do a complete salt setup. These
functions include:
You can specify a deploy script in the cloud configuration file
(``/etc/salt/cloud`` by default):
1. Install the salt minion. If this can be done via system packages this method
is HIGHLY preferred.
2. Add the salt minion keys before the minion is started for the first time.
The minion keys are available as strings that can be copied into place in
the Jinja template under the dict named "vm".
3. Start the salt-minion daemon and enable it at startup time.
4. Set up the minion configuration file from the "minion" data available in
the Jinja template.
.. code-block:: yaml
A good, well commented, example of this process is the Fedora deployment
script:
script: bootstrap-salt
https://github.com/saltstack/salt-cloud/blob/master/saltcloud/deploy/Fedora.sh
A number of legacy deploy scripts are included with the release tarball. None
of them are as functional or complete as Salt Bootstrap, and are still included
for academic purposes.
Or in a provider:
.. code-block:: yaml
my-provider:
# snip...
script: bootstrap-salt
Or in a profile:
.. code-block:: yaml
my-profile:
provider: my-provider
# snip...
script: bootstrap-salt
If you do not specify a script argument in your cloud configuration file,
provider configuration or profile configuration, the "bootstrap-salt" script
will be used by default.
Other Generic Deploy Scripts
@ -61,6 +69,54 @@ refit to meet your needs. One important use of them is to pass options to
the salt-bootstrap script, such as updating to specific git tags.
Custom Deploy Scripts
=====================
If the Salt Bootstrap script does not meet your needs, you may write your own.
The script should be written in shell and is a Jinja template. Deploy scripts
need to execute a number of functions to do a complete salt setup. These
functions include:
1. Install the salt minion. If this can be done via system packages this method
is HIGHLY preferred.
2. Add the salt minion keys before the minion is started for the first time.
The minion keys are available as strings that can be copied into place in
the Jinja template under the dict named "vm".
3. Start the salt-minion daemon and enable it at startup time.
4. Set up the minion configuration file from the "minion" data available in
the Jinja template.
A good, well commented example of this process is the Fedora deployment
script:
https://github.com/saltstack/salt-cloud/blob/master/saltcloud/deploy/Fedora.sh
A number of legacy deploy scripts are included with the release tarball. None
of them are as functional or complete as Salt Bootstrap, and are still included
for academic purposes.
Custom deploy scripts are picked up from ``/etc/salt/cloud.deploy.d`` by
default, but you can change the location of deploy scripts with the cloud
configuration ``deploy_scripts_search_path``. Additionally, if your deploy
script has the extension ``.sh``, you can leave out the extension in your
configuration.
For example, if your custom deploy script is located in
``/etc/salt/cloud.deploy.d/my_deploy.sh``, you could specify it in a cloud
profile like this:
.. code-block:: yaml
my-profile:
provider: my-provider
# snip...
script: my_deploy
You're also free to use the full path to the script if you like. Using full
paths, your script doesn't have to live inside ``/etc/salt/cloud.deploy.d`` or
whatever you've configured with ``deploy_scripts_search_path``.
Post-Deploy Commands
====================

Просмотреть файл

@ -24,6 +24,18 @@ This package can be installed using `pip` or `easy_install`:
pip install pyvmomi
easy_install pyvmomi
.. note::
Version 6.0 of pyVmomi has some problems with SSL error handling on certain
versions of Python. If using version 6.0 of pyVmomi, the machine that you
are running the proxy minion process from must have either Python 2.7.9 or
newer This is due to an upstream dependency in pyVmomi 6.0 that is not supported
in Python version 2.6 to 2.7.8. If the version of Python running the salt-cloud
command is not in the supported range, you will need to install an earlier version
of pyVmomi. See `Issue #29537`_ for more information.
.. _Issue #29537: https://github.com/saltstack/salt/issues/29537
Configuration
=============

Просмотреть файл

@ -274,9 +274,9 @@ with labels.
``Awesome``
The pull request implements an especially well crafted solution, or a very difficult but necessary change.
``Low Hanging Fruit``
The issue is trivial or almost trivial to implement or fix. Issues having this label should be a good starting
place for new contributors to Salt.
``Help Wanted``
The issue appears to have a simple solution. Issues having this label
should be a good starting place for new contributors to Salt.
``Needs Testcase``
The issue or pull request relates to a feature that needs test coverage. The pull request containing the tests

Просмотреть файл

@ -60,7 +60,7 @@ The access controls are manifested using matchers in these configurations:
In the above example, fred is able to send commands only to minions which match
the specified glob target. This can be expanded to include other functions for
other minions based on standard targets.
other minions based on standard targets (all matchers are supported except the compound one).
.. code-block:: yaml
@ -84,4 +84,3 @@ unrestricted access to salt commands.
.. note::
Functions are matched using regular expressions.

Просмотреть файл

@ -45,10 +45,10 @@ passed, an empty list must be added:
Mine Functions Aliases
----------------------
Function aliases can be used to provide friendly names, usage intentions or to allow
multiple calls of the same function with different arguments. There is a different
syntax for passing positional and key-value arguments. Mixing positional and
key-value arguments is not supported.
Function aliases can be used to provide friendly names, usage intentions or to
allow multiple calls of the same function with different arguments. There is a
different syntax for passing positional and key-value arguments. Mixing
positional and key-value arguments is not supported.
.. versionadded:: 2014.7.0
@ -115,6 +115,20 @@ stored in a different location. Here is an example of a flat roster containing
of the Minion in question. This results in a non-trivial delay in
retrieving the requested data.
Minions Targeting with Mine
===========================
The ``mine.get`` function supports various methods of :ref:`Minions targeting
<targeting>` to fetch Mine data from particular hosts, such as glob or regular
expression matching on Minion id (name), grains, pillars and :ref:`compound
matches <targeting-compound>`. See the :py:mod:`salt.modules.mine` module
documentation for the reference.
.. note::
Pillar data needs to be cached on Master for pillar targeting to work with
Mine. Read the note in :ref:`relevant section <targeting-pillar>`.
Example
=======
@ -160,7 +174,7 @@ to add them to the pool of load balanced servers.
<...file contents snipped...>
{% for server, addrs in salt['mine.get']('roles:web', 'network.ip_addrs', expr_form='grain').items() %}
{% for server, addrs in salt['mine.get']('roles:web', 'network.ip_addrs', expr_form='grain') | dictsort() %}
server {{ server }} {{ addrs[0] }}:80 check
{% endfor %}

Просмотреть файл

@ -90,6 +90,22 @@ To execute a function, use :mod:`salt.function <salt.states.saltmod.function>`:
salt-run state.orchestrate orch.cleanfoo
If you omit the "name" argument, the ID of the state will be the default name,
or in the case of ``salt.function``, the execution module function to run. You
can specify the "name" argument to avoid conflicting IDs:
.. code-block:: yaml
copy_some_file:
salt.function:
- name: file.copy
- tgt: '*'
- arg:
- /path/to/file
- /tmp/copy_of_file
- kwarg:
- remove_existing: true
State
^^^^^
@ -138,8 +154,9 @@ unless prevented from doing so by any :doc:`requisites
.. code-block:: yaml
cmd.run:
bootstrap_servers:
salt.function:
- name: cmd.run
- tgt: 10.0.0.0/24
- tgt_type: ipcidr
- arg:

Просмотреть файл

@ -12,10 +12,24 @@ features </topics/releases/2016.3.0>`! Be sure to report any bugs you find on `G
Installing Using Packages
=========================
Builds for a few platforms are available as part of the RC here:
https://repo.saltstack.com/salt_rc/. For RHEL and Ubuntu, Follow the
instructions on https://repo.saltstack.com/, but prepend the URL paths with
``salt_rc/``.
Builds for a few platforms are available as part of the RC at:
- https://repo.saltstack.com/salt_rc/
- https://repo.saltstack.com/freebsd/salt_rc/
.. note::
For RHEL and Ubuntu, Follow the instructions on
https://repo.saltstack.com/, but insert ``salt_rc/`` into the URL between
the hostname and the remainder of the path. For example:
.. code-block::
baseurl=https://repo.saltstack.com/salt_rc/yum/redhat/$releasever/$basearch/
.. code-block::
deb http://repo.saltstack.com/salt_rc/apt/ubuntu/14.04/amd64 jessie main
Available builds:
@ -24,6 +38,7 @@ Available builds:
- RHEL 6
- RHEL 7
- Ubuntu 14.04
- FreeBSD
Installing Using Bootstrap
==========================

Просмотреть файл

@ -7,6 +7,18 @@ Targeting using Pillar
Pillar data can be used when targeting minions. This allows for ultimate
control and flexibility when targeting minions.
.. note::
To start using Pillar targeting it is required to make a Pillar
data cache on Salt Master for each Minion via following commands:
``salt '*' saltutil.refresh_pillar`` or ``salt '*' saltutil.sync_all``.
Also Pillar data cache will be populated during the
:ref:`highstate <running-highstate>` run. Once Pillar data changes, you
must refresh the cache by running above commands for this targeting
method to work correctly.
Example:
.. code-block:: bash
salt -I 'somekey:specialvalue' test.ping

Просмотреть файл

@ -63,26 +63,38 @@ be used to install it:
If pygit2_ is not packaged for the platform on which the Master is running, the
pygit2_ website has installation instructions here__. Keep in mind however that
following these instructions will install libgit2 and pygit2_ without system
following these instructions will install libgit2_ and pygit2_ without system
packages. Additionally, keep in mind that :ref:`SSH authentication in pygit2
<pygit2-authentication-ssh>` requires libssh2_ (*not* libssh) development
libraries to be present before libgit2 is built. On some distros (debian based)
``pkg-config`` is also required to link libgit2 with libssh2.
libraries to be present before libgit2_ is built. On some Debian-based distros
``pkg-config`` is also required to link libgit2_ with libssh2.
Additionally, version 0.21.0 of pygit2 introduced a dependency on python-cffi_,
which in turn depends on newer releases of libffi_. Upgrading libffi_ is not
advisable as several other applications depend on it, so on older LTS linux
releases pygit2_ 0.20.3 and libgit2_ 0.20.0 is the recommended combination.
While these are not packaged in the official repositories for Debian and
Ubuntu, SaltStack is actively working on adding packages for these to our
repositories_. The progress of this effort can be tracked here__.
.. warning::
pygit2_ is actively developed and :ref:`frequently makes
non-backwards-compatible API changes <pygit2-version-policy>`, even in
minor releases. It is not uncommon for pygit2_ upgrades to result in errors
in Salt. Please take care when upgrading pygit2_, and pay close attention
to the :ref:`changelog <pygit2-changelog>`, keeping an eye out for API
changes. Errors can be reported on the :ref:`SaltStack issue tracker
<saltstack-issue-tracker>`.
to the changelog_, keeping an eye out for API changes. Errors can be
reported on the :ref:`SaltStack issue tracker <saltstack-issue-tracker>`.
.. _pygit2-version-policy: http://www.pygit2.org/install.html#version-numbers
.. _pygit2-changelog: https://github.com/libgit2/pygit2#changelog
.. _changelog: https://github.com/libgit2/pygit2#changelog
.. _saltstack-issue-tracker: https://github.com/saltstack/salt/issues
.. __: http://www.pygit2.org/install.html
.. _libgit2: https://libgit2.github.com/
.. _libssh2: http://www.libssh2.org/
.. _python-cffi: https://pypi.python.org/pypi/cffi
.. _libffi: http://sourceware.org/libffi/
.. _repositories: https://repo.saltstack.com
.. __: https://github.com/saltstack/salt-pack/issues/70
GitPython
---------
@ -313,7 +325,12 @@ tremendous amount of customization. Here's some example usage:
- https://foo.com/foo.git
- https://foo.com/bar.git:
- root: salt
- mountpoint: salt://foo/bar/baz
- mountpoint: salt://bar
- base: salt-base
- https://foo.com/bar.git:
- name: second_bar_repo
- root: other/salt
- mountpoint: salt://other/bar
- base: salt-base
- http://foo.com/baz.git:
- root: salt/states
@ -330,26 +347,32 @@ tremendous amount of customization. Here's some example usage:
with a colon.
2. Per-remote configuration parameters are named like the global versions,
with the ``gitfs_`` removed from the beginning.
with the ``gitfs_`` removed from the beginning. The exception being the
``name`` parameter which is only available to per-remote configurations.
In the example configuration above, the following is true:
1. The first and third gitfs remotes will use the ``develop`` branch/tag as the
``base`` environment, while the second one will use the ``salt-base``
1. The first and fourth gitfs remotes will use the ``develop`` branch/tag as the
``base`` environment, while the second and third will use the ``salt-base``
branch/tag as the ``base`` environment.
2. The first remote will serve all files in the repository. The second
remote will only serve files from the ``salt`` directory (and its
subdirectories), while the third remote will only serve files from the
``salt/states`` directory (and its subdirectories).
subdirectories). The third remote will only server files from the
``other/salt`` directory (and its subdirectorys), while the fourth remote
will only serve files from the ``salt/states`` directory (and its
subdirectories).
3. The files from the second remote will be located under
``salt://foo/bar/baz``, while the files from the first and third remotes
will be located under the root of the Salt fileserver namespace
(``salt://``).
3. The first and fourth remotes will have files located under the root of the
Salt fileserver namespace (``salt://``). The files from the second remote
will be located under ``salt://bar``, while the files from the third remote
will be located under ``salt://other/bar``.
4. The third remote overrides the default behavior of :ref:`not authenticating to
insecure (non-HTTPS) remotes <gitfs-insecure-auth>`.
4. The second and third remotes reference the same repository and unique names
need to be declared for duplicate gitfs remotes.
5. The fourth remote overrides the default behavior of :ref:`not authenticating
to insecure (non-HTTPS) remotes <gitfs-insecure-auth>`.
Serving from a Subdirectory
===========================

Просмотреть файл

@ -110,7 +110,6 @@ the sample configuration file (default values)
recon_max: 5000
recon_randomize: True
- recon_default: the default value the socket should use, i.e. 1000. This value is in
milliseconds. (1000ms = 1 second)
- recon_max: the max value that the socket should use as a delay before trying to reconnect

Просмотреть файл

@ -29,8 +29,8 @@ for any OS with a Bourne shell:
.. code-block:: bash
curl -L https://bootstrap.saltstack.com -o install_salt.sh
sudo sh install_salt.sh
curl -L https://bootstrap.saltstack.com -o bootstrap_salt.sh
sudo sh bootstrap_salt.sh
See the `salt-bootstrap`_ documentation for other one liners. When using `Vagrant`_

Просмотреть файл

@ -533,8 +533,8 @@ This example clearly illustrates that; one, using the YAML renderer by default
is a wise decision and two, unbridled power can be obtained where needed by
using a pure Python SLS.
Running and debugging salt states.
----------------------------------
Running and Debugging Salt States
---------------------------------
Once the rules in an SLS are ready, they should be tested to ensure they
work properly. To invoke these rules, simply execute

Просмотреть файл

@ -129,12 +129,24 @@ modules.
The Salt module functions are also made available in the template context as
``salt:``
The following example illustrates calling the ``group_to_gid`` function in the
``file`` execution module with a single positional argument called
``some_group_that_exists``.
.. code-block:: jinja
moe:
user.present:
- gid: {{ salt['file.group_to_gid']('some_group_that_exists') }}
One way to think about this might be that the ``gid`` key is being assigned
a value equivelent to the following python pseudo-code:
.. code-block:: python
import salt.modules.file
file.group_to_gid('some_group_that_exists')
Note that for the above example to work, ``some_group_that_exists`` must exist
before the state file is processed by the templating engine.
@ -145,6 +157,9 @@ MAC address for eth0:
salt['network.hw_addr']('eth0')
To examine the possible arguments to each execution module function,
one can examine the `module reference documentation </ref/modules/all>`:
Advanced SLS module syntax
==========================

Просмотреть файл

@ -101,9 +101,9 @@ cp $PKGRESOURCES/scripts/com.saltstack.salt.syndic.plist $PKGDIR/Library/LaunchD
cp $PKGRESOURCES/scripts/com.saltstack.salt.api.plist $PKGDIR/Library/LaunchDaemons
############################################################################
# Remove pkg-config files from the distro
# Remove unnecessary files from the package
############################################################################
echo -n -e "\033]0;Build_Pkg: Remove pkg-config files\007"
echo -n -e "\033]0;Build_Pkg: Trim unneeded files\007"
sudo rm -rdf $PKGDIR/opt/salt/bin/pkg-config
sudo rm -rdf $PKGDIR/opt/salt/lib/pkgconfig
@ -111,6 +111,10 @@ sudo rm -rdf $PKGDIR/opt/salt/lib/engines
sudo rm -rdf $PKGDIR/opt/salt/share/aclocal
sudo rm -rdf $PKGDIR/opt/salt/share/doc
sudo rm -rdf $PKGDIR/opt/salt/share/man/man1/pkg-config.1
sudo rm -rdf $PKGDIR/opt/salt/lib/python2.7/test
echo -n -e "\033]0;Build_Pkg: Remove compiled python files\007"
sudo find $PKGDIR/opt/salt -name '*.pyc' -type f -delete
############################################################################
# Copy Additional Resources from Salt Repo to the Package Directory

Просмотреть файл

@ -22,5 +22,12 @@
<key>NumberOfFiles</key>
<integer>100000</integer>
</dict>
<!-- uncomment the lines below to debug launchd issues -->
<!--
<key>StandardOutPath</key>
<string>/tmp/salt-minion.out</string>
<key>StandardErrorPath</key>
<string>/tmp/salt-minion.err</string>
-->
</dict>
</plist>

Просмотреть файл

@ -22,5 +22,12 @@
<key>NumberOfFiles</key>
<integer>100000</integer>
</dict>
<!-- uncomment the lines below to debug launchd issues -->
<!--
<key>StandardOutPath</key>
<string>/tmp/salt-minion.out</string>
<key>StandardErrorPath</key>
<string>/tmp/salt-minion.err</string>
-->
</dict>
</plist>

Просмотреть файл

@ -22,5 +22,12 @@
<key>NumberOfFiles</key>
<integer>100000</integer>
</dict>
<!-- uncomment the lines below to debug launchd issues -->
<!--
<key>StandardOutPath</key>
<string>/tmp/salt-minion.out</string>
<key>StandardErrorPath</key>
<string>/tmp/salt-minion.err</string>
-->
</dict>
</plist>

Просмотреть файл

@ -22,5 +22,12 @@
<key>NumberOfFiles</key>
<integer>100000</integer>
</dict>
<!-- uncomment the lines below to debug launchd issues -->
<!--
<key>StandardOutPath</key>
<string>/tmp/salt-minion.out</string>
<key>StandardErrorPath</key>
<string>/tmp/salt-minion.err</string>
-->
</dict>
</plist>

44
pkg/windows/readme.md Normal file
Просмотреть файл

@ -0,0 +1,44 @@
### Packaged Plugin Licenses
The following dependencies are packaged with
salt for Windows with their corresponding licenses:
| Module | License |
|-----------|---------|
| backports-abc | --- |
| backports.ssl-match-hostname | PSF |
| certifi | ISC |
| cffi | MIT |
| CherryPy | BSD |
| enum34 | BSD |
| futures | BSD |
| gitdb | BSD |
| GitPython | BSD |
| idna | BSD-like |
| ioflo | Apache 2.0 |
| ioloop | MIT |
| ipaddress | PSF |
| Jinja2 | BSD |
| libnacl | --- |
| Mako | MIT |
| MarkupSafe | BSD |
| msgpack-python | --- |
| pip | MIT |
| psutil | BSD |
| pyasn1 | BSD |
| pycparser | BSD |
| pycurl | LGPL + MIT |
| PyMySQL | MIT |
| pypiwin32 | PSF |
| python-dateutil | Simplified BSD |
| python-gnupg | BSD |
| PyYAML | MIT |
| pyzmq | LGPL + BSD |
| requests | Apache 2.0 |
| setuptools | MIT |
| singledispatch | MIT |
| six | MIT |
| smmap | BSD |
| timelib | ZLIB/PHP |
| tornado | Apache 2.0 |
| wheel | MIT |
| WMI | MIT |

Просмотреть файл

@ -3,19 +3,23 @@ backports.ssl-match-hostname==3.5.0.1
certifi
cffi==1.5.0
CherryPy==5.0.1
enum34==1.1.3
futures==3.0.4
gitdb==0.6.4
GitPython==1.0.1
idna==2.0
ioflo==1.5.0
ioloop==0.1a0
ipaddress==1.0.16
Jinja2==2.8
libnacl==1.4.4
Mako==1.0.3
MarkupSafe==0.23
msgpack-python==0.4.7
psutil==3.4.2
pyasn1==0.1.9
pycparser==2.14
pycurl==7.43.0
PyMySQL==0.7.1
pypiwin32==219
python-dateutil==2.4.2
@ -26,7 +30,7 @@ requests==2.9.1
singledispatch==3.4.0.3
six==1.10.0
smmap==0.9.0
timelib==0.2.4
timelib==0.2.5
tornado==4.3
wheel==0.26.0
WMI==1.4.9

Просмотреть файл

@ -49,7 +49,7 @@ try:
except ImportError as exc:
if exc.args[0] != 'No module named _msgpack':
raise
from salt.exceptions import SaltSystemExit
from salt.exceptions import SaltSystemExit, SaltClientError, get_error_message
# Let's instantiate log using salt.log.setup.logging.getLogger() so pylint
@ -105,7 +105,7 @@ class DaemonsMixin(object): # pylint: disable=no-init
:return:
'''
log.exception('Failed to create environment for {d_name}: {reason}'.format(
d_name=self.__class__.__name__, reason=error.message))
d_name=self.__class__.__name__, reason=get_error_message(error)))
self.shutdown(error)
@ -354,6 +354,8 @@ class Minion(parsers.MinionOptionParser, DaemonsMixin): # pylint: disable=no-in
self.action_log_info('Starting up')
self.verify_hash_type()
self.minion.tune_in()
if self.minion.restart:
raise SaltClientError('Minion could not connect to Master')
except (KeyboardInterrupt, SaltSystemExit) as error:
self.action_log_info('Stopping')
if isinstance(error, KeyboardInterrupt):

Просмотреть файл

@ -417,7 +417,7 @@ class LocalClient(object):
.. code-block:: python
>>> returns = local.cmd_batch('*', 'state.highstate', bat='10%')
>>> returns = local.cmd_batch('*', 'state.highstate', batch='10%')
>>> for ret in returns:
... print(ret)
{'jerry': {...}}

Просмотреть файл

@ -103,8 +103,8 @@ class FunctionWrapper(object):
The remote execution function
'''
argv = [cmd]
argv.extend([str(arg) for arg in args])
argv.extend(['{0}={1}'.format(key, val) for key, val in six.iteritems(kwargs)])
argv.extend([json.dumps(arg) for arg in args])
argv.extend(['{0}={1}'.format(key, json.dumps(val)) for key, val in six.iteritems(kwargs)])
single = salt.client.ssh.Single(
self.opts,
argv,

Просмотреть файл

@ -50,6 +50,7 @@ import salt.utils
# Import salt.cloud libs
from salt.cloud.libcloudfuncs import * # pylint: disable=redefined-builtin,wildcard-import,unused-wildcard-import
from salt.utils import namespaced_function
import salt.utils.cloud
import salt.config as config
from salt.exceptions import (
@ -64,6 +65,24 @@ try:
except ImportError:
HAS_NETADDR = False
# Some of the libcloud functions need to be in the same namespace as the
# functions defined in the module, so we create new function objects inside
# this module namespace
get_size = namespaced_function(get_size, globals())
get_image = namespaced_function(get_image, globals())
avail_locations = namespaced_function(avail_locations, globals())
avail_images = namespaced_function(avail_images, globals())
avail_sizes = namespaced_function(avail_sizes, globals())
script = namespaced_function(script, globals())
destroy = namespaced_function(destroy, globals())
reboot = namespaced_function(reboot, globals())
list_nodes = namespaced_function(list_nodes, globals())
list_nodes_full = namespaced_function(list_nodes_full, globals())
list_nodes_select = namespaced_function(list_nodes_select, globals())
show_instance = namespaced_function(show_instance, globals())
get_node = namespaced_function(get_node, globals())
# Get logging started
log = logging.getLogger(__name__)
@ -72,7 +91,7 @@ __virtualname__ = 'dimensiondata'
def __virtual__():
'''
Set up the libcloud functions and check for GCE configurations.
Set up the libcloud functions and check for dimensiondata configurations.
'''
if get_configured_provider() is False:
return False

Просмотреть файл

@ -221,9 +221,9 @@ def list_nodes(conn=None, call=None):
ret = {}
nodes = list_nodes_full(conn, call)
for node in nodes:
ret[node] = {}
for prop in ('id', 'image', 'name', 'size', 'state', 'private_ips', 'public_ips'):
ret[node][prop] = nodes[node][prop]
ret[node] = {'name': node}
for prop in ('id', 'image', 'size', 'state', 'private_ips', 'public_ips'):
ret[node][prop] = nodes[node].get(prop)
return ret
@ -585,6 +585,7 @@ def create(vm_):
# Deleting two useless keywords
del vm_kwargs['deployment_slot']
del vm_kwargs['label']
del vm_kwargs['virtual_network_name']
result = conn.add_role(**vm_kwargs)
_wait_for_async(conn, result.request_id)
except Exception as exc:

Просмотреть файл

@ -865,7 +865,7 @@ DEFAULT_MINION_OPTS = {
'environment': None,
'pillarenv': None,
'pillar_opts': False,
# `pillar_cache` and `pillar_ttl`
# ``pillar_cache``, ``pillar_cache_ttl`` and ``pillar_cache_backend``
# are not used on the minion but are unavoidably in the code path
'pillar_cache': False,
'pillar_cache_ttl': 3600,
@ -925,8 +925,8 @@ DEFAULT_MINION_OPTS = {
'gitfs_passphrase': '',
'gitfs_env_whitelist': [],
'gitfs_env_blacklist': [],
'gitfs_ssl_verify': True,
'gitfs_global_lock': True,
'gitfs_ssl_verify': True,
'hash_type': 'md5',
'disable_modules': [],
'disable_returners': [],
@ -1120,8 +1120,8 @@ DEFAULT_MASTER_OPTS = {
'gitfs_passphrase': '',
'gitfs_env_whitelist': [],
'gitfs_env_blacklist': [],
'gitfs_ssl_verify': True,
'gitfs_global_lock': True,
'gitfs_ssl_verify': True,
'hgfs_remotes': [],
'hgfs_mountpoint': '',
'hgfs_root': '',

Просмотреть файл

@ -26,6 +26,13 @@ def _nested_output(obj):
return ret
def get_error_message(error):
'''
Get human readable message from Python Exception
'''
return error.args[0] if error.args else ''
class SaltException(Exception):
'''
Base exception class; all Salt-specific exceptions should subclass this

Просмотреть файл

@ -1273,14 +1273,19 @@ def os_data():
for line in fhr:
if 'enterprise' in line.lower():
grains['lsb_distrib_id'] = 'SLES'
grains['lsb_distrib_codename'] = re.sub(r'\(.+\)', '', line).strip()
elif 'version' in line.lower():
version = re.sub(r'[^0-9]', '', line)
elif 'patchlevel' in line.lower():
patch = re.sub(r'[^0-9]', '', line)
grains['lsb_distrib_release'] = version
if patch:
grains['lsb_distrib_release'] += ' SP' + patch
grains['lsb_distrib_codename'] = 'n.a'
grains['lsb_distrib_release'] += '.' + patch
patchstr = 'SP' + patch
if grains['lsb_distrib_codename'] and patchstr not in grains['lsb_distrib_codename']:
grains['lsb_distrib_codename'] += ' ' + patchstr
if not grains['lsb_distrib_codename']:
grains['lsb_distrib_codename'] = 'n.a'
elif os.path.isfile('/etc/altlinux-release'):
# ALT Linux
grains['lsb_distrib_id'] = 'altlinux'

Просмотреть файл

@ -634,12 +634,12 @@ def grains(opts, force_refresh=False, proxy=None):
print __grains__['id']
'''
# if we hae no grains, lets try loading from disk (TODO: move to decorator?)
cfn = os.path.join(
opts['cachedir'],
'grains.cache.p'
)
if not force_refresh:
if opts.get('grains_cache', False):
cfn = os.path.join(
opts['cachedir'],
'grains.cache.p'
)
if os.path.isfile(cfn):
grains_cache_age = int(time.time() - os.path.getmtime(cfn))
if opts.get('grains_cache_expiration', 300) >= grains_cache_age and not \

Просмотреть файл

@ -820,6 +820,7 @@ class Minion(MinionBase):
self.win_proc = []
self.loaded_base_name = loaded_base_name
self.connected = False
self.restart = False
if io_loop is None:
if HAS_ZMQ:
@ -1858,9 +1859,13 @@ class Minion(MinionBase):
# if eval_master finds a new master for us, self.connected
# will be True again on successful master authentication
master, self.pub_channel = yield self.eval_master(
opts=self.opts,
failed=True)
try:
master, self.pub_channel = yield self.eval_master(
opts=self.opts,
failed=True)
except SaltClientError:
pass
if self.connected:
self.opts['master'] = master
@ -1898,6 +1903,9 @@ class Minion(MinionBase):
schedule=schedule)
else:
self.schedule.delete_job(name='__master_failback', persist=True)
else:
self.restart = True
self.io_loop.stop()
elif tag.startswith('__master_connected'):
# handle this event only once. otherwise it will pollute the log
@ -2049,6 +2057,8 @@ class Minion(MinionBase):
if start:
try:
self.io_loop.start()
if self.restart:
self.destroy()
except (KeyboardInterrupt, RuntimeError): # A RuntimeError can be re-raised by Tornado on shutdown
self.destroy()

Просмотреть файл

@ -1702,7 +1702,7 @@ def mod_repo(repo, saltenv='base', **kwargs):
enabled configuration. Anything supplied for this list will be saved
in the repo configuration with a comment marker (#) in front.
.. versionadded:: 2016.3.1
.. versionadded:: 2015.8.9
.. note:: Due to the way keys are stored for APT, there is a known issue
where the key won't be updated unless another change is made

Просмотреть файл

@ -415,6 +415,7 @@ def enable_beacon(name, **kwargs):
ret['comment'] = 'Beacon {0} is not currently configured.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire']({'func': 'enable_beacon', 'name': name}, 'manage_beacons')
@ -467,6 +468,7 @@ def disable_beacon(name, **kwargs):
ret['comment'] = 'Beacon {0} is not currently configured.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire']({'func': 'disable_beacon', 'name': name}, 'manage_beacons')

Просмотреть файл

@ -12,6 +12,7 @@ import json
import logging
import salt.utils
import salt.utils.http
from salt.exceptions import get_error_message
__proxyenabled__ = ['chronos']
@ -109,10 +110,10 @@ def update_job(name, config):
log.debug('update response: %s', response)
return {'success': True}
except Exception as ex:
log.error('unable to update chronos job: %s', ex.message)
log.error('unable to update chronos job: %s', get_error_message(ex))
return {
'exception': {
'message': ex.message,
'message': get_error_message(ex),
}
}

Просмотреть файл

@ -17,6 +17,7 @@ import salt.utils.url
import salt.crypt
import salt.transport
from salt.exceptions import CommandExecutionError
from salt.ext.six.moves.urllib.parse import urlparse as _urlparse # pylint: disable=import-error,no-name-in-module
# Import 3rd-party libs
import salt.ext.six as six
@ -358,6 +359,25 @@ def cache_file(path, saltenv='base', env=None):
# Backwards compatibility
saltenv = env
contextkey = '{0}_|-{1}_|-{2}'.format('cp.cache_file', path, saltenv)
path_is_remote = _urlparse(path).scheme in ('http', 'https', 'ftp')
try:
if path_is_remote and contextkey in __context__:
# Prevent multiple caches in the same salt run. Affects remote URLs
# since the master won't know their hash, so the fileclient
# wouldn't be able to prevent multiple caches if we try to cache
# the remote URL more than once.
if os.path.isfile(__context__[contextkey]):
return __context__[contextkey]
else:
# File is in __context__ but no longer exists in the minion
# cache, get rid of the context key and re-cache below.
# Accounts for corner case where file is removed from minion
# cache between cp.cache_file calls in the same salt-run.
__context__.pop(contextkey)
except AttributeError:
pass
_mk_client()
path, senv = salt.utils.url.split_env(path)
@ -371,6 +391,10 @@ def cache_file(path, saltenv='base', env=None):
path, saltenv
)
)
if path_is_remote:
# Cache was successful, store the result in __context__ to prevent
# multiple caches (see above).
__context__[contextkey] = result
return result

Просмотреть файл

@ -52,7 +52,7 @@ import salt.utils.filebuffer
import salt.utils.files
import salt.utils.atomicfile
import salt.utils.url
from salt.exceptions import CommandExecutionError, SaltInvocationError
from salt.exceptions import CommandExecutionError, SaltInvocationError, get_error_message as _get_error_message
log = logging.getLogger(__name__)
@ -1364,7 +1364,7 @@ def _regex_to_static(src, regex):
try:
src = re.search(regex, src)
except Exception as ex:
raise CommandExecutionError("{0}: '{1}'".format(ex.message, regex))
raise CommandExecutionError("{0}: '{1}'".format(_get_error_message(ex), regex))
return src and src.group() or regex
@ -3305,6 +3305,10 @@ def source_list(source, source_hash, saltenv):
salt '*' file.source_list salt://http/httpd.conf '{hash_type: 'md5', 'hsum': <md5sum>}' base
'''
contextkey = '{0}_|-{1}_|-{2}'.format(source, source_hash, saltenv)
if contextkey in __context__:
return __context__[contextkey]
# get the master file list
if isinstance(source, list):
mfiles = [(f, saltenv) for f in __salt__['cp.list_master'](saltenv)]
@ -3338,10 +3342,7 @@ def source_list(source, source_hash, saltenv):
ret = (single_src, single_hash)
break
elif proto.startswith('http') or proto == 'ftp':
dest = salt.utils.mkstemp()
fn_ = __salt__['cp.get_url'](single_src, dest)
os.remove(fn_)
if fn_:
if __salt__['cp.cache_file'](single_src):
ret = (single_src, single_hash)
break
elif proto == 'file' and os.path.exists(urlparsed_single_src.path):
@ -3357,11 +3358,16 @@ def source_list(source, source_hash, saltenv):
if (path, senv) in mfiles or (path, senv) in mdirs:
ret = (single, source_hash)
break
urlparsed_source = _urlparse(single)
if urlparsed_source.scheme == 'file' and os.path.exists(urlparsed_source.path):
urlparsed_src = _urlparse(single)
proto = urlparsed_src.scheme
if proto == 'file' and os.path.exists(urlparsed_src.path):
ret = (single, source_hash)
break
if single.startswith('/') and os.path.exists(single):
elif proto.startswith('http') or proto == 'ftp':
if __salt__['cp.cache_file'](single):
ret = (single, source_hash)
break
elif single.startswith('/') and os.path.exists(single):
ret = (single, source_hash)
break
if ret is None:
@ -3369,10 +3375,11 @@ def source_list(source, source_hash, saltenv):
raise CommandExecutionError(
'none of the specified sources were found'
)
else:
return ret
else:
return source, source_hash
ret = (source, source_hash)
__context__[contextkey] = ret
return ret
def apply_template_on_contents(

Просмотреть файл

@ -113,7 +113,6 @@ def uninstall(cert_name,
.. code-block:: bash
salt '*' keychain.install test.p12 test123
'''
if keychain_password is not None:
unlock_keychain(keychain, keychain_password)
@ -135,7 +134,6 @@ def list_certs(keychain="/Library/Keychains/System.keychain"):
.. code-block:: bash
salt '*' keychain.list_certs
'''
cmd = 'security find-certificate -a {0} | grep -o "alis".*\\" | ' \
'grep -o \'\\"[-A-Za-z0-9.:() ]*\\"\''.format(_quote(keychain))
@ -162,7 +160,6 @@ def get_friendly_name(cert, password):
.. code-block:: bash
salt '*' keychain.get_friendly_name /tmp/test.p12 test123
'''
cmd = 'openssl pkcs12 -in {0} -passin pass:{1} -info -nodes -nokeys 2> /dev/null | ' \
'grep friendlyName:'.format(_quote(cert), _quote(password))
@ -185,7 +182,6 @@ def get_default_keychain(user=None, domain="user"):
.. code-block:: bash
salt '*' keychain.get_default_keychain
'''
cmd = "security default-keychain -d {0}".format(domain)
return __salt__['cmd.run'](cmd, runas=user)
@ -209,7 +205,6 @@ def set_default_keychain(keychain, domain="user", user=None):
.. code-block:: bash
salt '*' keychain.set_keychain /Users/fred/Library/Keychains/login.keychain
'''
cmd = "security default-keychain -d {0} -s {1}".format(domain, keychain)
return __salt__['cmd.run'](cmd, runas=user)
@ -233,7 +228,6 @@ def unlock_keychain(keychain, password):
.. code-block:: bash
salt '*' keychain.unlock_keychain /tmp/test.p12 test123
'''
cmd = 'security unlock-keychain -p {0} {1}'.format(password, keychain)
__salt__['cmd.run'](cmd)
@ -250,6 +244,12 @@ def get_hash(name, password=None):
password
The password that is used in the certificate. Only required if your passing a p12 file.
Note: This will be outputted to logs
CLI Example:
.. code-block:: bash
salt '*' keychain.get_hash /tmp/test.p12 test123
'''
if '.p12' in name[-4:]:

Просмотреть файл

@ -25,7 +25,7 @@ from salt.ext.six import string_types
# Import salt libs
import salt.utils
import salt.utils.decorators as decorators
from salt.utils.locales import sdecode
from salt.utils.locales import sdecode as _sdecode
from salt.exceptions import CommandExecutionError, SaltInvocationError
log = logging.getLogger(__name__)
@ -140,7 +140,7 @@ def delete(name, remove=False, force=False):
.. code-block:: bash
salt '*' user.delete foo
salt '*' user.delete name remove=True force=True
'''
if salt.utils.contains_whitespace(name):
raise SaltInvocationError('Username cannot contain whitespace')
@ -298,12 +298,12 @@ def chfullname(name, fullname):
salt '*' user.chfullname foo 'Foo Bar'
'''
if isinstance(fullname, string_types):
fullname = sdecode(fullname)
fullname = _sdecode(fullname)
pre_info = info(name)
if not pre_info:
raise CommandExecutionError('User \'{0}\' does not exist'.format(name))
if isinstance(pre_info['fullname'], string_types):
pre_info['fullname'] = sdecode(pre_info['fullname'])
pre_info['fullname'] = _sdecode(pre_info['fullname'])
if fullname == pre_info['fullname']:
return True
_dscl(
@ -319,7 +319,7 @@ def chfullname(name, fullname):
current = info(name).get('fullname')
if isinstance(current, string_types):
current = sdecode(current)
current = _sdecode(current)
return current == fullname

Просмотреть файл

@ -12,6 +12,7 @@ import json
import logging
import salt.utils
import salt.utils.http
from salt.exceptions import get_error_message
__proxyenabled__ = ['marathon']
@ -125,10 +126,10 @@ def update_app(id, config):
log.debug('update response: %s', response)
return response['dict']
except Exception as ex:
log.error('unable to update marathon app: %s', ex.message)
log.error('unable to update marathon app: %s', get_error_message(ex))
return {
'exception': {
'message': ex.message,
'message': get_error_message(ex),
}
}

Просмотреть файл

@ -22,6 +22,7 @@ import json
# Import salt libs
from salt.ext.six import string_types
from salt.exceptions import get_error_message as _get_error_message
# Import third party libs
@ -428,6 +429,16 @@ def insert(objects, collection, user=None, password=None,
def find(collection, query=None, user=None, password=None,
host=None, port=None, database='admin'):
"""
Find an object or list of objects in a collection
CLI Example:
.. code-block:: bash
salt '*' mongodb.find mycollection '[{"foo": "FOO", "bar": "BAR"}]' <user> <password> <host> <port> <database>
"""
conn = _connect(user, password, host, port, database)
if not conn:
return 'Failed to connect to mongo database'
@ -444,7 +455,7 @@ def find(collection, query=None, user=None, password=None,
ret = col.find(query)
return list(ret)
except pymongo.errors.PyMongoError as err:
log.error("Removing objects failed with error: %s", err)
log.error("Searching objects failed with error: %s", err)
return err
@ -467,7 +478,7 @@ def remove(collection, query=None, user=None, password=None,
try:
query = _to_dict(query)
except Exception as err:
return err.message
return _get_error_message(err)
try:
log.info("Removing %r from %s", query, collection)
@ -476,5 +487,5 @@ def remove(collection, query=None, user=None, password=None,
ret = col.remove(query, w=w)
return "{0} objects removed".format(ret['n'])
except pymongo.errors.PyMongoError as err:
log.error("Removing objects failed with error: %s", err.message)
return err.message
log.error("Removing objects failed with error: %s", _get_error_message(err))
return _get_error_message(err)

Просмотреть файл

@ -26,6 +26,7 @@ from salt.modules.inspectlib.exceptions import (InspectorQueryException,
import salt.utils
import salt.utils.fsutils
from salt.exceptions import CommandExecutionError
from salt.exceptions import get_error_message as _get_error_message
log = logging.getLogger(__name__)
@ -92,7 +93,7 @@ def inspect(mode='all', priority=19, **kwargs):
except InspectorSnapshotException as ex:
raise CommandExecutionError(ex)
except Exception as ex:
log.error(ex.message)
log.error(_get_error_message(ex))
raise Exception(ex)
@ -157,5 +158,5 @@ def query(scope, **kwargs):
except InspectorQueryException as ex:
raise CommandExecutionError(ex)
except Exception as ex:
log.error(ex.message)
log.error(_get_error_message(ex))
raise Exception(ex)

Просмотреть файл

@ -599,7 +599,7 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914
cur_version = __salt__['pip.version'](bin_env)
if not salt.utils.compare_versions(ver1=cur_version, oper='>=',
ver2=min_version):
log.error(
logger.error(
('The --use-wheel option is only supported in pip {0} and '
'newer. The version of pip detected is {1}. This option '
'will be ignored.'.format(min_version, cur_version))
@ -612,7 +612,7 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914
cur_version = __salt__['pip.version'](bin_env)
if not salt.utils.compare_versions(ver1=cur_version, oper='>=',
ver2=min_version):
log.error(
logger.error(
('The --no-use-wheel option is only supported in pip {0} and '
'newer. The version of pip detected is {1}. This option '
'will be ignored.'.format(min_version, cur_version))

Просмотреть файл

@ -129,6 +129,12 @@ def get_http_proxy(network_service="Ethernet"):
network_service
The network service to apply the changes to, this only necessary on OSX
CLI Example:
.. code-block:: bash
salt '*' proxy.get_http_proxy Ethernet
'''
if __grains__['os'] == 'Windows':
return _get_proxy_windows(['http'])
@ -159,6 +165,12 @@ def set_http_proxy(server, port, user=None, password=None, network_service="Ethe
bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use
set_proxy_bypass to edit the bypass hosts.
CLI Example:
.. code-block:: bash
salt '*' proxy.set_http_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
'''
if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['http'], bypass_hosts)
@ -172,6 +184,12 @@ def get_https_proxy(network_service="Ethernet"):
network_service
The network service to apply the changes to, this only necessary on OSX
CLI Example:
.. code-block:: bash
salt '*' proxy.get_https_proxy Ethernet
'''
if __grains__['os'] == 'Windows':
return _get_proxy_windows(['https'])
@ -202,6 +220,12 @@ def set_https_proxy(server, port, user=None, password=None, network_service="Eth
bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use
set_proxy_bypass to edit the bypass hosts.
CLI Example:
.. code-block:: bash
salt '*' proxy.set_https_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
'''
if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['https'], bypass_hosts)
@ -215,6 +239,12 @@ def get_ftp_proxy(network_service="Ethernet"):
network_service
The network service to apply the changes to, this only necessary on OSX
CLI Example:
.. code-block:: bash
salt '*' proxy.get_ftp_proxy Ethernet
'''
if __grains__['os'] == 'Windows':
return _get_proxy_windows(['ftp'])
@ -244,6 +274,12 @@ def set_ftp_proxy(server, port, user=None, password=None, network_service="Ether
bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use
set_proxy_bypass to edit the bypass hosts.
CLI Example:
.. code-block:: bash
salt '*' proxy.set_ftp_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
'''
if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['ftp'], bypass_hosts)
@ -317,6 +353,12 @@ def set_proxy_win(server, port, types=None, bypass_hosts=None):
bypass_hosts
The hosts that are allowed to by pass the proxy.
CLI Example:
.. code-block:: bash
salt '*' proxy.set_http_proxy example.com 1080 types="['http', 'https']"
'''
if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, types, bypass_hosts)
@ -325,6 +367,12 @@ def set_proxy_win(server, port, types=None, bypass_hosts=None):
def get_proxy_win():
'''
Gets all of the proxy settings in one call, only available on Windows
CLI Example:
.. code-block:: bash
salt '*' proxy.get_proxy_win
'''
if __grains__['os'] == 'Windows':
return _get_proxy_windows()

Просмотреть файл

@ -49,6 +49,7 @@ import salt.ext.six as six
# Import salt libs
import salt.utils
from salt.exceptions import CommandExecutionError
from salt.utils import locales
log = logging.getLogger(__name__)
@ -81,10 +82,10 @@ def _get_gecos(name):
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4:
gecos_field.append('')
return {'fullname': str(gecos_field[0]),
'roomnumber': str(gecos_field[1]),
'workphone': str(gecos_field[2]),
'homephone': str(gecos_field[3])}
return {'fullname': locales.sdecode(gecos_field[0]),
'roomnumber': locales.sdecode(gecos_field[1]),
'workphone': locales.sdecode(gecos_field[2]),
'homephone': locales.sdecode(gecos_field[3])}
def _build_gecos(gecos_dict):
@ -92,7 +93,7 @@ def _build_gecos(gecos_dict):
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with pw usermod.
'''
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname', ''),
return u'{0},{1},{2},{3}'.format(gecos_dict.get('fullname', ''),
gecos_dict.get('roomnumber', ''),
gecos_dict.get('workphone', ''),
gecos_dict.get('homephone', ''))
@ -448,7 +449,7 @@ def get_loginclass(name):
'''
userinfo = __salt__['cmd.run_stdout']('pw usershow -n {0}'.format(name))
userinfo = __salt__['cmd.run_stdout'](['pw', 'usershow', '-n', name])
userinfo = userinfo.split(':')
return {'loginclass': userinfo[4] if len(userinfo) == 10 else ''}

Просмотреть файл

@ -23,6 +23,7 @@ import salt.utils
import salt.utils.decorators as decorators
from salt.ext import six
from salt.exceptions import CommandExecutionError
from salt.utils import locales
log = logging.getLogger(__name__)
@ -51,10 +52,10 @@ def _get_gecos(name):
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4:
gecos_field.append('')
return {'fullname': str(gecos_field[0]),
'roomnumber': str(gecos_field[1]),
'workphone': str(gecos_field[2]),
'homephone': str(gecos_field[3])}
return {'fullname': locales.sdecode(gecos_field[0]),
'roomnumber': locales.sdecode(gecos_field[1]),
'workphone': locales.sdecode(gecos_field[2]),
'homephone': locales.sdecode(gecos_field[3])}
def _build_gecos(gecos_dict):
@ -62,7 +63,7 @@ def _build_gecos(gecos_dict):
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with usermod.
'''
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname', ''),
return u'{0},{1},{2},{3}'.format(gecos_dict.get('fullname', ''),
gecos_dict.get('roomnumber', ''),
gecos_dict.get('workphone', ''),
gecos_dict.get('homephone', ''))

Просмотреть файл

@ -155,7 +155,7 @@ class daclConstants(object):
'THIS FOLDER ONLY': {
'TEXT': 'this file/folder only',
'BITS': win32security.NO_INHERITANCE},
'THIS FOLDER, SUBFOLDERS, and FILES': {
'THIS FOLDER, SUBFOLDERS, AND FILES': {
'TEXT': 'this folder, subfolders, and files',
'BITS': win32security.CONTAINER_INHERIT_ACE |
win32security.OBJECT_INHERIT_ACE},

Просмотреть файл

@ -1023,7 +1023,10 @@ def install(name=None,
log.warning('"version" parameter will be ignored for multiple '
'package targets')
old = list_pkgs()
old = list_pkgs(versions_as_list=False)
# Use of __context__ means no duplicate work here, just accessing
# information already in __context__ from the previous call to list_pkgs()
old_as_list = list_pkgs(versions_as_list=True)
targets = []
downgrade = []
to_reinstall = {}
@ -1095,20 +1098,54 @@ def install(name=None,
else:
pkgstr = pkgpath
cver = old.get(pkgname, '')
if reinstall and cver \
and salt.utils.compare_versions(ver1=version_num,
oper='==',
ver2=cver,
cmp_func=version_cmp):
to_reinstall[pkgname] = pkgstr
elif not cver or salt.utils.compare_versions(ver1=version_num,
oper='>=',
ver2=cver,
cmp_func=version_cmp):
targets.append(pkgstr)
# Lambda to trim the epoch from the currently-installed version if
# no epoch is specified in the specified version
norm_epoch = lambda x, y: x.split(':', 1)[-1] \
if ':' not in y \
else x
cver = old_as_list.get(pkgname, [])
if reinstall and cver:
for ver in cver:
ver = norm_epoch(ver, version_num)
if salt.utils.compare_versions(ver1=version_num,
oper='==',
ver2=ver,
cmp_func=version_cmp):
# This version is already installed, so we need to
# reinstall.
to_reinstall[pkgname] = pkgstr
break
else:
downgrade.append(pkgstr)
if not cver:
targets.append(pkgstr)
else:
for ver in cver:
ver = norm_epoch(ver, version_num)
if salt.utils.compare_versions(ver1=version_num,
oper='>=',
ver2=ver,
cmp_func=version_cmp):
targets.append(pkgstr)
break
else:
if re.match('kernel(-.+)?', name):
# kernel and its subpackages support multiple
# installs as their paths do not conflict.
# Performing a yum/dnf downgrade will be a no-op
# so just do an install instead. It will fail if
# there are other interdependencies that have
# conflicts, and that's OK. We don't want to force
# anything, we just want to properly handle it if
# someone tries to install a kernel/kernel-devel of
# a lower version than the currently-installed one.
# TODO: find a better way to determine if a package
# supports multiple installs.
targets.append(pkgstr)
else:
# None of the currently-installed versions are
# greater than the specified version, so this is a
# downgrade.
downgrade.append(pkgstr)
def _add_common_args(cmd):
'''
@ -1167,7 +1204,7 @@ def install(name=None,
errors.append(out['stdout'])
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
new = list_pkgs(versions_as_list=False)
ret = salt.utils.compare_dicts(old, new)

Просмотреть файл

@ -254,7 +254,7 @@ class SaltClientsMixIn(object):
# not the actual client we'll use.. but its what we'll use to get args
'local_batch': local_client.cmd_batch,
'local_async': local_client.run_job,
'runner': salt.runner.RunnerClient(opts=self.application.opts).async,
'runner': salt.runner.RunnerClient(opts=self.application.opts).cmd_async,
'runner_async': None, # empty, since we use the same client as `runner`
}
return SaltClientsMixIn.__saltclients
@ -895,8 +895,6 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # pylint: disable=W
def disbatch(self):
'''
Disbatch all lowstates to the appropriate clients
Auth must have been verified before this point
'''
ret = []
@ -905,16 +903,23 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # pylint: disable=W
if not self._verify_client(low):
return
for low in self.lowstate:
# make sure that the chunk has a token, if not we can't do auth per-request
# Note: this means that you can send different tokens per lowstate
# as long as the base token (to auth with the API) is valid
if 'token' not in low:
# Make sure we have 'token' or 'username'/'password' in each low chunk.
# Salt will verify the credentials are correct.
if self.token is not None and 'token' not in low:
low['token'] = self.token
if not (('token' in low)
or ('username' in low and 'password' in low and 'eauth' in low)):
ret.append('Failed to authenticate')
break
# disbatch to the correct handler
try:
chunk_ret = yield getattr(self, '_disbatch_{0}'.format(low['client']))(low)
ret.append(chunk_ret)
except EauthAuthenticationError as exc:
ret.append('Failed to authenticate')
break
except Exception as ex:
ret.append('Unexpected exception while handling request: {0}'.format(ex))
logger.error('Unexpected exception while handling request:', exc_info=True)
@ -1112,8 +1117,7 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # pylint: disable=W
'''
Disbatch runner client commands
'''
f_call = {'args': [chunk['fun'], chunk]}
pub_data = self.saltclients['runner'](chunk['fun'], chunk)
pub_data = self.saltclients['runner'](chunk)
tag = pub_data['tag'] + '/ret'
try:
event = yield self.application.event_listener.get_event(self, tag=tag)
@ -1128,8 +1132,7 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # pylint: disable=W
'''
Disbatch runner client_async commands
'''
f_call = {'args': [chunk['fun'], chunk]}
pub_data = self.saltclients['runner'](chunk['fun'], chunk)
pub_data = self.saltclients['runner'](chunk)
raise tornado.gen.Return(pub_data)

Просмотреть файл

@ -41,7 +41,7 @@ def get_pillar(opts, grains, minion_id, saltenv=None, ext=None, funcs=None,
'local': Pillar
}.get(opts['file_client'], Pillar)
# If local pillar and we're caching, run through the cache system first
log.info('Determining pillar cache')
log.debug('Determining pillar cache')
if opts['pillar_cache']:
log.info('Compiling pillar from cache')
log.debug('get_pillar using pillar cache with ext: {0}'.format(ext))

54
salt/renderers/json5.py Normal file
Просмотреть файл

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
'''
JSON5 Renderer for Salt
.. versionadded:: 2016.3.0
JSON5 is an unofficial extension to JSON. See http://json5.org/ for more
information.
This renderer requires the `json5 python bindings`__, installable via pip.
.. __: https://pypi.python.org/pypi/json5
'''
from __future__ import absolute_import
# Import python libs
import logging
try:
import json5 as json
HAS_JSON5 = True
except ImportError:
HAS_JSON5 = False
# Import salt libs
from salt.ext.six import string_types
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'json5'
def __virtual__():
if not HAS_JSON5:
return (False, 'json5 module not installed')
return __virtualname__
def render(json_data, saltenv='base', sls='', **kws):
'''
Accepts JSON as a string or as a file object and runs it through the JSON
parser.
:rtype: A Python data structure
'''
if not isinstance(json_data, string_types):
json_data = json_data.read()
if json_data.startswith('#!'):
json_data = json_data[(json_data.find('\n') + 1):]
if not json_data.strip():
return {}
return json.loads(json_data)

Просмотреть файл

@ -393,6 +393,8 @@ class SPMClient(object):
can_has = {}
cant_has = []
if 'dependencies' in formula_def and formula_def['dependencies'] is None:
formula_def['dependencies'] = ''
for dep in formula_def.get('dependencies', '').split(','):
dep = dep.strip()
if not dep:

Просмотреть файл

@ -16,10 +16,10 @@ from contextlib import closing
# Import 3rd-party libs
import salt.ext.six as six
# # Use salt.utils.fopen
# Import salt libs
from salt.exceptions import CommandExecutionError
import salt.utils
log = logging.getLogger(__name__)
__virtualname__ = 'archive'
@ -239,14 +239,31 @@ def extracted(name,
__env__,
'{0}.{1}'.format(re.sub('[:/\\\\]', '_', if_missing),
archive_format))
if __opts__['test']:
source_match = source
else:
try:
source_match = __salt__['file.source_list'](source,
source_hash,
__env__)[0]
except CommandExecutionError as exc:
ret['result'] = False
ret['comment'] = exc.strerror
return ret
if not os.path.exists(filename):
if __opts__['test']:
ret['result'] = None
ret['comment'] = \
'Archive {0} would have been downloaded in cache'.format(source)
'{0} {1} would be downloaded to cache'.format(
'One of' if not isinstance(source_match, six.string_types)
else 'Archive',
source_match
)
return ret
log.debug('Archive file {0} is not in cache, download it'.format(source))
log.debug('%s is not in cache, downloading it', source_match)
file_result = __salt__['state.single']('file.managed',
filename,
source=source,
@ -269,17 +286,21 @@ def extracted(name,
log.debug('failed to download {0}'.format(source))
return file_result
else:
log.debug('Archive file {0} is already in cache'.format(name))
log.debug('Archive %s is already in cache', name)
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Archive {0} would have been extracted in {1}'.format(
source, name)
ret['comment'] = '{0} {1} would be extracted to {2}'.format(
'One of' if not isinstance(source_match, six.string_types)
else 'Archive',
source_match,
name
)
return ret
__salt__['file.makedirs'](name, user=user, group=group)
log.debug('Extract {0} in {1}'.format(filename, name))
log.debug('Extracting {0} to {1}'.format(filename, name))
if archive_format == 'zip':
files = __salt__['archive.unzip'](filename, name, options=zip_options, trim_output=trim_output, password=password)
elif archive_format == 'rar':
@ -327,7 +348,7 @@ def extracted(name,
# Note: We do this here because we might not have access to the cachedir.
if user or group:
dir_result = __salt__['state.single']('file.directory',
name,
if_missing,
user=user,
group=group,
recurse=['user', 'group'])
@ -337,7 +358,7 @@ def extracted(name,
ret['result'] = True
ret['changes']['directories_created'] = [name]
ret['changes']['extracted_files'] = files
ret['comment'] = '{0} extracted in {1}'.format(source, name)
ret['comment'] = '{0} extracted to {1}'.format(source_match, name)
if not keep:
os.unlink(filename)
if source_hash and source_hash_update:
@ -346,5 +367,5 @@ def extracted(name,
else:
__salt__['file.remove'](if_missing)
ret['result'] = False
ret['comment'] = 'Can\'t extract content of {0}'.format(source)
ret['comment'] = 'Can\'t extract content of {0}'.format(source_match)
return ret

Просмотреть файл

@ -329,7 +329,7 @@ def mounted(name,
# convert uid/gid to numeric value from user/group name
name_id_opts = {'uid': 'user.info',
'gid': 'group.info'}
if opt.split('=')[0] in name_id_opts:
if opt.split('=')[0] in name_id_opts and len(opt.split('=')) > 1:
_givenid = opt.split('=')[1]
_param = opt.split('=')[0]
_id = _givenid

Просмотреть файл

@ -122,6 +122,11 @@ def state(
queue
Pass ``queue=true`` through to the state function
batch
Execute the command :ref:`in batches <targeting-batch>`. E.g.: ``10%``.
.. versionadded:: 2016.3.0
Examples:
Run a list of sls files via :py:func:`state.sls <salt.state.sls>` on target

Просмотреть файл

@ -31,7 +31,7 @@ import logging
# Import salt libs
import salt.utils
from salt.utils.locales import sdecode
from salt.utils.locales import sdecode, sdecode_if_string
# Import 3rd-party libs
from salt.ext.six import string_types, iteritems
@ -149,10 +149,8 @@ def _changes(name,
change['expire'] = expire
# GECOS fields
if isinstance(fullname, string_types):
fullname = sdecode(fullname)
if isinstance(lusr['fullname'], string_types):
lusr['fullname'] = sdecode(lusr['fullname'])
fullname = sdecode_if_string(fullname)
lusr['fullname'] = sdecode_if_string(lusr['fullname'])
if fullname is not None and lusr['fullname'] != fullname:
change['fullname'] = fullname
if win_homedrive and lusr['homedrive'] != win_homedrive:
@ -167,17 +165,23 @@ def _changes(name,
# MacOS doesn't have full GECOS support, so check for the "ch" functions
# and ignore these parameters if these functions do not exist.
if 'user.chroomnumber' in __salt__ \
and roomnumber is not None \
and lusr['roomnumber'] != roomnumber:
change['roomnumber'] = roomnumber
and roomnumber is not None:
roomnumber = sdecode_if_string(roomnumber)
lusr['roomnumber'] = sdecode_if_string(lusr['roomnumber'])
if lusr['roomnumber'] != roomnumber:
change['roomnumber'] = roomnumber
if 'user.chworkphone' in __salt__ \
and workphone is not None \
and lusr['workphone'] != workphone:
change['workphone'] = workphone
and workphone is not None:
workphone = sdecode_if_string(workphone)
lusr['workphone'] = sdecode_if_string(lusr['workphone'])
if lusr['workphone'] != workphone:
change['workphone'] = workphone
if 'user.chhomephone' in __salt__ \
and homephone is not None \
and lusr['homephone'] != homephone:
change['homephone'] = homephone
and homephone is not None:
homephone = sdecode_if_string(homephone)
lusr['homephone'] = sdecode_if_string(lusr['homephone'])
if lusr['homephone'] != homephone:
change['homephone'] = homephone
# OpenBSD/FreeBSD login class
if __grains__['kernel'] in ('OpenBSD', 'FreeBSD'):
if loginclass:
@ -473,7 +477,7 @@ def present(name,
for key, val in iteritems(changes):
if key == 'password':
val = 'XXX-REDACTED-XXX'
ret['comment'] += '{0}: {1}\n'.format(key, val)
ret['comment'] += u'{0}: {1}\n'.format(key, val)
return ret
# The user is present
if 'shadow.info' in __salt__:

Просмотреть файл

@ -14,6 +14,8 @@ import os
import salt.version
import salt.utils
from salt.exceptions import CommandNotFoundError
from salt.ext import six
log = logging.getLogger(__name__)
@ -160,19 +162,24 @@ def managed(name,
return ret
if not venv_exists or (venv_exists and clear):
_ret = __salt__['virtualenv.create'](
name,
venv_bin=venv_bin,
system_site_packages=system_site_packages,
distribute=distribute,
clear=clear,
python=python,
extra_search_dir=extra_search_dir,
never_download=never_download,
prompt=prompt,
user=user,
use_vt=use_vt,
)
try:
_ret = __salt__['virtualenv.create'](
name,
venv_bin=venv_bin,
system_site_packages=system_site_packages,
distribute=distribute,
clear=clear,
python=python,
extra_search_dir=extra_search_dir,
never_download=never_download,
prompt=prompt,
user=user,
use_vt=use_vt,
)
except CommandNotFoundError as err:
ret['result'] = False
ret['comment'] = 'Failed to create virtualenv: {0}'.formar(err)
return ret
if _ret['retcode'] != 0:
ret['result'] = False

Просмотреть файл

@ -801,7 +801,7 @@ def check_or_die(command):
raise CommandNotFoundError('\'None\' is not a valid command.')
if not which(command):
raise CommandNotFoundError(command)
raise CommandNotFoundError('\'{0}\' is not in the path'.format(command))
def backup_minion(path, bkroot):

Просмотреть файл

@ -2671,13 +2671,11 @@ def update_bootstrap(config, url=None):
'''
Update the salt-bootstrap script
url can be either:
- The URL to fetch the bootstrap script from
- The absolute path to the bootstrap
- The content of the bootstrap script
url can be one of:
- The URL to fetch the bootstrap script from
- The absolute path to the bootstrap
- The content of the bootstrap script
'''
default_url = config.get('bootstrap_script_url',
'https://bootstrap.saltstack.com')

Просмотреть файл

@ -19,6 +19,18 @@ import subprocess
import time
from datetime import datetime
# Import salt libs
import salt.utils
import salt.utils.itertools
import salt.utils.url
import salt.fileserver
from salt.utils.process import os_is_running as pid_exists
from salt.exceptions import FileserverConfigError, GitLockError, get_error_message
from salt.utils.event import tagify
# Import third party libs
import salt.ext.six as six
VALID_PROVIDERS = ('gitpython', 'pygit2', 'dulwich')
# Optional per-remote params that can only be used on a per-remote basis, and
# thus do not have defaults in salt/config.py.
@ -54,17 +66,8 @@ _INVALID_REPO = (
'master to continue to use this {1} remote.'
)
# Import salt libs
import salt.utils
import salt.utils.itertools
import salt.utils.url
import salt.fileserver
from salt.utils.process import os_is_running as pid_exists
from salt.exceptions import FileserverConfigError, GitLockError
from salt.utils.event import tagify
log = logging.getLogger(__name__)
# Import third party libs
import salt.ext.six as six
# pylint: disable=import-error
try:
import git
@ -80,8 +83,13 @@ try:
GitError = pygit2.errors.GitError
except AttributeError:
GitError = Exception
except ImportError:
HAS_PYGIT2 = False
except Exception as err: # cffi VerificationError also may happen
HAS_PYGIT2 = False # and pygit2 requrests re-compilation
# on a production system (!),
# but cffi might be absent as well!
# Therefore just a generic Exception class.
if not isinstance(err, ImportError):
log.error('Import pygit2 failed: {0}'.format(err))
try:
import dulwich.errors
@ -94,8 +102,6 @@ except ImportError:
HAS_DULWICH = False
# pylint: enable=import-error
log = logging.getLogger(__name__)
# Minimum versions for backend providers
GITPYTHON_MINVER = '0.3'
PYGIT2_MINVER = '0.20.3'
@ -1289,9 +1295,7 @@ class Pygit2(GitProvider):
try:
fetch_results = origin.fetch(**fetch_kwargs)
except GitError as exc:
# Using exc.__str__() here to avoid deprecation warning
# when referencing exc.message
exc_str = exc.__str__().lower()
exc_str = get_error_message(exc).lower()
if 'unsupported url protocol' in exc_str \
and isinstance(self.credentials, pygit2.Keypair):
log.error(

Просмотреть файл

@ -9,6 +9,7 @@ import sys
import salt.utils
from salt.utils.decorators import memoize as real_memoize
from salt.ext.six import string_types
@real_memoize
@ -50,6 +51,16 @@ def sdecode(string_):
return string_
def sdecode_if_string(value_):
'''
If the value is a string, run sdecode() on it to ensure it is parsed
properly. If it is not a string, return it as-is
'''
if isinstance(value_, string_types):
value_ = sdecode(value_)
return value_
def split_locale(loc):
'''
Split a locale specifier. The general format is

Просмотреть файл

@ -1393,8 +1393,7 @@ class ExecutionOptionsMixIn(six.with_metaclass(MixInMeta, object)):
'-u', '--update-bootstrap',
default=False,
action='store_true',
help='Update salt-bootstrap to the latest develop version on '
'GitHub.'
help='Update salt-bootstrap to the latest stable bootstrap release.'
)
group.add_option(
'-y', '--assume-yes',

Просмотреть файл

@ -174,6 +174,7 @@ class FileModuleTest(integration.ModuleCase):
return_value=['http/httpd.conf.fallback']),
'cp.list_master_dirs': MagicMock(return_value=[]),
}
filemod.__context__ = {}
ret = filemod.source_list(['salt://http/httpd.conf',
'salt://http/httpd.conf.fallback'],
@ -189,6 +190,8 @@ class FileModuleTest(integration.ModuleCase):
'cp.list_master': MagicMock(side_effect=list_master),
'cp.list_master_dirs': MagicMock(return_value=[]),
}
filemod.__context__ = {}
ret = filemod.source_list(['salt://http/httpd.conf?saltenv=dev',
'salt://http/httpd.conf.fallback'],
'filehash', 'base')
@ -200,6 +203,8 @@ class FileModuleTest(integration.ModuleCase):
'cp.list_master': MagicMock(return_value=['http/httpd.conf']),
'cp.list_master_dirs': MagicMock(return_value=[]),
}
filemod.__context__ = {}
ret = filemod.source_list(
[{'salt://http/httpd.conf': ''}], 'filehash', 'base')
self.assertItemsEqual(ret, ['salt://http/httpd.conf', 'filehash'])
@ -210,8 +215,10 @@ class FileModuleTest(integration.ModuleCase):
filemod.__salt__ = {
'cp.list_master': MagicMock(return_value=[]),
'cp.list_master_dirs': MagicMock(return_value=[]),
'cp.get_url': MagicMock(return_value='/tmp/http.conf'),
'cp.cache_file': MagicMock(return_value='/tmp/http.conf'),
}
filemod.__context__ = {}
ret = filemod.source_list(
[{'http://t.est.com/http/httpd.conf': 'filehash'}], '', 'base')
self.assertItemsEqual(ret, ['http://t.est.com/http/httpd.conf',

Просмотреть файл

@ -17,6 +17,7 @@ ensure_in_syspath('../../../')
from tests.utils import BaseRestCherryPyTest
# Import Salt Libs
import salt.utils
from tests import integration
# Import 3rd-party libs
@ -31,7 +32,7 @@ except ImportError:
USERA = 'saltdev'
USERA_PWD = 'saltdev'
SET_USERA_PWD = '$6$SALTsalt$ZZFD90fKFWq8AGmmX0L3uBtS9fXL62SrTk5zcnQ6EkD6zoiM3kB88G1Zvs0xm/gZ7WXJRs5nsTBybUvGSqZkT.'
HASHED_USERA_PWD = '$6$SALTsalt$ZZFD90fKFWq8AGmmX0L3uBtS9fXL62SrTk5zcnQ6EkD6zoiM3kB88G1Zvs0xm/gZ7WXJRs5nsTBybUvGSqZkT.'
AUTH_CREDS = {
'username': USERA,
@ -52,7 +53,7 @@ class TestAuthPAM(BaseRestCherryPyTest, integration.ModuleCase):
try:
add_user = self.run_function('user.add', [USERA], createhome=False)
add_pwd = self.run_function('shadow.set_password',
[USERA, SET_USERA_PWD])
[USERA, USERA_PWD if salt.utils.is_darwin() else HASHED_USERA_PWD])
self.assertTrue(add_user)
self.assertTrue(add_pwd)
user_list = self.run_function('user.list_users')

Просмотреть файл

@ -19,6 +19,7 @@ from salttesting.helpers import (
ensure_in_syspath('../../')
# Import salt libs
import salt.utils
from salt.utils.pycrypto import gen_hash
import integration
@ -76,11 +77,18 @@ class AuthTest(integration.ShellCase):
def test_pam_auth_valid_user(self):
'''
test pam auth mechanism is working with a valid user
test that pam auth mechanism works with a valid user
'''
password, hashed_pwd = gen_password()
self.run_call("shadow.set_password {0} '{1}'".format(self.userA, hashed_pwd))
# set user password
set_pw_cmd = "shadow.set_password {0} '{1}'".format(
self.userA,
password if salt.utils.is_darwin() else hashed_pwd
)
self.run_call(set_pw_cmd)
# test user auth against pam
cmd = ('-a pam "*" test.ping '
'--username {0} --password {1}'.format(self.userA, password))
resp = self.run_salt(cmd)
@ -101,11 +109,19 @@ class AuthTest(integration.ShellCase):
def test_pam_auth_valid_group(self):
'''
test pam auth mechanism success for a valid group
test that pam auth mechanism works for a valid group
'''
password, hashed_pwd = gen_password()
self.run_call("shadow.set_password {0} '{1}'".format(self.userB, hashed_pwd))
# set user password
set_pw_cmd = "shadow.set_password {0} '{1}'".format(
self.userB,
password if salt.utils.is_darwin() else hashed_pwd
)
self.run_call(set_pw_cmd)
# test group auth against pam: saltadm is not configured in
# external_auth, but saltops is and saldadm is a member of saltops
cmd = ('-a pam "*" test.ping '
'--username {0} --password {1}'.format(self.userB, password))
resp = self.run_salt(cmd)

Просмотреть файл

@ -33,7 +33,7 @@ class EnabledTest(integration.ModuleCase):
'''
enabled_ret = '3\nsaltines' # the result of running self.cmd in a shell
ret = self.run_function('cmd.run', [self.cmd])
self.assertEqual(ret, enabled_ret)
self.assertEqual(ret.strip(), enabled_ret)
def test_shell_disabled(self):
'''

Просмотреть файл

@ -156,6 +156,29 @@ class UserTest(integration.ModuleCase,
ret = self.run_state('group.absent', name='salt_test')
self.assertSaltTrueReturn(ret)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
def test_user_present_unicode(self):
'''
This is a DESTRUCTIVE TEST it creates a new user on the on the minion.
It ensures that unicode GECOS data will be properly handled, without
any encoding-related failures.
'''
ret = self.run_state(
'user.present', name='salt_test', fullname=u'Sålt Test', roomnumber=u'①②③',
workphone=u'١٢٣٤', homephone=u'६७८'
)
self.assertSaltTrueReturn(ret)
# Ensure updating a user also works
ret = self.run_state(
'user.present', name='salt_test', fullname=u'Sølt Test', roomnumber=u'①③②',
workphone=u'٣٤١٢', homephone=u'६८७'
)
self.assertSaltTrueReturn(ret)
ret = self.run_state('user.absent', name='salt_test')
self.assertSaltTrueReturn(ret)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
def test_user_present_gecos(self):

Просмотреть файл

@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: `Anthony Shaw <anthonyshaw@apache.org>`
tests.unit.cloud.clouds.dimensiondata_test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Libs
from salt.cloud.clouds import dimensiondata
from salt.exceptions import SaltCloudSystemExit
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import MagicMock, NO_MOCK, NO_MOCK_REASON, patch
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../../')
# Global Variables
dimensiondata.__active_provider_name__ = ''
dimensiondata.__opts__ = {
'providers': {
'my-dimensiondata-cloud': {
'dimensiondata': {
'driver': 'dimensiondata',
'region': 'dd-au',
'user_id': 'jon_snow',
'key': 'IKnowNothing'
}
}
}
}
VM_NAME = 'winterfell'
class ExtendedTestCase(TestCase):
'''
Extended TestCase class containing additional helper methods.
'''
def assertRaisesWithMessage(self, exc_type, exc_msg, func, *args, **kwargs):
try:
func(*args, **kwargs)
self.assertFail()
except Exception as exc:
self.assertEqual(type(exc), exc_type)
self.assertEqual(exc.message, exc_msg)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@patch('salt.cloud.clouds.dimensiondata.__virtual__', MagicMock(return_value='dimensiondata'))
class DimensionDataTestCase(ExtendedTestCase):
'''
Unit TestCase for salt.cloud.clouds.dimensiondata module.
'''
def test_avail_images_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call avail_images
with --action or -a.
'''
self.assertRaises(
SaltCloudSystemExit,
dimensiondata.avail_images,
call='action'
)
def test_avail_locations_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call avail_locations
with --action or -a.
'''
self.assertRaises(
SaltCloudSystemExit,
dimensiondata.avail_locations,
call='action'
)
def test_avail_sizes_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call avail_sizes
with --action or -a.
'''
self.assertRaises(
SaltCloudSystemExit,
dimensiondata.avail_sizes,
call='action'
)
def test_list_nodes_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call list_nodes
with --action or -a.
'''
self.assertRaises(
SaltCloudSystemExit,
dimensiondata.list_nodes,
call='action'
)
def test_destroy_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call destroy
with --function or -f.
'''
self.assertRaises(
SaltCloudSystemExit,
dimensiondata.destroy,
name=VM_NAME,
call='function'
)
def test_avail_sizes(self):
'''
Tests that avail_sizes returns an empty dictionary.
'''
sizes = dimensiondata.avail_sizes(call='foo')
self.assertEqual(
len(sizes),
1
)
self.assertEqual(
sizes['default']['name'],
'default'
)
@patch('libcloud.compute.drivers.dimensiondata.DimensionDataNodeDriver.list_nodes', MagicMock(return_value=[]))
def test_list_nodes(self):
nodes = dimensiondata.list_nodes()
self.assertEqual(
nodes,
{}
)
@patch('libcloud.compute.drivers.dimensiondata.DimensionDataNodeDriver.list_locations', MagicMock(return_value=[]))
def test_list_locations(self):
locations = dimensiondata.avail_locations()
self.assertEqual(
locations,
{}
)
if __name__ == '__main__':
from integration import run_tests
run_tests(DimensionDataTestCase, needs_daemon=False)

Просмотреть файл

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: `Anthony Shaw <anthonyshaw@apache.org>`
tests.unit.cloud.clouds.gce_test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Libs
from salt.cloud.clouds import gce
from salt.exceptions import SaltCloudSystemExit
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import MagicMock, NO_MOCK, NO_MOCK_REASON, patch
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../../')
# Global Variables
gce.__active_provider_name__ = ''
gce.__opts__ = {
'providers': {
'my-google-cloud': {
'gce': {
'project': 'daenerys-cloud',
'service_account_email_address': 'dany@targaryen.westeros.cloud',
'service_account_private_key': '/home/dany/PRIVKEY.pem',
'driver': 'gce',
'ssh_interface': 'public_ips'
}
}
}
}
VM_NAME = 'kings_landing'
DUMMY_TOKEN = {
'refresh_token': None,
'client_id': 'dany123',
'client_secret': 'lalalalalalala',
'grant_type': 'refresh_token'
}
class ExtendedTestCase(TestCase):
'''
Extended TestCase class containing additional helper methods.
'''
def assertRaisesWithMessage(self, exc_type, exc_msg, func, *args, **kwargs):
try:
func(*args, **kwargs)
self.assertFail()
except Exception as exc:
self.assertEqual(type(exc), exc_type)
self.assertEqual(exc.message, exc_msg)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@patch('salt.cloud.clouds.gce.__virtual__', MagicMock(return_value='gce'))
@patch('libcloud.common.google.GoogleInstalledAppAuthConnection.get_new_token', MagicMock(return_value=DUMMY_TOKEN))
@patch('libcloud.compute.drivers.gce.GCENodeDriver.ex_list_zones', MagicMock(return_value=[]))
@patch('libcloud.compute.drivers.gce.GCENodeDriver.ex_list_regions', MagicMock(return_value=[]))
class GCETestCase(ExtendedTestCase):
'''
Unit TestCase for salt.cloud.clouds.gce module.
'''
def test_destroy_call(self):
'''
Tests that a SaltCloudSystemExit is raised when trying to call destroy
with --function or -f.
'''
self.assertRaises(
SaltCloudSystemExit,
gce.destroy,
vm_name=VM_NAME,
call='function'
)
@patch('libcloud.compute.drivers.gce.GCENodeDriver.list_sizes', MagicMock(return_value=[]))
def test_avail_sizes(self):
'''
Tests that avail_sizes returns an empty dictionary.
'''
sizes = gce.avail_sizes()
self.assertEqual(
sizes,
[]
)
@patch('libcloud.compute.drivers.gce.GCENodeDriver.list_nodes', MagicMock(return_value=[]))
def test_list_nodes(self):
nodes = gce.list_nodes()
self.assertEqual(
nodes,
{}
)
@patch('libcloud.compute.drivers.gce.GCENodeDriver.list_locations', MagicMock(return_value=[]))
def test_list_locations(self):
locations = gce.avail_locations()
self.assertEqual(
locations,
{}
)
if __name__ == '__main__':
from integration import run_tests
run_tests(GCETestCase, needs_daemon=False)

Просмотреть файл

@ -124,8 +124,9 @@ class DaemonsStarterTestCase(TestCase, integration.SaltClientTestCaseMixIn):
'''
obj = daemons.Minion()
obj.config = {'user': 'dummy', 'hash_type': alg}
for attr in ['minion', 'start_log_info', 'prepare', 'shutdown']:
for attr in ['start_log_info', 'prepare', 'shutdown']:
setattr(obj, attr, MagicMock())
setattr(obj, 'minion', MagicMock(restart=False))
return obj

Просмотреть файл

@ -3,6 +3,7 @@
# Import Python libs
from __future__ import absolute_import
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module
import platform
import random
import string
@ -35,6 +36,10 @@ try:
except ImportError:
HAS_BOTO = False
ON_SUSE = False
if 'SuSE' in platform.dist():
ON_SUSE = True
# pylint: enable=import-error,no-name-in-module
# the boto_lambda module relies on the connect_to_region() method
@ -619,6 +624,7 @@ class BotoLambdaEventSourceMappingTestCase(BotoLambdaTestCaseBase, BotoLambdaTes
**conn_parameters)
self.assertTrue(result['deleted'])
@skipIf(ON_SUSE, 'Skipping while debugging why the test suite hangs and bails on this test on opensuse')
def test_that_when_deleting_an_event_source_mapping_by_name_succeeds_the_delete_event_source_mapping_method_returns_true(self):
'''
tests True mapping deleted.

Просмотреть файл

@ -178,6 +178,10 @@ class PwUserTestCase(TestCase):
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chfullname('name', 'fullname'))
mock = MagicMock(return_value={'fullname': u'Unicøde name ①③②'})
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chfullname('name', u'Unicøde name ①③②'))
mock = MagicMock(return_value={'fullname': 'fullname'})
with patch.object(pw_user, '_get_gecos', mock):
mock = MagicMock(return_value=None)
@ -202,6 +206,10 @@ class PwUserTestCase(TestCase):
with patch.object(pw_user, '_get_gecos', mock):
self.assertFalse(pw_user.chroomnumber('name', 1))
mock = MagicMock(return_value={'roomnumber': u'Unicøde room ①③②'})
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chroomnumber('name', u'Unicøde room ①③②'))
mock = MagicMock(return_value={'roomnumber': '1'})
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chroomnumber('name', 1))
@ -234,6 +242,10 @@ class PwUserTestCase(TestCase):
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chworkphone('name', 1))
mock = MagicMock(return_value={'workphone': u'Unicøde phone number ①③②'})
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chworkphone('name', u'Unicøde phone number ①③②'))
mock = MagicMock(return_value={'workphone': '2'})
with patch.object(pw_user, '_get_gecos', mock):
mock = MagicMock(return_value=None)
@ -262,6 +274,10 @@ class PwUserTestCase(TestCase):
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chhomephone('name', 1))
mock = MagicMock(return_value={'homephone': u'Unicøde phone number ①③②'})
with patch.object(pw_user, '_get_gecos', mock):
self.assertTrue(pw_user.chhomephone('name', u'Unicøde phone number ①③②'))
mock = MagicMock(return_value={'homephone': '2'})
with patch.object(pw_user, '_get_gecos', mock):
mock = MagicMock(return_value=None)

Просмотреть файл

@ -69,7 +69,7 @@ class SaltnadoTestCase(integration.ModuleCase, AsyncHTTPTestCase):
@property
def opts(self):
return self.get_config('master', from_scratch=True)
return self.get_config('client_config', from_scratch=True)
@property
def mod_opts(self):

Просмотреть файл

@ -65,6 +65,7 @@ class ArchiveTestCase(TestCase):
mock_false = MagicMock(return_value=False)
ret = {'stdout': ['saltines', 'cheese'], 'stderr': 'biscuits', 'retcode': '31337', 'pid': '1337'}
mock_run = MagicMock(return_value=ret)
mock_source_list = MagicMock(return_value=source)
with patch('os.path.exists', mock_true):
with patch.dict(archive.__opts__, {'test': False,
@ -72,7 +73,8 @@ class ArchiveTestCase(TestCase):
with patch.dict(archive.__salt__, {'file.directory_exists': mock_false,
'file.file_exists': mock_false,
'file.makedirs': mock_true,
'cmd.run_all': mock_run}):
'cmd.run_all': mock_run,
'file.source_list': mock_source_list}):
filename = os.path.join(
tmp_dir,
'files/test/_tmp_test_archive_.tar'
@ -90,10 +92,19 @@ class ArchiveTestCase(TestCase):
Tests the call of extraction with gnutar
'''
gnutar = MagicMock(return_value='tar (GNU tar)')
source = 'GNU tar'
missing = MagicMock(return_value=False)
nop = MagicMock(return_value=True)
run_all = MagicMock(return_value={'retcode': 0, 'stdout': 'stdout', 'stderr': 'stderr'})
with patch.dict(archive.__salt__, {'cmd.run': gnutar, 'file.directory_exists': missing, 'file.file_exists': missing, 'state.single': nop, 'file.makedirs': nop, 'cmd.run_all': run_all}):
mock_source_list = MagicMock(return_value=source)
with patch.dict(archive.__salt__, {'cmd.run': gnutar,
'file.directory_exists': missing,
'file.file_exists': missing,
'state.single': nop,
'file.makedirs': nop,
'cmd.run_all': run_all,
'file.source_list': mock_source_list}):
ret = archive.extracted('/tmp/out', '/tmp/foo.tar.gz', 'tar', tar_options='xvzf', keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stdout')
@ -102,10 +113,19 @@ class ArchiveTestCase(TestCase):
Tests the call of extraction with bsdtar
'''
bsdtar = MagicMock(return_value='tar (bsdtar)')
source = 'bsdtar'
missing = MagicMock(return_value=False)
nop = MagicMock(return_value=True)
run_all = MagicMock(return_value={'retcode': 0, 'stdout': 'stdout', 'stderr': 'stderr'})
with patch.dict(archive.__salt__, {'cmd.run': bsdtar, 'file.directory_exists': missing, 'file.file_exists': missing, 'state.single': nop, 'file.makedirs': nop, 'cmd.run_all': run_all}):
mock_source_list = MagicMock(return_value=source)
with patch.dict(archive.__salt__, {'cmd.run': bsdtar,
'file.directory_exists': missing,
'file.file_exists': missing,
'state.single': nop,
'file.makedirs': nop,
'cmd.run_all': run_all,
'file.source_list': mock_source_list}):
ret = archive.extracted('/tmp/out', '/tmp/foo.tar.gz', 'tar', tar_options='xvzf', keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stderr')