Merge branch 'develop' into master

This commit is contained in:
Nicole Thomas 2017-12-05 15:46:42 -05:00 committed by GitHub
commit 849b026762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 6367 additions and 747 deletions

4
.github/stale.yml vendored
View File

@ -1,8 +1,8 @@
# Probot Stale configuration file
# Number of days of inactivity before an issue becomes stale
# 875 is approximately 2 years and 5 months
daysUntilStale: 875
# 860 is approximately 2 years and 4 months
daysUntilStale: 860
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7

1
.gitignore vendored
View File

@ -88,6 +88,7 @@ tests/integration/cloud/providers/logs
# Private keys from the integration tests
tests/integration/cloud/providers/pki/minions
/helpers/
# Ignore tox virtualenvs
/.tox/

View File

@ -1,6 +1,6 @@
---
<% vagrant = system('which vagrant 2>/dev/null >/dev/null') %>
<% version = '2017.7.2' %>
<% version = '2017.7.1' %>
<% platformsfile = ENV['SALT_KITCHEN_PLATFORMS'] || '.kitchen/platforms.yml' %>
<% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %>
@ -19,6 +19,8 @@ driver:
disable_upstart: false
provision_command:
- echo 'L /run/docker.sock - - - - /docker.sock' > /etc/tmpfiles.d/docker.conf
transport:
name: sftp
<% end %>
sudo: false
@ -164,6 +166,9 @@ suites:
clone_repo: false
salttesting_namespec: salttesting==2017.6.1
- name: py3
excludes:
- centos-6
- ubuntu-14.04
provisioner:
pillars:
top.sls:

View File

@ -1,9 +1,10 @@
# This file is only used for running the test suite with kitchen-salt.
source "https://rubygems.org"
source 'https://rubygems.org'
gem "test-kitchen"
gem "kitchen-salt", :git => 'https://github.com/saltstack/kitchen-salt.git'
gem 'test-kitchen'
gem 'kitchen-salt', :git => 'https://github.com/saltstack/kitchen-salt.git'
gem 'kitchen-sync'
gem 'git'
group :docker do

View File

@ -297,6 +297,11 @@
#batch_safe_limit: 100
#batch_safe_size: 8
# Master stats enables stats events to be fired from the master at close
# to the defined interval
#master_stats: False
#master_stats_event_iter: 60
##### Security settings #####
##########################################

View File

@ -868,6 +868,29 @@ what you are doing! Transports are explained in :ref:`Salt Transports
ret_port: 4606
zeromq: []
.. conf_master:: master_stats
``master_stats``
----------------
Default: False
Turning on the master stats enables runtime throughput and statistics events
to be fired from the master event bus. These events will report on what
functions have been run on the master and how long these runs have, on
average, taken over a given period of time.
.. conf_master:: master_stats_event_iter
``master_stats_event_iter``
---------------------------
Default: 60
The time in seconds to fire master_stats events. This will only fire in
conjunction with receiving a request to the master, idle masters will not
fire these events.
.. conf_master:: sock_pool_size
``sock_pool_size``

View File

@ -1725,9 +1725,15 @@ enabled and can be disabled by changing this value to ``False``.
If ``extmod_whitelist`` is specified, modules which are not whitelisted will also be cleaned here.
.. conf_minion:: environment
.. conf_minion:: saltenv
``environment``
---------------
``saltenv``
-----------
.. versionchanged:: Oxygen
Renamed from ``environment`` to ``saltenv``. If ``environment`` is used,
``saltenv`` will take its value. If both are used, ``environment`` will be
ignored and ``saltenv`` will be used.
Normally the minion is not isolated to any single environment on the master
when running states, but the environment can be isolated on the minion side
@ -1736,7 +1742,25 @@ environments is to isolate via the top file.
.. code-block:: yaml
environment: dev
saltenv: dev
.. conf_minion:: lock_saltenv
``lock_saltenv``
----------------
.. versionadded:: Oxygen
Default: ``False``
For purposes of running states, this option prevents using the ``saltenv``
argument to manually set the environment. This is useful to keep a minion which
has the :conf_minion:`saltenv` option set to ``dev`` from running states from
an environment other than ``dev``.
.. code-block:: yaml
lock_saltenv: True
.. conf_minion:: snapper_states

View File

@ -404,6 +404,22 @@ The above example will force the minion to use the :py:mod:`systemd
.. __: https://github.com/saltstack/salt/issues/new
Logging Restrictions
--------------------
As a rule, logging should not be done anywhere in a Salt module before it is
loaded. This rule apples to all code that would run before the ``__virtual__()``
function, as well as the code within the ``__virtual__()`` function itself.
If logging statements are made before the virtual function determines if
the module should be loaded, then those logging statements will be called
repeatedly. This clutters up log files unnecessarily.
Exceptions may be considered for logging statements made at the ``trace`` level.
However, it is better to provide the necessary information by another means.
One method is to :ref:`return error information <modules-error-info>` in the
``__virtual__()`` function.
.. _modules-virtual-name:
``__virtualname__``

View File

@ -111,6 +111,8 @@ This code will call the `managed` function in the :mod:`file
<salt.states.file>` state module and pass the arguments ``name`` and ``source``
to it.
.. _state-return-data:
Return Data
===========

View File

@ -47,6 +47,7 @@ Available in
- State Modules
- Returners
- Runners
- SDB Modules
``__salt__`` contains the execution module functions. This allows for all
functions to be called as they have been set up by the salt loader.

View File

@ -5,10 +5,10 @@ Orchestrate Runner
==================
Executing states or highstate on a minion is perfect when you want to ensure that
minion configured and running the way you want. Sometimes however you want to
minion configured and running the way you want. Sometimes however you want to
configure a set of minions all at once.
For example, if you want to set up a load balancer in front of a cluster of web
For example, if you want to set up a load balancer in front of a cluster of web
servers you can ensure the load balancer is set up first, and then the same
matching configuration is applied consistently across the whole cluster.
@ -137,6 +137,43 @@ can specify the "name" argument to avoid conflicting IDs:
- kwarg:
remove_existing: true
.. _orchestrate-runner-fail-functions:
Fail Functions
**************
When running a remote execution function in orchestration, certain return
values for those functions may indicate failure, while the function itself
doesn't set a return code. For those circumstances, using a "fail function"
allows for a more flexible means of assessing success or failure.
A fail function can be written as part of a :ref:`custom execution module
<writing-execution-modules>`. The function should accept one argument, and
return a boolean result. For example:
.. code-block:: python
def check_func_result(retval):
if some_condition:
return True
else:
return False
The function can then be referenced in orchestration SLS like so:
.. code-block:: yaml
do_stuff:
salt.function:
- name: modname.funcname
- tgt: '*'
- fail_function: mymod.check_func_result
.. important::
Fail functions run *on the master*, so they must be synced using ``salt-run
saltutil.sync_modules``.
State
^^^^^
@ -221,6 +258,35 @@ To execute with pillar data.
salt-run state.orch orch.deploy pillar='{"servers": "newsystem1",
"master": "mymaster"}'
.. _orchestrate-runner-return-codes-runner-wheel:
Return Codes in Runner/Wheel Jobs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: Oxygen
State (``salt.state``) jobs are able to report failure via the :ref:`state
return dictionary <state-return-data>`. Remote execution (``salt.function``)
jobs are able to report failure by setting a ``retcode`` key in the
``__context__`` dictionary. However, runner (``salt.runner``) and wheel
(``salt.wheel``) jobs would only report a ``False`` result when the
runner/wheel function raised an exception. As of the Oxygen release, it is now
possible to set a retcode in runner and wheel functions just as you can do in
remote execution functions. Here is some example pseudocode:
.. code-block:: python
def myrunner():
...
do stuff
...
if some_error_condition:
__context__['retcode'] = 1
return result
This allows a custom runner/wheel function to report its failure so that
requisites can accurately tell that a job has failed.
More Complex Orchestration
~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -270,3 +336,270 @@ Given the above setup, the orchestration will be carried out as follows:
.. note::
Remember, salt-run is always executed on the master.
.. _orchestrate-runner-parsing-results-programatically:
Parsing Results Programatically
-------------------------------
Orchestration jobs return output in a specific data structure. That data
structure is represented differently depending on the outputter used. With the
default outputter for orchestration, you get a nice human-readable output.
Assume the following orchestration SLS:
.. code-block:: yaml
good_state:
salt.state:
- tgt: myminion
- sls:
- succeed_with_changes
bad_state:
salt.state:
- tgt: myminion
- sls:
- fail_with_changes
mymod.myfunc:
salt.function:
- tgt: myminion
mymod.myfunc_false_result:
salt.function:
- tgt: myminion
Running this using the default outputter would produce output which looks like
this:
.. code-block:: text
fa5944a73aa8_master:
----------
ID: good_state
Function: salt.state
Result: True
Comment: States ran successfully. Updating myminion.
Started: 21:08:02.681604
Duration: 265.565 ms
Changes:
myminion:
----------
ID: test succeed with changes
Function: test.succeed_with_changes
Result: True
Comment: Success!
Started: 21:08:02.835893
Duration: 0.375 ms
Changes:
----------
testing:
----------
new:
Something pretended to change
old:
Unchanged
Summary for myminion
------------
Succeeded: 1 (changed=1)
Failed: 0
------------
Total states run: 1
Total run time: 0.375 ms
----------
ID: bad_state
Function: salt.state
Result: False
Comment: Run failed on minions: myminion
Started: 21:08:02.947702
Duration: 177.01 ms
Changes:
myminion:
----------
ID: test fail with changes
Function: test.fail_with_changes
Result: False
Comment: Failure!
Started: 21:08:03.116634
Duration: 0.502 ms
Changes:
----------
testing:
----------
new:
Something pretended to change
old:
Unchanged
Summary for myminion
------------
Succeeded: 0 (changed=1)
Failed: 1
------------
Total states run: 1
Total run time: 0.502 ms
----------
ID: mymod.myfunc
Function: salt.function
Result: True
Comment: Function ran successfully. Function mymod.myfunc ran on myminion.
Started: 21:08:03.125011
Duration: 159.488 ms
Changes:
myminion:
True
----------
ID: mymod.myfunc_false_result
Function: salt.function
Result: False
Comment: Running function mymod.myfunc_false_result failed on minions: myminion. Function mymod.myfunc_false_result ran on myminion.
Started: 21:08:03.285148
Duration: 176.787 ms
Changes:
myminion:
False
Summary for fa5944a73aa8_master
------------
Succeeded: 2 (changed=4)
Failed: 2
------------
Total states run: 4
Total run time: 778.850 ms
However, using the ``json`` outputter, you can get the output in an easily
loadable and parsable format:
.. code-block:: bash
salt-run state.orchestrate test --out=json
.. code-block:: json
{
"outputter": "highstate",
"data": {
"fa5944a73aa8_master": {
"salt_|-good_state_|-good_state_|-state": {
"comment": "States ran successfully. Updating myminion.",
"name": "good_state",
"start_time": "21:35:16.868345",
"result": true,
"duration": 267.299,
"__run_num__": 0,
"__jid__": "20171130213516897392",
"__sls__": "test",
"changes": {
"ret": {
"myminion": {
"test_|-test succeed with changes_|-test succeed with changes_|-succeed_with_changes": {
"comment": "Success!",
"name": "test succeed with changes",
"start_time": "21:35:17.022592",
"result": true,
"duration": 0.362,
"__run_num__": 0,
"__sls__": "succeed_with_changes",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged"
}
},
"__id__": "test succeed with changes"
}
}
},
"out": "highstate"
},
"__id__": "good_state"
},
"salt_|-bad_state_|-bad_state_|-state": {
"comment": "Run failed on minions: test",
"name": "bad_state",
"start_time": "21:35:17.136511",
"result": false,
"duration": 197.635,
"__run_num__": 1,
"__jid__": "20171130213517202203",
"__sls__": "test",
"changes": {
"ret": {
"myminion": {
"test_|-test fail with changes_|-test fail with changes_|-fail_with_changes": {
"comment": "Failure!",
"name": "test fail with changes",
"start_time": "21:35:17.326268",
"result": false,
"duration": 0.509,
"__run_num__": 0,
"__sls__": "fail_with_changes",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged"
}
},
"__id__": "test fail with changes"
}
}
},
"out": "highstate"
},
"__id__": "bad_state"
},
"salt_|-mymod.myfunc_|-mymod.myfunc_|-function": {
"comment": "Function ran successfully. Function mymod.myfunc ran on myminion.",
"name": "mymod.myfunc",
"start_time": "21:35:17.334373",
"result": true,
"duration": 151.716,
"__run_num__": 2,
"__jid__": "20171130213517361706",
"__sls__": "test",
"changes": {
"ret": {
"myminion": true
},
"out": "highstate"
},
"__id__": "mymod.myfunc"
},
"salt_|-mymod.myfunc_false_result-mymod.myfunc_false_result-function": {
"comment": "Running function mymod.myfunc_false_result failed on minions: myminion. Function mymod.myfunc_false_result ran on myminion.",
"name": "mymod.myfunc_false_result",
"start_time": "21:35:17.486625",
"result": false,
"duration": 174.241,
"__run_num__": 3,
"__jid__": "20171130213517536270",
"__sls__": "test",
"changes": {
"ret": {
"myminion": false
},
"out": "highstate"
},
"__id__": "mymod.myfunc_false_result"
}
}
},
"retcode": 1
}
The Oxygen release includes a couple fixes to make parsing this data easier and
more accurate. The first is the ability to set a :ref:`return code
<orchestrate-runner-return-codes-runner-wheel>` in a custom runner or wheel
function, as noted above. The second is a change to how failures are included
in the return data. Prior to the Oxygen release, minions that failed a
``salt.state`` orchestration job would show up in the ``comment`` field of the
return data, in a human-readable string that was not easily parsed. They are
now included in the ``changes`` dictionary alongside the minions that
succeeded. In addition, ``salt.function`` jobs which failed because the
:ref:`fail function <orchestrate-runner-fail-functions>` returned ``False``
used to handle their failures in the same way ``salt.state`` jobs did, and this
has likewise been corrected.

View File

@ -25,6 +25,25 @@ by any master tops matches that are not matched via a top file.
To make master tops matches execute first, followed by top file matches, set
the new :conf_minion:`master_tops_first` minion config option to ``True``.
Return Codes for Runner/Wheel Functions
---------------------------------------
When using :ref:`orchestration <orchestrate-runner>`, runner and wheel
functions used to report a ``True`` result if the function ran to completion
without raising an exception. It is now possible to set a return code in the
``__context__`` dictionary, allowing runner and wheel functions to report that
they failed. Here's some example pseudocode:
.. code-block:: python
def myrunner():
...
do stuff
...
if some_error_condition:
__context__['retcode'] = 1
return result
LDAP via External Authentication Changes
----------------------------------------
In this release of Salt, if LDAP Bind Credentials are supplied, then
@ -46,6 +65,37 @@ noon PST so the Stormpath external authentication module has been removed.
https://stormpath.com/oktaplusstormpath
:conf_minion:`environment` config option renamed to :conf_minion:`saltenv`
--------------------------------------------------------------------------
The :conf_minion:`environment` config option predates referring to a salt
fileserver environment as a **saltenv**. To pin a minion to a single
environment for running states, one would use :conf_minion:`environment`, but
overriding that environment would be done with the ``saltenv`` argument. For
consistency, :conf_minion:`environment` is now simply referred to as
:conf_minion:`saltenv`. There are no plans to deprecate or remove
:conf_minion:`environment`, if used it will log a warning and its value will be
used as :conf_minion:`saltenv`.
:conf_minion:`lock_saltenv` config option added
-----------------------------------------------
If set to ``True``, this option will prevent a minion from allowing the
``saltenv`` argument to override the value set in :conf_minion:`saltenv` when
running states.
Failed Minions for State/Function Orchestration Jobs Added to Changes Dictionary
--------------------------------------------------------------------------------
For orchestration jobs which run states (or run remote execution functions and
also use a :ref:`fail function <orchestrate-runner-fail-functions>` to indicate
success or failure), minions which have ``False`` results were previously
included as a formatted string in the comment field of the return for that
orchestration job. This made the failed returns difficult to :ref:`parse
programatically <orchestrate-runner-parsing-results-programatically>`. The
failed returns in these cases are now included in the changes dictionary,
making for much easier parsing.
New Grains
----------

View File

@ -78,7 +78,7 @@ UNIX systems
**BSD**:
- OpenBSD (``pip`` installation)
- OpenBSD
- FreeBSD 9/10/11
**SunOS**:
@ -272,66 +272,118 @@ Here's a summary of the command line options:
$ sh bootstrap-salt.sh -h
Usage : bootstrap-salt.sh [options] <install-type> <install-type-args>
Installation types:
- stable (default)
- stable [version] (ubuntu specific)
- daily (ubuntu specific)
- testing (redhat specific)
- git
- stable Install latest stable release. This is the default
install type
- stable [branch] Install latest version on a branch. Only supported
for packages available at repo.saltstack.com
- stable [version] Install a specific version. Only supported for
packages available at repo.saltstack.com
- daily Ubuntu specific: configure SaltStack Daily PPA
- testing RHEL-family specific: configure EPEL testing repo
- git Install from the head of the develop branch
- git [ref] Install from any git ref (such as a branch, tag, or
commit)
Examples:
- bootstrap-salt.sh
- bootstrap-salt.sh stable
- bootstrap-salt.sh stable 2014.7
- bootstrap-salt.sh stable 2017.7
- bootstrap-salt.sh stable 2017.7.2
- bootstrap-salt.sh daily
- bootstrap-salt.sh testing
- bootstrap-salt.sh git
- bootstrap-salt.sh git develop
- bootstrap-salt.sh git v0.17.0
- bootstrap-salt.sh git 8c3fadf15ec183e5ce8c63739850d543617e4357
- bootstrap-salt.sh git 2017.7
- bootstrap-salt.sh git v2017.7.2
- bootstrap-salt.sh git 06f249901a2e2f1ed310d58ea3921a129f214358
Options:
-h Display this message
-v Display script version
-n No colours.
-D Show debug output.
-c Temporary configuration directory
-g Salt repository URL. (default: git://github.com/saltstack/salt.git)
-G Instead of cloning from git://github.com/saltstack/salt.git, clone from https://github.com/saltstack/salt.git (Usually necessary on systems which have the regular git protocol port blocked, where https usually is not)
-k Temporary directory holding the minion keys which will pre-seed
the master.
-s Sleep time used when waiting for daemons to start, restart and when checking
for the services running. Default: 3
-M Also install salt-master
-S Also install salt-syndic
-N Do not install salt-minion
-X Do not start daemons after installation
-C Only run the configuration function. This option automatically
bypasses any installation.
-P Allow pip based installations. On some distributions the required salt
packages or its dependencies are not available as a package for that
distribution. Using this flag allows the script to use pip as a last
resort method. NOTE: This only works for functions which actually
implement pip based installations.
-F Allow copied files to overwrite existing(config, init.d, etc)
-U If set, fully upgrade the system prior to bootstrapping salt
-K If set, keep the temporary files in the temporary directories specified
with -c and -k.
-I If set, allow insecure connections while downloading any files. For
example, pass '--no-check-certificate' to 'wget' or '--insecure' to 'curl'
-A Pass the salt-master DNS name or IP. This will be stored under
${BS_SALT_ETC_DIR}/minion.d/99-master-address.conf
-i Pass the salt-minion id. This will be stored under
${BS_SALT_ETC_DIR}/minion_id
-L Install the Apache Libcloud package if possible(required for salt-cloud)
-p Extra-package to install while installing salt dependencies. One package
per -p flag. You're responsible for providing the proper package name.
-d Disable check_service functions. Setting this flag disables the
'install_<distro>_check_services' checks. You can also do this by
touching /tmp/disable_salt_checks on the target host. Defaults ${BS_FALSE}
-H Use the specified http proxy for the installation
-Z Enable external software source for newer ZeroMQ(Only available for RHEL/CentOS/Fedora/Ubuntu based distributions)
-b Assume that dependencies are already installed and software sources are set up.
If git is selected, git tree is still checked out as dependency step.
-h Display this message
-v Display script version
-n No colours
-D Show debug output
-c Temporary configuration directory
-g Salt Git repository URL. Default: https://github.com/saltstack/salt.git
-w Install packages from downstream package repository rather than
upstream, saltstack package repository. This is currently only
implemented for SUSE.
-k Temporary directory holding the minion keys which will pre-seed
the master.
-s Sleep time used when waiting for daemons to start, restart and when
checking for the services running. Default: 3
-L Also install salt-cloud and required python-libcloud package
-M Also install salt-master
-S Also install salt-syndic
-N Do not install salt-minion
-X Do not start daemons after installation
-d Disables checking if Salt services are enabled to start on system boot.
You can also do this by touching /tmp/disable_salt_checks on the target
host. Default: ${BS_FALSE}
-P Allow pip based installations. On some distributions the required salt
packages or its dependencies are not available as a package for that
distribution. Using this flag allows the script to use pip as a last
resort method. NOTE: This only works for functions which actually
implement pip based installations.
-U If set, fully upgrade the system prior to bootstrapping Salt
-I If set, allow insecure connections while downloading any files. For
example, pass '--no-check-certificate' to 'wget' or '--insecure' to
'curl'. On Debian and Ubuntu, using this option with -U allows to obtain
GnuPG archive keys insecurely if distro has changed release signatures.
-F Allow copied files to overwrite existing (config, init.d, etc)
-K If set, keep the temporary files in the temporary directories specified
with -c and -k
-C Only run the configuration function. Implies -F (forced overwrite).
To overwrite Master or Syndic configs, -M or -S, respectively, must
also be specified. Salt installation will be ommitted, but some of the
dependencies could be installed to write configuration with -j or -J.
-A Pass the salt-master DNS name or IP. This will be stored under
${BS_SALT_ETC_DIR}/minion.d/99-master-address.conf
-i Pass the salt-minion id. This will be stored under
${BS_SALT_ETC_DIR}/minion_id
-p Extra-package to install while installing Salt dependencies. One package
per -p flag. You're responsible for providing the proper package name.
-H Use the specified HTTP proxy for all download URLs (including https://).
For example: http://myproxy.example.com:3128
-Z Enable additional package repository for newer ZeroMQ
(only available for RHEL/CentOS/Fedora/Ubuntu based distributions)
-b Assume that dependencies are already installed and software sources are
set up. If git is selected, git tree is still checked out as dependency
step.
-f Force shallow cloning for git installations.
This may result in an "n/a" in the version number.
-l Disable ssl checks. When passed, switches "https" calls to "http" where
possible.
-V Install Salt into virtualenv
(only available for Ubuntu based distributions)
-a Pip install all Python pkg dependencies for Salt. Requires -V to install
all pip pkgs into the virtualenv.
(Only available for Ubuntu based distributions)
-r Disable all repository configuration performed by this script. This
option assumes all necessary repository configuration is already present
on the system.
-R Specify a custom repository URL. Assumes the custom repository URL
points to a repository that mirrors Salt packages located at
repo.saltstack.com. The option passed with -R replaces the
"repo.saltstack.com". If -R is passed, -r is also set. Currently only
works on CentOS/RHEL and Debian based distributions.
-J Replace the Master config file with data passed in as a JSON string. If
a Master config file is found, a reasonable effort will be made to save
the file with a ".bak" extension. If used in conjunction with -C or -F,
no ".bak" file will be created as either of those options will force
a complete overwrite of the file.
-j Replace the Minion config file with data passed in as a JSON string. If
a Minion config file is found, a reasonable effort will be made to save
the file with a ".bak" extension. If used in conjunction with -C or -F,
no ".bak" file will be created as either of those options will force
a complete overwrite of the file.
-q Quiet salt installation from git (setup.py install -q)
-x Changes the python version used to install a git version of salt. Currently
this is considered experimental and has only been tested on Centos 6. This
only works for git installations.
-y Installs a different python version on host. Currently this has only been
tested with Centos 6 and is considered experimental. This will install the
ius repo on the box if disable repo is false. This must be used in conjunction
with -x <pythonversion>. For example:
sh bootstrap.sh -P -y -x python2.7 git v2016.11.3
The above will install python27 and install the git version of salt using the
python2.7 executable. This only works for git and pip installations.

View File

@ -161,6 +161,7 @@ class Master(salt.utils.parsers.MasterOptionParser, DaemonsMixin): # pylint: di
v_dirs,
self.config['user'],
permissive=self.config['permissive_pki_access'],
root_dir=self.config['root_dir'],
sensitive_dirs=[self.config['pki_dir'], self.config['key_dir']],
)
# Clear out syndics from cachedir
@ -281,6 +282,7 @@ class Minion(salt.utils.parsers.MinionOptionParser, DaemonsMixin): # pylint: di
v_dirs,
self.config['user'],
permissive=self.config['permissive_pki_access'],
root_dir=self.config['root_dir'],
sensitive_dirs=[self.config['pki_dir']],
)
except OSError as error:
@ -468,6 +470,7 @@ class ProxyMinion(salt.utils.parsers.ProxyMinionOptionParser, DaemonsMixin): #
v_dirs,
self.config['user'],
permissive=self.config['permissive_pki_access'],
root_dir=self.config['root_dir'],
sensitive_dirs=[self.config['pki_dir']],
)
except OSError as error:
@ -576,6 +579,7 @@ class Syndic(salt.utils.parsers.SyndicOptionParser, DaemonsMixin): # pylint: di
],
self.config['user'],
permissive=self.config['permissive_pki_access'],
root_dir=self.config['root_dir'],
sensitive_dirs=[self.config['pki_dir']],
)
except OSError as error:

View File

@ -32,7 +32,10 @@ class SPM(parsers.SPMParser):
v_dirs = [
self.config['cachedir'],
]
verify_env(v_dirs, self.config['user'],)
verify_env(v_dirs,
self.config['user'],
root_dir=self.config['root_dir'],
)
verify_log(self.config)
client = salt.spm.SPMClient(ui, self.config)
client.run(self.args)

View File

@ -385,7 +385,11 @@ class SyncClientMixin(object):
# Initialize a context for executing the method.
with tornado.stack_context.StackContext(self.functions.context_dict.clone):
data[u'return'] = self.functions[fun](*args, **kwargs)
data[u'success'] = True
try:
data[u'success'] = self.context.get(u'retcode', 0) == 0
except AttributeError:
# Assume a True result if no context attribute
data[u'success'] = True
if isinstance(data[u'return'], dict) and u'data' in data[u'return']:
# some functions can return boolean values
data[u'success'] = salt.utils.state.check_result(data[u'return'][u'data'])

View File

@ -1026,6 +1026,8 @@ class Single(object):
opts_pkg[u'__master_opts__'] = self.context[u'master_opts']
if u'_caller_cachedir' in self.opts:
opts_pkg[u'_caller_cachedir'] = self.opts[u'_caller_cachedir']
if u'known_hosts_file' in self.opts:
opts_pkg[u'known_hosts_file'] = self.opts[u'known_hosts_file']
else:
opts_pkg[u'_caller_cachedir'] = self.opts[u'cachedir']
# Use the ID defined in the roster file
@ -1048,7 +1050,7 @@ class Single(object):
popts,
opts_pkg[u'grains'],
opts_pkg[u'id'],
opts_pkg.get(u'environment', u'base')
opts_pkg.get(u'saltenv', u'base')
)
pillar_data = pillar.compile_pillar()

View File

@ -67,7 +67,8 @@ class SaltCloud(salt.utils.parsers.SaltCloudParser):
if self.config['verify_env']:
verify_env(
[os.path.dirname(self.config['conf_file'])],
salt_master_user
salt_master_user,
root_dir=self.config['root_dir'],
)
logfile = self.config['log_file']
if logfile is not None and not logfile.startswith('tcp://') \

View File

@ -80,6 +80,7 @@ def _master_opts(cfg='master'):
cfg = os.environ.get(
'SALT_MASTER_CONFIG', os.path.join(default_dir, cfg))
opts = config.master_config(cfg)
opts['output'] = 'quiet'
return opts
@ -559,9 +560,10 @@ def get_configured_provider(vm_=None):
# in all cases, verify that the linked saltmaster is alive.
if data:
ret = _salt('test.ping', salt_target=data['target'])
if not ret:
raise SaltCloudSystemExit(
if ret:
return data
else:
log.error(
'Configured provider {0} minion: {1} is unreachable'.format(
__active_provider_name__, data['target']))
return data
return False

View File

@ -2473,7 +2473,12 @@ def create(vm_):
# Either a datacenter or a folder can be optionally specified when cloning, required when creating.
# If not specified when cloning, the existing VM/template\'s parent folder is used.
if folder:
folder_ref = salt.utils.vmware.get_mor_by_property(si, vim.Folder, folder, container_ref=container_ref)
folder_parts = folder.split('/')
search_reference = container_ref
for folder_part in folder_parts:
if folder_part:
folder_ref = salt.utils.vmware.get_mor_by_property(si, vim.Folder, folder_part, container_ref=search_reference)
search_reference = folder_ref
if not folder_ref:
log.error("Specified folder: '{0}' does not exist".format(folder))
log.debug("Using folder in which {0} {1} is present".format(clone_type, vm_['clonefrom']))
@ -3690,6 +3695,49 @@ def remove_all_snapshots(name, kwargs=None, call=None):
return 'removed all snapshots'
def convert_to_template(name, kwargs=None, call=None):
'''
Convert the specified virtual machine to template.
CLI Example:
.. code-block:: bash
salt-cloud -a convert_to_template vmname
'''
if call != 'action':
raise SaltCloudSystemExit(
'The convert_to_template action must be called with '
'-a or --action.'
)
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
if vm_ref.config.template:
raise SaltCloudSystemExit(
'{0} already a template'.format(
name
)
)
try:
vm_ref.MarkAsTemplate()
except Exception as exc:
log.error(
'Error while converting VM to template {0}: {1}'.format(
name,
exc
),
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG
)
return 'failed to convert to teamplate'
return '{0} converted to template'.format(
name
)
def add_host(kwargs=None, call=None):
'''
Add a host system to the specified cluster or datacenter in this VMware environment

View File

@ -165,6 +165,10 @@ VALID_OPTS = {
# The master_pubkey_signature must also be set for this.
'master_use_pubkey_signature': bool,
# Enable master stats eveents to be fired, these events will contain information about
# what commands the master is processing and what the rates are of the executions
'master_stats': bool,
'master_stats_event_iter': int,
# The key fingerprint of the higher-level master for the syndic to verify it is talking to the
# intended master
'syndic_finger': str,
@ -242,7 +246,10 @@ VALID_OPTS = {
'autoload_dynamic_modules': bool,
# Force the minion into a single environment when it fetches files from the master
'environment': str,
'saltenv': str,
# Prevent saltenv from being overriden on the command line
'lock_saltenv': bool,
# Force the minion into a single pillar root when it fetches pillar data from the master
'pillarenv': str,
@ -1177,7 +1184,8 @@ DEFAULT_MINION_OPTS = {
'random_startup_delay': 0,
'failhard': False,
'autoload_dynamic_modules': True,
'environment': None,
'saltenv': None,
'lock_saltenv': False,
'pillarenv': None,
'pillarenv_from_saltenv': False,
'pillar_opts': False,
@ -1454,7 +1462,8 @@ DEFAULT_MASTER_OPTS = {
},
'top_file_merging_strategy': 'merge',
'env_order': [],
'environment': None,
'saltenv': None,
'lock_saltenv': False,
'default_top': 'base',
'file_client': 'local',
'git_pillar_base': 'master',
@ -1515,6 +1524,8 @@ DEFAULT_MASTER_OPTS = {
'svnfs_saltenv_whitelist': [],
'svnfs_saltenv_blacklist': [],
'max_event_size': 1048576,
'master_stats': False,
'master_stats_event_iter': 60,
'minionfs_env': 'base',
'minionfs_mountpoint': '',
'minionfs_whitelist': [],
@ -3590,6 +3601,24 @@ def apply_minion_config(overrides=None,
if overrides:
opts.update(overrides)
if u'environment' in opts:
if u'saltenv' in opts:
log.warning(
u'The \'saltenv\' and \'environment\' minion config options '
u'cannot both be used. Ignoring \'environment\' in favor of '
u'\'saltenv\'.',
)
# Set environment to saltenv in case someone's custom module is
# refrencing __opts__['environment']
opts[u'environment'] = opts[u'saltenv']
else:
log.warning(
u'The \'environment\' minion config option has been renamed '
u'to \'saltenv\'. Using %s as the \'saltenv\' config value.',
opts[u'environment']
)
opts[u'saltenv'] = opts[u'environment']
opts['__cli'] = os.path.basename(sys.argv[0])
# No ID provided. Will getfqdn save us?
@ -3742,6 +3771,24 @@ def apply_master_config(overrides=None, defaults=None):
if overrides:
opts.update(overrides)
if u'environment' in opts:
if u'saltenv' in opts:
log.warning(
u'The \'saltenv\' and \'environment\' master config options '
u'cannot both be used. Ignoring \'environment\' in favor of '
u'\'saltenv\'.',
)
# Set environment to saltenv in case someone's custom runner is
# refrencing __opts__['environment']
opts[u'environment'] = opts[u'saltenv']
else:
log.warning(
u'The \'environment\' master config option has been renamed '
u'to \'saltenv\'. Using %s as the \'saltenv\' config value.',
opts[u'environment']
)
opts[u'saltenv'] = opts[u'environment']
if len(opts['sock_dir']) > len(opts['cachedir']) + 10:
opts['sock_dir'] = os.path.join(opts['cachedir'], '.salt-unix')

View File

@ -737,7 +737,7 @@ class SaltLoadPillar(ioflo.base.deeding.Deed):
'dst': (master.name, None, 'remote_cmd')}
load = {'id': self.opts.value['id'],
'grains': self.grains.value,
'saltenv': self.opts.value['environment'],
'saltenv': self.opts.value['saltenv'],
'ver': '2',
'cmd': '_pillar'}
self.road_stack.value.transmit({'route': route, 'load': load},

View File

@ -42,6 +42,7 @@ import salt.utils.platform
import salt.utils.stringutils
import salt.utils.user
import salt.utils.verify
import salt.utils.versions
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.pillar import git_pillar
from salt.exceptions import FileserverConfigError, SaltMasterError
@ -534,7 +535,7 @@ class RemoteFuncs(object):
return ret
expr_form = load.get('expr_form')
if expr_form is not None and 'tgt_type' not in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Neon',
u'_mine_get: minion {0} uses pre-Nitrogen API key '
u'"expr_form". Accepting for backwards compatibility '

View File

@ -474,8 +474,14 @@ def _sunos_memdata():
grains['mem_total'] = int(comps[2].strip())
swap_cmd = salt.utils.path.which('swap')
swap_total = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()[1]
grains['swap_total'] = int(swap_total) // 1024
swap_data = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()
try:
swap_avail = int(swap_data[-2][:-1])
swap_used = int(swap_data[-4][:-1])
swap_total = (swap_avail + swap_used) // 1024
except ValueError:
swap_total = None
grains['swap_total'] = swap_total
return grains
@ -2475,10 +2481,9 @@ def _linux_iqn():
if os.path.isfile(initiator):
with salt.utils.files.fopen(initiator, 'r') as _iscsi:
for line in _iscsi:
if line.find('InitiatorName') != -1:
iqn = line.split('=')
final_iqn = iqn[1].rstrip()
ret.extend([final_iqn])
line = line.strip()
if line.startswith('InitiatorName='):
ret.append(line.split('=', 1)[1])
return ret
@ -2492,9 +2497,10 @@ def _aix_iqn():
aixret = __salt__['cmd.run'](aixcmd)
if aixret[0].isalpha():
iqn = aixret.split()
final_iqn = iqn[1].rstrip()
ret.extend([final_iqn])
try:
ret.append(aixret.split()[1].rstrip())
except IndexError:
pass
return ret
@ -2507,8 +2513,7 @@ def _linux_wwns():
for fcfile in glob.glob('/sys/class/fc_host/*/port_name'):
with salt.utils.files.fopen(fcfile, 'r') as _wwn:
for line in _wwn:
line = line.rstrip()
ret.extend([line[2:]])
ret.append(line.rstrip()[2:])
return ret
@ -2532,11 +2537,9 @@ def _windows_iqn():
wmic, namespace, mspath, get))
for line in cmdret['stdout'].splitlines():
if line[0].isalpha():
continue
line = line.rstrip()
ret.extend([line])
if line.startswith('iqn.'):
line = line.rstrip()
ret.append(line.rstrip())
return ret
@ -2551,7 +2554,6 @@ def _windows_wwns():
cmdret = __salt__['cmd.run_ps'](ps_cmd)
for line in cmdret:
line = line.rstrip()
ret.append(line)
ret.append(line.rstrip())
return ret

View File

@ -372,15 +372,18 @@ def tops(opts):
return FilterDictWrapper(ret, u'.top')
def wheels(opts, whitelist=None):
def wheels(opts, whitelist=None, context=None):
'''
Returns the wheels modules
'''
if context is None:
context = {}
return LazyLoader(
_module_dirs(opts, u'wheel'),
opts,
tag=u'wheel',
whitelist=whitelist,
pack={u'__context__': context},
)
@ -836,17 +839,19 @@ def call(fun, **kwargs):
return funcs[fun](*args)
def runner(opts, utils=None):
def runner(opts, utils=None, context=None):
'''
Directly call a function inside a loader directory
'''
if utils is None:
utils = {}
if context is None:
context = {}
ret = LazyLoader(
_module_dirs(opts, u'runners', u'runner', ext_type_dirs=u'runner_dirs'),
opts,
tag=u'runners',
pack={u'__utils__': utils},
pack={u'__utils__': utils, u'__context__': context},
)
# TODO: change from __salt__ to something else, we overload __salt__ too much
ret.pack[u'__salt__'] = ret
@ -879,6 +884,7 @@ def sdb(opts, functions=None, whitelist=None, utils=None):
u'__sdb__': functions,
u'__opts__': opts,
u'__utils__': utils,
u'__salt__': minion_mods(opts, utils),
},
whitelist=whitelist,
)
@ -1588,8 +1594,10 @@ class LazyLoader(salt.utils.lazy.LazyDict):
Load a single item if you have it
'''
# if the key doesn't have a '.' then it isn't valid for this mod dict
if not isinstance(key, six.string_types) or u'.' not in key:
raise KeyError
if not isinstance(key, six.string_types):
raise KeyError(u'The key must be a string.')
if u'.' not in key:
raise KeyError(u'The key \'%s\' should contain a \'.\'', key)
mod_name, _ = key.split(u'.', 1)
if mod_name in self.missing_modules:
return True

View File

@ -16,6 +16,7 @@ import errno
import signal
import stat
import logging
import collections
import multiprocessing
import salt.serializers.msgpack
@ -797,6 +798,7 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
:return: Master worker
'''
kwargs[u'name'] = name
self.name = name
super(MWorker, self).__init__(**kwargs)
self.opts = opts
self.req_channels = req_channels
@ -804,6 +806,8 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
self.mkey = mkey
self.key = key
self.k_mtime = 0
self.stats = collections.defaultdict(lambda: {'mean': 0, 'runs': 0})
self.stat_clock = time.time()
# We need __setstate__ and __getstate__ to also pickle 'SMaster.secrets'.
# Otherwise, 'SMaster.secrets' won't be copied over to the spawned process
@ -879,6 +883,19 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
u'clear': self._handle_clear}[key](load)
raise tornado.gen.Return(ret)
def _post_stats(self, start, cmd):
'''
Calculate the master stats and fire events with stat info
'''
end = time.time()
duration = end - start
self.stats[cmd][u'mean'] = (self.stats[cmd][u'mean'] * (self.stats[cmd][u'runs'] - 1) + duration) / self.stats[cmd][u'runs']
if end - self.stat_clock > self.opts[u'master_stats_event_iter']:
# Fire the event with the stats and wipe the tracker
self.aes_funcs.event.fire_event({u'time': end - self.stat_clock, u'worker': self.name, u'stats': self.stats}, tagify(self.name, u'stats'))
self.stats = collections.defaultdict(lambda: {'mean': 0, 'runs': 0})
self.stat_clock = end
def _handle_clear(self, load):
'''
Process a cleartext command
@ -888,9 +905,16 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
the command specified in the load's 'cmd' key.
'''
log.trace(u'Clear payload received with command %s', load[u'cmd'])
if load[u'cmd'].startswith(u'__'):
cmd = load[u'cmd']
if cmd.startswith(u'__'):
return False
return getattr(self.clear_funcs, load[u'cmd'])(load), {u'fun': u'send_clear'}
if self.opts[u'master_stats']:
start = time.time()
self.stats[cmd][u'runs'] += 1
ret = getattr(self.clear_funcs, cmd)(load), {u'fun': u'send_clear'}
if self.opts[u'master_stats']:
self._post_stats(start, cmd)
return ret
def _handle_aes(self, data):
'''
@ -903,10 +927,17 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
if u'cmd' not in data:
log.error(u'Received malformed command %s', data)
return {}
cmd = data[u'cmd']
log.trace(u'AES payload received with command %s', data[u'cmd'])
if data[u'cmd'].startswith(u'__'):
if cmd.startswith(u'__'):
return False
return self.aes_funcs.run_func(data[u'cmd'], data)
if self.opts[u'master_stats']:
start = time.time()
self.stats[cmd][u'runs'] += 1
ret = self.aes_funcs.run_func(data[u'cmd'], data)
if self.opts[u'master_stats']:
self._post_stats(start, cmd)
return ret
def run(self):
'''

View File

@ -736,8 +736,8 @@ class SMinion(MinionBase):
if not os.path.isdir(pdir):
os.makedirs(pdir, 0o700)
ptop = os.path.join(pdir, u'top.sls')
if self.opts[u'environment'] is not None:
penv = self.opts[u'environment']
if self.opts[u'saltenv'] is not None:
penv = self.opts[u'saltenv']
else:
penv = u'base'
cache_top = {penv: {self.opts[u'id']: [u'cache']}}
@ -773,7 +773,7 @@ class SMinion(MinionBase):
self.opts,
self.opts[u'grains'],
self.opts[u'id'],
self.opts[u'environment'],
self.opts[u'saltenv'],
pillarenv=self.opts.get(u'pillarenv'),
).compile_pillar()
@ -1144,7 +1144,7 @@ class Minion(MinionBase):
self.opts,
self.opts[u'grains'],
self.opts[u'id'],
self.opts[u'environment'],
self.opts[u'saltenv'],
pillarenv=self.opts.get(u'pillarenv')
).compile_pillar()
@ -2032,7 +2032,7 @@ class Minion(MinionBase):
self.opts,
self.opts[u'grains'],
self.opts[u'id'],
self.opts[u'environment'],
self.opts[u'saltenv'],
pillarenv=self.opts.get(u'pillarenv'),
).compile_pillar()
except SaltClientError:
@ -2067,12 +2067,16 @@ class Minion(MinionBase):
self.schedule.run_job(name)
elif func == u'disable_job':
self.schedule.disable_job(name, persist)
elif func == u'postpone_job':
self.schedule.postpone_job(name, data)
elif func == u'reload':
self.schedule.reload(schedule)
elif func == u'list':
self.schedule.list(where)
elif func == u'save_schedule':
self.schedule.save_schedule()
elif func == u'get_next_fire_time':
self.schedule.get_next_fire_time(name)
def manage_beacons(self, tag, data):
'''
@ -3349,7 +3353,7 @@ class ProxyMinion(Minion):
self.opts,
self.opts[u'grains'],
self.opts[u'id'],
saltenv=self.opts[u'environment'],
saltenv=self.opts[u'saltenv'],
pillarenv=self.opts.get(u'pillarenv'),
).compile_pillar()
@ -3391,7 +3395,7 @@ class ProxyMinion(Minion):
# we can then sync any proxymodules down from the master
# we do a sync_all here in case proxy code was installed by
# SPM or was manually placed in /srv/salt/_modules etc.
self.functions[u'saltutil.sync_all'](saltenv=self.opts[u'environment'])
self.functions[u'saltutil.sync_all'](saltenv=self.opts[u'saltenv'])
# Pull in the utils
self.utils = salt.loader.utils(self.opts)

View File

@ -125,8 +125,7 @@ def cert(name,
salt 'gitlab.example.com' acme.cert dev.example.com "[gitlab.example.com]" test_cert=True renew=14 webroot=/opt/gitlab/embedded/service/gitlab-rails/public
'''
# cmd = [LEA, 'certonly', '--quiet']
cmd = [LEA, 'certonly']
cmd = [LEA, 'certonly', '--non-interactive']
cert_file = _cert_file(name, 'cert')
if not __salt__['file.file_exists'](cert_file):

View File

@ -214,14 +214,12 @@ def __virtual__():
'''
ret = ansible is not None
msg = not ret and "Ansible is not installed on this system" or None
if msg:
log.warning(msg)
else:
if ret:
global _resolver
global _caller
_resolver = AnsibleModuleResolver(__opts__).resolve().install()
_caller = AnsibleModuleCaller(_resolver)
_set_callables(list())
_set_callables(list())
return ret, msg

View File

@ -219,7 +219,7 @@ def _gather_pillar(pillarenv, pillar_override):
__opts__,
__grains__,
__opts__['id'],
__opts__['environment'],
__opts__['saltenv'],
pillar_override=pillar_override,
pillarenv=pillarenv
)
@ -589,11 +589,17 @@ def _run(cmd,
out = proc.stdout.decode(__salt_system_encoding__)
except AttributeError:
out = u''
except UnicodeDecodeError:
log.error('UnicodeDecodeError while decoding output of cmd {0}'.format(cmd))
out = proc.stdout.decode(__salt_system_encoding__, 'replace')
try:
err = proc.stderr.decode(__salt_system_encoding__)
except AttributeError:
err = u''
except UnicodeDecodeError:
log.error('UnicodeDecodeError while decoding error of cmd {0}'.format(cmd))
err = proc.stderr.decode(__salt_system_encoding__, 'replace')
if rstrip:
if out is not None:

View File

@ -49,7 +49,7 @@ def _gather_pillar(pillarenv, pillar_override):
__opts__,
__grains__,
__opts__['id'],
__opts__['environment'],
__opts__['saltenv'],
pillar_override=pillar_override,
pillarenv=pillarenv
)

View File

@ -5290,7 +5290,7 @@ def _gather_pillar(pillarenv, pillar_override, **grains):
grains,
# Not sure if these two are correct
__opts__['id'],
__opts__['environment'],
__opts__['saltenv'],
pillar_override=pillar_override,
pillarenv=pillarenv
)

View File

@ -225,7 +225,7 @@ def send(tag,
data_dict['pillar'] = __pillar__
if with_env_opts:
data_dict['saltenv'] = __opts__.get('environment', 'base')
data_dict['saltenv'] = __opts__.get('saltenv', 'base')
data_dict['pillarenv'] = __opts__.get('pillarenv')
if kwargs:

View File

@ -552,11 +552,11 @@ def lsattr(path):
raise SaltInvocationError("File or directory does not exist.")
cmd = ['lsattr', path]
result = __salt__['cmd.run'](cmd, python_shell=False)
result = __salt__['cmd.run'](cmd, ignore_retcode=True, python_shell=False)
results = {}
for line in result.splitlines():
if not line.startswith('lsattr'):
if not line.startswith('lsattr: '):
vals = line.split(None, 1)
results[vals[1]] = re.findall(r"[acdijstuADST]", vals[0])
@ -5203,13 +5203,18 @@ def manage_file(name,
'Replace symbolic link with regular file'
if salt.utils.platform.is_windows():
ret = check_perms(name,
ret,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
None,
kwargs.get('win_inheritance'))
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1120,E1121,E1123
ret = check_perms(
path=name,
ret=ret,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
ret, _ = check_perms(name, ret, user, group, mode, attrs, follow_symlinks)
@ -5250,13 +5255,15 @@ def manage_file(name,
if salt.utils.platform.is_windows():
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1121
makedirs_(name,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
kwargs.get('win_inheritance'))
# pylint: enable=E1121
# pylint: disable=E1120,E1121,E1123
makedirs_(
path=name,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
makedirs_(name, user=user, group=group, mode=dir_mode)
@ -5369,13 +5376,18 @@ def manage_file(name,
mode = oct((0o777 ^ mask) & 0o666)
if salt.utils.platform.is_windows():
ret = check_perms(name,
ret,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
None,
kwargs.get('win_inheritance'))
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1120,E1121,E1123
ret = check_perms(
path=name,
ret=ret,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
ret, _ = check_perms(name, ret, user, group, mode, attrs)

View File

@ -13,6 +13,7 @@ import re
# Import Salt Libs
from salt.exceptions import CommandExecutionError
import salt.utils.path
import salt.utils.versions
log = logging.getLogger(__name__)
@ -635,8 +636,10 @@ def add_port(zone, port, permanent=True, force_masquerade=None):
# This will be deprecated in a future release
if force_masquerade is None:
force_masquerade = True
salt.utils.warn_until('Neon',
'add_port function will no longer force enable masquerading in future releases. Use add_masquerade to enable masquerading.')
salt.utils.versions.warn_until(
'Neon',
'add_port function will no longer force enable masquerading '
'in future releases. Use add_masquerade to enable masquerading.')
# (DEPRECATED) Force enable masquerading
# TODO: remove in future release
@ -709,8 +712,10 @@ def add_port_fwd(zone, src, dest, proto='tcp', dstaddr='', permanent=True, force
# This will be deprecated in a future release
if force_masquerade is None:
force_masquerade = True
salt.utils.warn_until('Neon',
'add_port_fwd function will no longer force enable masquerading in future releases. Use add_masquerade to enable masquerading.')
salt.utils.versions.warn_until(
'Neon',
'add_port_fwd function will no longer force enable masquerading '
'in future releases. Use add_masquerade to enable masquerading.')
# (DEPRECATED) Force enable masquerading
# TODO: remove in future release

873
salt/modules/keystoneng.py Normal file
View File

@ -0,0 +1,873 @@
# -*- coding: utf-8 -*-
'''
Keystone module for interacting with OpenStack Keystone
.. versionadded:: Nitrogen
:depends:shade
Example configuration
.. code-block:: yaml
keystone:
cloud: default
.. code-block:: yaml
keystone:
auth:
username: admin
password: password123
user_domain_name: mydomain
project_name: myproject
project_domain_name: myproject
auth_url: https://example.org:5000/v3
identity_api_version: 3
'''
from __future__ import absolute_import
import salt.utils
HAS_SHADE = False
try:
import shade
from shade.exc import OpenStackCloudException
HAS_SHADE = True
except ImportError:
pass
__virtualname__ = 'keystoneng'
def __virtual__():
'''
Only load this module if shade python module is installed
'''
if HAS_SHADE:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def compare_changes(obj, **kwargs):
'''
Compare two dicts returning only keys that exist in the first dict and are
different in the second one
'''
changes = {}
for k, v in obj.items():
if k in kwargs:
if v != kwargs[k]:
changes[k] = kwargs[k]
return changes
def get_entity(ent_type, **kwargs):
'''
Attempt to query Keystone for more information about an entity
'''
try:
func = 'keystoneng.{}_get'.format(ent_type)
ent = __salt__[func](**kwargs)
except OpenStackCloudException as e:
# NOTE(SamYaple): If this error was something other than Forbidden we
# reraise the issue since we are not prepared to handle it
if 'HTTP 403' not in e.inner_exception[1][0]:
raise
# NOTE(SamYaple): The user may be authorized to perform the function
# they are trying to do, but not authorized to search. In such a
# situation we want to trust that the user has passed a valid id, even
# though we cannot validate that this is a valid id
ent = kwargs['name']
return ent
def _clean_kwargs(keep_name=False, **kwargs):
'''
Sanatize the the arguments for use with shade
'''
if 'name' in kwargs and not keep_name:
kwargs['name_or_id'] = kwargs.pop('name')
try:
clean_func = salt.utils.args.clean_kwargs
except AttributeError:
clean_func = salt.utils.clean_kwargs
return clean_func(**kwargs)
def setup_clouds(auth=None):
'''
Call functions to create Shade cloud objects in __context__ to take
advantage of Shade's in-memory caching across several states
'''
get_operator_cloud(auth)
get_openstack_cloud(auth)
def get_operator_cloud(auth=None):
'''
Return an operator_cloud
'''
if auth is None:
auth = __salt__['config.option']('keystone', {})
if 'shade_opcloud' in __context__:
if __context__['shade_opcloud'].auth == auth:
return __context__['shade_opcloud']
__context__['shade_opcloud'] = shade.operator_cloud(**auth)
return __context__['shade_opcloud']
def get_openstack_cloud(auth=None):
'''
Return an openstack_cloud
'''
if auth is None:
auth = __salt__['config.option']('keystone', {})
if 'shade_oscloud' in __context__:
if __context__['shade_oscloud'].auth == auth:
return __context__['shade_oscloud']
__context__['shade_oscloud'] = shade.openstack_cloud(**auth)
return __context__['shade_oscloud']
def group_create(auth=None, **kwargs):
'''
Create a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_create name=group1
salt '*' keystoneng.group_create name=group2 domain=domain1 description='my group2'
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_group(**kwargs)
def group_delete(auth=None, **kwargs):
'''
Delete a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_delete name=group1
salt '*' keystoneng.group_delete name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.group_delete name=0e4febc2a5ab4f2c8f374b054162506d
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_group(**kwargs)
def group_update(auth=None, **kwargs):
'''
Update a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_update name=group1 description='new description'
salt '*' keystoneng.group_create name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e new_name=newgroupname
salt '*' keystoneng.group_create name=0e4febc2a5ab4f2c8f374b054162506d new_name=newgroupname
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_group(**kwargs)
def group_list(auth=None, **kwargs):
'''
List groups
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_list
salt '*' keystoneng.group_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_groups(**kwargs)
def group_search(auth=None, **kwargs):
'''
Search for groups
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_search name=group1
salt '*' keystoneng.group_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_groups(**kwargs)
def group_get(auth=None, **kwargs):
'''
Get a single group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_get name=group1
salt '*' keystoneng.group_get name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.group_get name=0e4febc2a5ab4f2c8f374b054162506d
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_group(**kwargs)
def project_create(auth=None, **kwargs):
'''
Create a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_create name=project1
salt '*' keystoneng.project_create name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_create name=project3 enabled=False description='my project3'
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_project(**kwargs)
def project_delete(auth=None, **kwargs):
'''
Delete a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_delete name=project1
salt '*' keystoneng.project_delete name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_delete name=f315afcf12f24ad88c92b936c38f2d5a
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_project(**kwargs)
def project_update(auth=None, **kwargs):
'''
Update a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_update name=project1 new_name=newproject
salt '*' keystoneng.project_update name=project2 enabled=False description='new description'
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_project(**kwargs)
def project_list(auth=None, **kwargs):
'''
List projects
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_list
salt '*' keystoneng.project_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_projects(**kwargs)
def project_search(auth=None, **kwargs):
'''
Search projects
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_search
salt '*' keystoneng.project_search name=project1
salt '*' keystoneng.project_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_projects(**kwargs)
def project_get(auth=None, **kwargs):
'''
Get a single project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_get name=project1
salt '*' keystoneng.project_get name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_get name=f315afcf12f24ad88c92b936c38f2d5a
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_project(**kwargs)
def domain_create(auth=None, **kwargs):
'''
Create a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_create name=domain1
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_domain(**kwargs)
def domain_delete(auth=None, **kwargs):
'''
Delete a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_delete name=domain1
salt '*' keystoneng.domain_delete name=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_domain(**kwargs)
def domain_update(auth=None, **kwargs):
'''
Update a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_update name=domain1 new_name=newdomain
salt '*' keystoneng.domain_update name=domain1 enabled=True description='new description'
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_domain(**kwargs)
def domain_list(auth=None, **kwargs):
'''
List domains
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_domains(**kwargs)
def domain_search(auth=None, **kwargs):
'''
Search domains
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_search
salt '*' keystoneng.domain_search name=domain1
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_domains(**kwargs)
def domain_get(auth=None, **kwargs):
'''
Get a single domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_get name=domain1
salt '*' keystoneng.domain_get name=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_domain(**kwargs)
def role_create(auth=None, **kwargs):
'''
Create a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_create name=role1
salt '*' keystoneng.role_create name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_role(**kwargs)
def role_delete(auth=None, **kwargs):
'''
Delete a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_delete name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.role_delete name=1eb6edd5525e4ac39af571adee673559
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_role(**kwargs)
def role_update(auth=None, **kwargs):
'''
Update a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_update name=role1 new_name=newrole
salt '*' keystoneng.role_update name=1eb6edd5525e4ac39af571adee673559 new_name=newrole
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_role(**kwargs)
def role_list(auth=None, **kwargs):
'''
List roles
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_list
salt '*' keystoneng.role_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_roles(**kwargs)
def role_search(auth=None, **kwargs):
'''
Search roles
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_search
salt '*' keystoneng.role_search name=role1
salt '*' keystoneng.role_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_roles(**kwargs)
def role_get(auth=None, **kwargs):
'''
Get a single role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_get name=role1
salt '*' keystoneng.role_get name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.role_get name=1eb6edd5525e4ac39af571adee673559
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_role(**kwargs)
def user_create(auth=None, **kwargs):
'''
Create a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_create name=user1
salt '*' keystoneng.user_create name=user2 password=1234 enabled=False
salt '*' keystoneng.user_create name=user3 domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_user(**kwargs)
def user_delete(auth=None, **kwargs):
'''
Delete a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_delete name=user1
salt '*' keystoneng.user_delete name=user2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.user_delete name=a42cbbfa1e894e839fd0f584d22e321f
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_user(**kwargs)
def user_update(auth=None, **kwargs):
'''
Update a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_update name=user1 enabled=False description='new description'
salt '*' keystoneng.user_update name=user1 new_name=newuser
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_user(**kwargs)
def user_list(auth=None, **kwargs):
'''
List users
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_list
salt '*' keystoneng.user_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_users(**kwargs)
def user_search(auth=None, **kwargs):
'''
List users
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_list
salt '*' keystoneng.user_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_users(**kwargs)
def user_get(auth=None, **kwargs):
'''
Get a single user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_get name=user1
salt '*' keystoneng.user_get name=user1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.user_get name=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_user(**kwargs)
def endpoint_create(auth=None, **kwargs):
'''
Create an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_create interface=admin service=glance url=https://example.org:9292
salt '*' keystoneng.endpoint_create interface=public service=glance region=RegionOne url=https://example.org:9292
salt '*' keystoneng.endpoint_create interface=admin service=glance url=https://example.org:9292 enabled=True
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_endpoint(**kwargs)
def endpoint_delete(auth=None, **kwargs):
'''
Delete an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_delete id=3bee4bd8c2b040ee966adfda1f0bfca9
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_endpoint(**kwargs)
def endpoint_update(auth=None, **kwargs):
'''
Update an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 interface=public enabled=False
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 region=newregion
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 service_name_or_id=glance url=https://example.org:9292
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.update_endpoint(**kwargs)
def endpoint_list(auth=None, **kwargs):
'''
List endpoints
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_endpoints(**kwargs)
def endpoint_search(auth=None, **kwargs):
'''
Search endpoints
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_search
salt '*' keystoneng.endpoint_search id=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_endpoints(**kwargs)
def endpoint_get(auth=None, **kwargs):
'''
Get a single endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_get id=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_endpoint(**kwargs)
def service_create(auth=None, **kwargs):
'''
Create a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_create name=glance type=image
salt '*' keystoneng.service_create name=glance type=image description="Image"
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_service(**kwargs)
def service_delete(auth=None, **kwargs):
'''
Delete a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_delete name=glance
salt '*' keystoneng.service_delete name=39cc1327cdf744ab815331554430e8ec
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_service(**kwargs)
def service_update(auth=None, **kwargs):
'''
Update a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_update name=cinder type=volumev2
salt '*' keystoneng.service_update name=cinder description='new description'
salt '*' keystoneng.service_update name=ab4d35e269f147b3ae2d849f77f5c88f enabled=False
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.update_service(**kwargs)
def service_list(auth=None, **kwargs):
'''
List services
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_services(**kwargs)
def service_search(auth=None, **kwargs):
'''
Search services
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_search
salt '*' keystoneng.service_search name=glance
salt '*' keystoneng.service_search name=135f0403f8e544dc9008c6739ecda860
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_services(**kwargs)
def service_get(auth=None, **kwargs):
'''
Get a single service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_get name=glance
salt '*' keystoneng.service_get name=75a5804638944b3ab54f7fbfcec2305a
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_service(**kwargs)
def role_assignment_list(auth=None, **kwargs):
'''
List role assignments
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_assignment_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_role_assignments(**kwargs)
def role_grant(auth=None, **kwargs):
'''
Grant a role in a project/domain to a user/group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_grant name=role1 user=user1 project=project1
salt '*' keystoneng.role_grant name=ddbe3e0ed74e4c7f8027bad4af03339d group=user1 project=project1 domain=domain1
salt '*' keystoneng.role_grant name=ddbe3e0ed74e4c7f8027bad4af03339d group=19573afd5e4241d8b65c42215bae9704 project=1dcac318a83b4610b7a7f7ba01465548
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.grant_role(**kwargs)
def role_revoke(auth=None, **kwargs):
'''
Grant a role in a project/domain to a user/group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_revoke name=role1 user=user1 project=project1
salt '*' keystoneng.role_revoke name=ddbe3e0ed74e4c7f8027bad4af03339d group=user1 project=project1 domain=domain1
salt '*' keystoneng.role_revoke name=ddbe3e0ed74e4c7f8027bad4af03339d group=19573afd5e4241d8b65c42215bae9704 project=1dcac318a83b4610b7a7f7ba01465548
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.revoke_role(**kwargs)

View File

@ -6,9 +6,10 @@ Module for sending messages to Mattermost
:configuration: This module can be used by either passing an api_url and hook
directly or by specifying both in a configuration profile in the salt
master/minion config.
For example:
master/minion config. For example:
.. code-block:: yaml
mattermost:
hook: peWcBiMOS9HrZG15peWcBiMOS9HrZG15
api_url: https://example.com
@ -35,6 +36,7 @@ __virtualname__ = 'mattermost'
def __virtual__():
'''
Return virtual name of the module.
:return: The virtual name of the module.
'''
return __virtualname__
@ -43,6 +45,7 @@ def __virtual__():
def _get_hook():
'''
Retrieves and return the Mattermost's configured hook
:return: String: the hook string
'''
hook = __salt__['config.get']('mattermost.hook') or \
@ -56,6 +59,7 @@ def _get_hook():
def _get_api_url():
'''
Retrieves and return the Mattermost's configured api url
:return: String: the api url string
'''
api_url = __salt__['config.get']('mattermost.api_url') or \
@ -69,6 +73,7 @@ def _get_api_url():
def _get_channel():
'''
Retrieves the Mattermost's configured channel
:return: String: the channel string
'''
channel = __salt__['config.get']('mattermost.channel') or \
@ -80,6 +85,7 @@ def _get_channel():
def _get_username():
'''
Retrieves the Mattermost's configured username
:return: String: the username string
'''
username = __salt__['config.get']('mattermost.username') or \
@ -95,14 +101,18 @@ def post_message(message,
hook=None):
'''
Send a message to a Mattermost channel.
:param channel: The channel name, either will work.
:param username: The username of the poster.
:param message: The message to send to the Mattermost channel.
:param api_url: The Mattermost api url, if not specified in the configuration.
:param hook: The Mattermost hook, if not specified in the configuration.
:return: Boolean if message was sent successfully.
CLI Example:
.. code-block:: bash
salt '*' mattermost.post_message message='Build is done"
'''
if not api_url:

126
salt/modules/netbox.py Normal file
View File

@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
'''
NetBox
======
Module to query NetBox
:codeauthor: Zach Moody <zmoody@do.co>
:maturity: new
:depends: pynetbox
The following config should be in the minion config file. In order to
work with ``secrets`` you should provide a token and path to your
private key file:
.. code-block:: yaml
netbox:
url: <NETBOX_URL>
token: <NETBOX_USERNAME_API_TOKEN (OPTIONAL)>
keyfile: </PATH/TO/NETBOX/KEY (OPTIONAL)>
.. versionadded:: Oxygen
'''
from __future__ import absolute_import
import logging
from salt.exceptions import CommandExecutionError
from salt.utils.args import clean_kwargs
log = logging.getLogger(__name__)
try:
import pynetbox
HAS_PYNETBOX = True
except ImportError:
HAS_PYNETBOX = False
AUTH_ENDPOINTS = (
'secrets',
)
def __virtual__():
'''
pynetbox must be installed.
'''
if not HAS_PYNETBOX:
return (
False,
'The netbox execution module cannot be loaded: '
'pynetbox library is not installed.'
)
else:
return True
def _config():
config = __salt__['config.get']('netbox')
if not config:
raise CommandExecutionError(
'NetBox execution module configuration could not be found'
)
return config
def _nb_obj(auth_required=False):
pynb_kwargs = {}
if auth_required:
pynb_kwargs['token'] = _config().get('token')
pynb_kwargs['private_key_file'] = _config().get('keyfile')
return pynetbox.api(_config().get('url'), **pynb_kwargs)
def _strip_url_field(input_dict):
if 'url' in input_dict.keys():
del input_dict['url']
for k, v in input_dict.items():
if isinstance(v, dict):
_strip_url_field(v)
return input_dict
def filter(app, endpoint, **kwargs):
'''
Get a list of items from NetBox.
.. code-block:: bash
salt myminion netbox.filter dcim devices status=1 role=router
'''
ret = []
nb = _nb_obj(auth_required=True if app in AUTH_ENDPOINTS else False)
nb_query = getattr(getattr(nb, app), endpoint).filter(
**clean_kwargs(**kwargs)
)
if nb_query:
ret = [_strip_url_field(dict(i)) for i in nb_query]
return sorted(ret)
def get(app, endpoint, id=None, **kwargs):
'''
Get a single item from NetBox.
To get an item based on ID.
.. code-block:: bash
salt myminion netbox.get dcim devices id=123
Or using named arguments that correspond with accepted filters on
the NetBox endpoint.
.. code-block:: bash
salt myminion netbox.get dcim devices name=my-router
'''
nb = _nb_obj(auth_required=True if app in AUTH_ENDPOINTS else False)
if id:
return dict(getattr(getattr(nb, app), endpoint).get(id))
else:
return dict(
getattr(getattr(nb, app), endpoint).get(**clean_kwargs(**kwargs))
)

View File

@ -519,6 +519,22 @@ def get_domain_config():
return __proxy__['panos.call'](query)
def get_dos_blocks():
'''
Show the DoS block-ip table.
CLI Example:
.. code-block:: bash
salt '*' panos.get_dos_blocks
'''
query = {'type': 'op', 'cmd': '<show><dos-block-table><all></all></dos-block-table></show>'}
return __proxy__['panos.call'](query)
def get_fqdn_cache():
'''
Print FQDNs used in rules and their IPs.
@ -846,6 +862,32 @@ def get_lldp_neighbors():
return __proxy__['panos.call'](query)
def get_local_admins():
'''
Show all local administrator accounts.
CLI Example:
.. code-block:: bash
salt '*' panos.get_local_admins
'''
admin_list = get_users_config()
response = []
if 'users' not in admin_list['result']:
return response
if isinstance(admin_list['result']['users']['entry'], list):
for entry in admin_list['result']['users']['entry']:
response.append(entry['name'])
else:
response.append(admin_list['result']['users']['entry']['name'])
return response
def get_logdb_quota():
'''
Report the logdb quotas.
@ -2157,6 +2199,120 @@ def shutdown():
return __proxy__['panos.call'](query)
def test_fib_route(ip=None,
vr='vr1'):
'''
Perform a route lookup within active route table (fib).
ip (str): The destination IP address to test.
vr (str): The name of the virtual router to test.
CLI Example:
.. code-block:: bash
salt '*' panos.test_fib_route 4.2.2.2
salt '*' panos.test_fib_route 4.2.2.2 my-vr
'''
xpath = "<test><routing><fib-lookup>"
if ip:
xpath += "<ip>{0}</ip>".format(ip)
if vr:
xpath += "<virtual-router>{0}</virtual-router>".format(vr)
xpath += "</fib-lookup></routing></test>"
query = {'type': 'op',
'cmd': xpath}
return __proxy__['panos.call'](query)
def test_security_policy(sourcezone=None,
destinationzone=None,
source=None,
destination=None,
protocol=None,
port=None,
application=None,
category=None,
vsys='1',
allrules=False):
'''
Checks which security policy as connection will match on the device.
sourcezone (str): The source zone matched against the connection.
destinationzone (str): The destination zone matched against the connection.
source (str): The source address. This must be a single IP address.
destination (str): The destination address. This must be a single IP address.
protocol (int): The protocol number for the connection. This is the numerical representation of the protocol.
port (int): The port number for the connection.
application (str): The application that should be matched.
category (str): The category that should be matched.
vsys (int): The numerical representation of the VSYS ID.
allrules (bool): Show all potential match rules until first allow rule.
CLI Example:
.. code-block:: bash
salt '*' panos.test_security_policy sourcezone=trust destinationzone=untrust protocol=6 port=22
salt '*' panos.test_security_policy sourcezone=trust destinationzone=untrust protocol=6 port=22 vsys=2
'''
xpath = "<test><security-policy-match>"
if sourcezone:
xpath += "<from>{0}</from>".format(sourcezone)
if destinationzone:
xpath += "<to>{0}</to>".format(destinationzone)
if source:
xpath += "<source>{0}</source>".format(source)
if destination:
xpath += "<destination>{0}</destination>".format(destination)
if protocol:
xpath += "<protocol>{0}</protocol>".format(protocol)
if port:
xpath += "<destination-port>{0}</destination-port>".format(port)
if application:
xpath += "<application>{0}</application>".format(application)
if category:
xpath += "<category>{0}</category>".format(category)
if allrules:
xpath += "<show-all>yes</show-all>"
xpath += "</security-policy-match></test>"
query = {'type': 'op',
'vsys': "vsys{0}".format(vsys),
'cmd': xpath}
return __proxy__['panos.call'](query)
def unlock_admin(username=None):
'''
Unlocks a locked administrator account.

View File

@ -237,7 +237,7 @@ def items(*args, **kwargs):
pillarenv = kwargs.get('pillarenv')
if pillarenv is None:
if __opts__.get('pillarenv_from_saltenv', False):
pillarenv = kwargs.get('saltenv') or __opts__['environment']
pillarenv = kwargs.get('saltenv') or __opts__['saltenv']
else:
pillarenv = __opts__['pillarenv']
@ -468,7 +468,7 @@ def ext(external, pillar=None):
__opts__,
__grains__,
__opts__['id'],
__opts__['environment'],
__opts__['saltenv'],
ext=external,
pillar_override=pillar)

View File

@ -1153,6 +1153,61 @@ def list_upgrades(bin_env=None,
return packages
def is_installed(pkgname=None,
bin_env=None,
user=None,
cwd=None):
'''
Filter list of installed apps from ``freeze`` and return True or False if
``pkgname`` exists in the list of packages installed.
.. note::
If the version of pip available is older than 8.0.3, the packages
wheel, setuptools, and distribute will not be reported by this function
even if they are installed. Unlike
:py:func:`pip.freeze <salt.modules.pip.freeze>`, this function always
reports the version of pip which is installed.
CLI Example:
.. code-block:: bash
salt '*' pip.is_installed salt
.. versionadded:: Oxygen
The packages wheel, setuptools, and distribute are included if the
installed pip is new enough.
'''
for line in freeze(bin_env=bin_env, user=user, cwd=cwd):
if line.startswith('-f') or line.startswith('#'):
# ignore -f line as it contains --find-links directory
# ignore comment lines
continue
elif line.startswith('-e hg+not trust'):
# ignore hg + not trust problem
continue
elif line.startswith('-e'):
line = line.split('-e ')[1]
version_, name = line.split('#egg=')
elif len(line.split('===')) >= 2:
name = line.split('===')[0]
version_ = line.split('===')[1]
elif len(line.split('==')) >= 2:
name = line.split('==')[0]
version_ = line.split('==')[1]
else:
logger.error('Can\'t parse line \'{0}\''.format(line))
continue
if pkgname:
if pkgname == name.lower():
return True
return False
def upgrade_available(pkg,
bin_env=None,
user=None,

View File

@ -643,12 +643,18 @@ def _parse_settings_eth(opts, iface_type, enabled, iface):
result[opt] = opts[opt]
if iface_type not in ['bond', 'vlan', 'bridge', 'ipip']:
auto_addr = False
if 'addr' in opts:
if salt.utils.validate.net.mac(opts['addr']):
result['addr'] = opts['addr']
else:
_raise_error_iface(iface, opts['addr'], ['AA:BB:CC:DD:EE:FF'])
elif opts['addr'] == 'auto':
auto_addr = True
elif opts['addr'] != 'none':
_raise_error_iface(iface, opts['addr'], ['AA:BB:CC:DD:EE:FF', 'auto', 'none'])
else:
auto_addr = True
if auto_addr:
# If interface type is slave for bond, not setting hwaddr
if iface_type != 'slave':
ifaces = __salt__['network.interfaces']()

View File

@ -509,7 +509,7 @@ class SaltCheck(object):
# state cache should be updated before running this method
search_list = []
cachedir = __opts__.get('cachedir', None)
environment = __opts__['environment']
environment = __opts__['saltenv']
if environment:
path = cachedir + os.sep + "files" + os.sep + environment
search_list.append(path)

View File

@ -474,7 +474,7 @@ def sync_returners(saltenv=None, refresh=True, extmod_whitelist=None, extmod_bla
'''
.. versionadded:: 0.10.0
Sync beacons from ``salt://_returners`` to the minion
Sync returners from ``salt://_returners`` to the minion
saltenv
The fileserver environment from which to sync. To sync from more than
@ -585,6 +585,44 @@ def sync_engines(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blac
return ret
def sync_thorium(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: Oxygen
Sync Thorium modules from ``salt://_thorium`` to the minion
saltenv
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
If not passed, then all environments configured in the :ref:`top files
<states-top>` will be checked for engines to sync. If no top files are
found, then the ``base`` environment will be synced.
refresh: ``True``
If ``True``, refresh the available execution modules on the minion.
This refresh will be performed even if no new Thorium modules are synced.
Set to ``False`` to prevent this refresh.
extmod_whitelist
comma-seperated list of modules to sync
extmod_blacklist
comma-seperated list of modules to blacklist based on type
CLI Examples:
.. code-block:: bash
salt '*' saltutil.sync_thorium
salt '*' saltutil.sync_thorium saltenv=base,dev
'''
ret = _sync('thorium', saltenv, extmod_whitelist, extmod_blacklist)
if refresh:
refresh_modules()
return ret
def sync_output(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None):
'''
Sync outputters from ``salt://_output`` to the minion
@ -628,7 +666,7 @@ def sync_clouds(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blackl
'''
.. versionadded:: 2017.7.0
Sync utility modules from ``salt://_cloud`` to the minion
Sync cloud modules from ``salt://_cloud`` to the minion
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -864,6 +902,7 @@ def sync_all(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist
ret['log_handlers'] = sync_log_handlers(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['proxymodules'] = sync_proxymodules(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['engines'] = sync_engines(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['thorium'] = sync_thorium(saltenv, False, extmod_whitelist, extmod_blacklist)
if __opts__['file_client'] == 'local':
ret['pillar'] = sync_pillar(saltenv, False, extmod_whitelist, extmod_blacklist)
if refresh:

View File

@ -10,6 +10,7 @@ Module for managing the Salt schedule on a minion
from __future__ import absolute_import
import copy as pycopy
import difflib
import logging
import os
import yaml
@ -23,7 +24,6 @@ from salt.ext import six
__proxyenabled__ = ['*']
import logging
log = logging.getLogger(__name__)
__func_alias__ = {
@ -58,6 +58,7 @@ SCHEDULE_CONF = [
'return_config',
'return_kwargs',
'run_on_start'
'skip_during_range',
]
@ -353,7 +354,7 @@ def build_schedule_item(name, **kwargs):
for item in ['range', 'when', 'once', 'once_fmt', 'cron',
'returner', 'after', 'return_config', 'return_kwargs',
'until', 'run_on_start']:
'until', 'run_on_start', 'skip_during_range']:
if item in kwargs:
schedule[name][item] = kwargs[item]
@ -771,7 +772,7 @@ def disable(**kwargs):
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Schedule enable job failed.'
ret['comment'] = 'Event module not available. Schedule disable job failed.'
return ret
@ -951,3 +952,191 @@ def copy(name, target, **kwargs):
ret['minions'] = minions
return ret
return ret
def postpone_job(name, current_time, new_time, **kwargs):
'''
Postpone a job in the minion's schedule
Current time and new time should be specified as Unix timestamps
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.postpone_job job current_time new_time
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
return ret
if not current_time:
ret['comment'] = 'Job current time is required.'
ret['result'] = False
return ret
else:
if not isinstance(current_time, six.integer_types):
ret['comment'] = 'Job current time must be an integer.'
ret['result'] = False
return ret
if not new_time:
ret['comment'] = 'Job new_time is required.'
ret['result'] = False
return ret
else:
if not isinstance(new_time, six.integer_types):
ret['comment'] = 'Job new time must be an integer.'
ret['result'] = False
return ret
if 'test' in __opts__ and __opts__['test']:
ret['comment'] = 'Job: {0} would be postponed in schedule.'.format(name)
else:
if name in list_(show_all=True, where='opts', return_yaml=False):
event_data = {'name': name,
'time': current_time,
'new_time': new_time,
'func': 'postpone_job'}
elif name in list_(show_all=True, where='pillar', return_yaml=False):
event_data = {'name': name,
'time': current_time,
'new_time': new_time,
'where': 'pillar',
'func': 'postpone_job'}
else:
ret['comment'] = 'Job {0} does not exist.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data, 'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_postpone_job_complete', wait=30)
if event_ret and event_ret['complete']:
schedule = event_ret['schedule']
# check item exists in schedule and is enabled
if name in schedule and schedule[name]['enabled']:
ret['result'] = True
ret['comment'] = 'Postponed Job {0} in schedule.'.format(name)
else:
ret['result'] = False
ret['comment'] = 'Failed to postpone job {0} in schedule.'.format(name)
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Schedule postpone job failed.'
return ret
def skip_job(name, time, **kwargs):
'''
Skip a job in the minion's schedule at specified time.
Time to skip should be specified as Unix timestamps
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.skip_job job time
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
if not time:
ret['comment'] = 'Job time is required.'
ret['result'] = False
if 'test' in __opts__ and __opts__['test']:
ret['comment'] = 'Job: {0} would be skipped in schedule.'.format(name)
else:
if name in list_(show_all=True, where='opts', return_yaml=False):
event_data = {'name': name,
'time': time,
'func': 'skip_job'}
elif name in list_(show_all=True, where='pillar', return_yaml=False):
event_data = {'name': name,
'time': time,
'where': 'pillar',
'func': 'skip_job'}
else:
ret['comment'] = 'Job {0} does not exist.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data, 'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_skip_job_complete', wait=30)
if event_ret and event_ret['complete']:
schedule = event_ret['schedule']
# check item exists in schedule and is enabled
if name in schedule and schedule[name]['enabled']:
ret['result'] = True
ret['comment'] = 'Added Skip Job {0} in schedule.'.format(name)
else:
ret['result'] = False
ret['comment'] = 'Failed to skip job {0} in schedule.'.format(name)
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Schedule skip job failed.'
return ret
def show_next_fire_time(name, **kwargs):
'''
Show the next fire time for scheduled job
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.show_next_fire_time job_name
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
try:
event_data = {'name': name, 'func': 'get_next_fire_time'}
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data,
'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_next_fire_time_complete', wait=30)
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret = {}
ret['comment'] = 'Event module not available. Schedule show next fire time failed.'
ret['result'] = True
log.debug(ret['comment'])
return ret
return event_ret

View File

@ -43,6 +43,7 @@ from salt.runners.state import orchestrate as _orchestrate
# Import 3rd-party libs
from salt.ext import six
import msgpack
__proxyenabled__ = ['*']
@ -165,6 +166,99 @@ def _snapper_post(opts, jid, pre_num):
log.error('Failed to create snapper pre snapshot for jid: {0}'.format(jid))
def pause(jid, state_id=None, duration=None):
'''
Set up a state id pause, this instructs a running state to pause at a given
state id. This needs to pass in the jid of the running state and can
optionally pass in a duration in seconds. If a state_id is not passed then
the jid referenced will be paused at the begining of the next state run.
The given state id is the id got a given state execution, so given a state
that looks like this:
.. code-block:: yaml
vim:
pkg.installed: []
The state_id to pass to `pause` is `vim`
CLI Examples:
.. code-block:: bash
salt '*' state.pause 20171130110407769519
salt '*' state.pause 20171130110407769519 vim
salt '*' state.pause 20171130110407769519 vim 20
'''
jid = str(jid)
if state_id is None:
state_id = '__all__'
pause_dir = os.path.join(__opts__[u'cachedir'], 'state_pause')
pause_path = os.path.join(pause_dir, jid)
if not os.path.exists(pause_dir):
try:
os.makedirs(pause_dir)
except OSError:
# File created in the gap
pass
data = {}
if os.path.exists(pause_path):
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
data = msgpack.loads(fp_.read())
if state_id not in data:
data[state_id] = {}
if duration:
data[state_id]['duration'] = int(duration)
with salt.utils.files.fopen(pause_path, 'wb') as fp_:
fp_.write(msgpack.dumps(data))
def resume(jid, state_id=None):
'''
Remove a pause from a jid, allowing it to continue. If the state_id is
not specified then the a general pause will be resumed.
The given state_id is the id got a given state execution, so given a state
that looks like this:
.. code-block:: yaml
vim:
pkg.installed: []
The state_id to pass to `rm_pause` is `vim`
CLI Examples:
.. code-block:: bash
salt '*' state.resume 20171130110407769519
salt '*' state.resume 20171130110407769519 vim
'''
jid = str(jid)
if state_id is None:
state_id = '__all__'
pause_dir = os.path.join(__opts__[u'cachedir'], 'state_pause')
pause_path = os.path.join(pause_dir, jid)
if not os.path.exists(pause_dir):
try:
os.makedirs(pause_dir)
except OSError:
# File created in the gap
pass
data = {}
if os.path.exists(pause_path):
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
data = msgpack.loads(fp_.read())
else:
return True
if state_id in data:
data.pop(state_id)
with salt.utils.files.fopen(pause_path, 'wb') as fp_:
fp_.write(msgpack.dumps(data))
def orchestrate(mods,
saltenv='base',
test=None,
@ -270,10 +364,14 @@ def _get_opts(**kwargs):
if 'saltenv' in kwargs:
saltenv = kwargs['saltenv']
if saltenv is not None and not isinstance(saltenv, six.string_types):
opts['environment'] = str(kwargs['saltenv'])
else:
opts['environment'] = kwargs['saltenv']
if saltenv is not None:
if not isinstance(saltenv, six.string_types):
saltenv = six.text_type(saltenv)
if opts['lock_saltenv'] and saltenv != opts['saltenv']:
raise CommandExecutionError(
'lock_saltenv is enabled, saltenv cannot be changed'
)
opts['saltenv'] = kwargs['saltenv']
if 'pillarenv' in kwargs or opts.get('pillarenv_from_saltenv', False):
pillarenv = kwargs.get('pillarenv') or kwargs.get('saltenv')
@ -840,7 +938,7 @@ def highstate(test=None, queue=False, **kwargs):
kwargs.pop('env')
if 'saltenv' in kwargs:
opts['environment'] = kwargs['saltenv']
opts['saltenv'] = kwargs['saltenv']
if 'pillarenv' in kwargs:
opts['pillarenv'] = kwargs['pillarenv']
@ -1032,8 +1130,8 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
# Since this is running a specific SLS file (or files), fall back to the
# 'base' saltenv if none is configured and none was passed.
if opts['environment'] is None:
opts['environment'] = 'base'
if opts['saltenv'] is None:
opts['saltenv'] = 'base'
pillar_override = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1089,7 +1187,7 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
st_.push_active()
ret = {}
try:
high_, errors = st_.render_highstate({opts['environment']: mods})
high_, errors = st_.render_highstate({opts['saltenv']: mods})
if errors:
__context__['retcode'] = 1
@ -1411,8 +1509,8 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
# Since this is running a specific ID within a specific SLS file, fall back
# to the 'base' saltenv if none is configured and none was passed.
if opts['environment'] is None:
opts['environment'] = 'base'
if opts['saltenv'] is None:
opts['saltenv'] = 'base'
pillar_override = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1446,7 +1544,7 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
split_mods = mods.split(',')
st_.push_active()
try:
high_, errors = st_.render_highstate({opts['environment']: split_mods})
high_, errors = st_.render_highstate({opts['saltenv']: split_mods})
finally:
st_.pop_active()
errors += st_.state.verify_high(high_)
@ -1472,7 +1570,7 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
if not ret:
raise SaltInvocationError(
'No matches for ID \'{0}\' found in SLS \'{1}\' within saltenv '
'\'{2}\''.format(id_, mods, opts['environment'])
'\'{2}\''.format(id_, mods, opts['saltenv'])
)
return ret
@ -1523,8 +1621,8 @@ def show_low_sls(mods, test=None, queue=False, **kwargs):
# Since this is dealing with a specific SLS file (or files), fall back to
# the 'base' saltenv if none is configured and none was passed.
if opts['environment'] is None:
opts['environment'] = 'base'
if opts['saltenv'] is None:
opts['saltenv'] = 'base'
pillar_override = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1555,7 +1653,7 @@ def show_low_sls(mods, test=None, queue=False, **kwargs):
mods = mods.split(',')
st_.push_active()
try:
high_, errors = st_.render_highstate({opts['environment']: mods})
high_, errors = st_.render_highstate({opts['saltenv']: mods})
finally:
st_.pop_active()
errors += st_.state.verify_high(high_)
@ -1594,7 +1692,7 @@ def show_sls(mods, test=None, queue=False, **kwargs):
.. code-block:: bash
salt '*' state.show_sls core,edit.vim dev
salt '*' state.show_sls core,edit.vim saltenv=dev
'''
if 'env' in kwargs:
# "env" is not supported; Use "saltenv".
@ -1610,8 +1708,8 @@ def show_sls(mods, test=None, queue=False, **kwargs):
# Since this is dealing with a specific SLS file (or files), fall back to
# the 'base' saltenv if none is configured and none was passed.
if opts['environment'] is None:
opts['environment'] = 'base'
if opts['saltenv'] is None:
opts['saltenv'] = 'base'
pillar_override = kwargs.get('pillar')
pillar_enc = kwargs.get('pillar_enc')
@ -1644,7 +1742,7 @@ def show_sls(mods, test=None, queue=False, **kwargs):
mods = mods.split(',')
st_.push_active()
try:
high_, errors = st_.render_highstate({opts['environment']: mods})
high_, errors = st_.render_highstate({opts['saltenv']: mods})
finally:
st_.pop_active()
errors += st_.state.verify_high(high_)
@ -1810,6 +1908,7 @@ def pkg(pkg_path,
salt '*' state.pkg /tmp/salt_state.tgz 760a9353810e36f6d81416366fc426dc md5
'''
# TODO - Add ability to download from salt master or other source
popts = _get_opts(**kwargs)
if not os.path.isfile(pkg_path):
return {}
if not salt.utils.hashutils.get_hash(pkg_path, hash_type) == pkg_sum:
@ -1844,7 +1943,6 @@ def pkg(pkg_path,
with salt.utils.files.fopen(roster_grains_json, 'r') as fp_:
roster_grains = json.load(fp_, object_hook=salt.utils.data.decode_dict)
popts = _get_opts(**kwargs)
if os.path.isfile(roster_grains_json):
popts['grains'] = roster_grains
popts['fileclient'] = 'local'

View File

@ -1218,41 +1218,55 @@ def mkdir(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Ensure that the directory is available and permissions are set.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directory, likely SYSTEM
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
To set advanced permissions use a list for the ``perms`` parameter,
ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
inheritance (bool):
If True the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created.
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful
@ -1289,10 +1303,16 @@ def mkdir(path,
# Set owner
if owner:
salt.utils.win_dacl.set_owner(path, owner)
salt.utils.win_dacl.set_owner(obj_name=path, principal=owner)
# Set permissions
set_perms(path, grant_perms, deny_perms, inheritance)
set_perms(
path=path,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
except WindowsError as exc:
raise CommandExecutionError(exc)
@ -1303,49 +1323,63 @@ def makedirs_(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Ensure that the parent directory containing this path is available.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directly, likely SYSTEM
.. note::
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
The path must end with a trailing slash otherwise the
directory(s) will be created up to the parent directory. For
example if path is ``C:\\temp\\test``, then it would be treated
as ``C:\\temp\\`` but if the path ends with a trailing slash
like ``C:\\temp\\test\\``, then it would be treated as
``C:\\temp\\test\\``.
.. code-block:: yaml
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directly, likely SYSTEM
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
.. code-block:: yaml
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
.. note::
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
The path must end with a trailing slash otherwise the directory(s) will
be created up to the parent directory. For example if path is
``C:\\temp\\test``, then it would be treated as ``C:\\temp\\`` but if
the path ends with a trailing slash like ``C:\\temp\\test\\``, then it
would be treated as ``C:\\temp\\test\\``.
inheritance (bool):
If True the object will inherit permissions from the parent, if
False, inheritance will be disabled. Inheritance setting will not
apply to parent directories if they must be created.
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful
@ -1405,7 +1439,13 @@ def makedirs_(path,
for directory_to_create in directories_to_create:
# all directories have the user, group and mode set!!
log.debug('Creating directory: %s', directory_to_create)
mkdir(directory_to_create, owner, grant_perms, deny_perms, inheritance)
mkdir(
path=directory_to_create,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
return True
@ -1414,41 +1454,54 @@ def makedirs_perms(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=True):
'''
Set owner and permissions for each directory created.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directory, likely SYSTEM
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
inheritance (bool):
If ``True`` the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful, otherwise raise an error
@ -1482,8 +1535,15 @@ def makedirs_perms(path,
try:
# Create the directory here, set inherited True because this is a
# parent directory, the inheritance setting will only apply to the
# child directory
makedirs_perms(head, owner, grant_perms, deny_perms, True)
# target directory. Reset will be False as we only want to reset
# the permissions on the target directory
makedirs_perms(
path=head,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=True,
reset=False)
except OSError as exc:
# be happy if someone already created the path
if exc.errno != errno.EEXIST:
@ -1492,7 +1552,13 @@ def makedirs_perms(path,
return {}
# Make the directory
mkdir(path, owner, grant_perms, deny_perms, inheritance)
mkdir(
path=path,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
return True
@ -1502,66 +1568,64 @@ def check_perms(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Set owner and permissions for each directory created.
Check owner and permissions for the passed directory. This function checks
the permissions and sets them, returning the changes made.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
ret (dict): A dictionary to append changes to and return. If not passed,
will create a new dictionary to return.
ret (dict):
A dictionary to append changes to and return. If not passed, will
create a new dictionary to return.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner to set for the directory.
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
check/grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
Default is ``None``.
.. code-block:: yaml
deny_perms (dict):
A dictionary containing the user/group and permissions to
check/deny. Default is ``None``.
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
inheritance (bool):
``True will check if inheritance is enabled and enable it. ``False``
will check if inheritance is disabled and disable it. Defaultl is
``True``.
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
reset (bool):
``True`` wil show what permisisons will be removed by resetting the
DACL. ``False`` will do nothing. Default is ``False``.
Returns:
bool: True if successful, otherwise raise an error
dict: A dictionary of changes that have been made
CLI Example:
.. code-block:: bash
# To grant the 'Users' group 'read & execute' permissions.
salt '*' file.check_perms C:\\Temp\\ Administrators "{'Users': {'perms': 'read_execute'}}"
# To see changes to ``C:\\Temp`` if the 'Users' group is given 'read & execute' permissions.
salt '*' file.check_perms C:\\Temp\\ {} Administrators "{'Users': {'perms': 'read_execute'}}"
# Locally using salt call
salt-call file.check_perms C:\\Temp\\ Administrators "{'Users': {'perms': 'read_execute', 'applies_to': 'this_folder_only'}}"
salt-call file.check_perms C:\\Temp\\ {} Administrators "{'Users': {'perms': 'read_execute', 'applies_to': 'this_folder_only'}}"
# Specify advanced attributes with a list
salt '*' file.check_perms C:\\Temp\\ Administrators "{'jsnuffy': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'files_only'}}"
salt '*' file.check_perms C:\\Temp\\ {} Administrators "{'jsnuffy': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'files_only'}}"
'''
path = os.path.expanduser(path)
if not ret:
ret = {'name': path,
'changes': {},
'pchanges': {},
'comment': [],
'result': True}
orig_comment = ''
@ -1571,14 +1635,16 @@ def check_perms(path,
# Check owner
if owner:
owner = salt.utils.win_dacl.get_name(owner)
current_owner = salt.utils.win_dacl.get_owner(path)
owner = salt.utils.win_dacl.get_name(principal=owner)
current_owner = salt.utils.win_dacl.get_owner(obj_name=path)
if owner != current_owner:
if __opts__['test'] is True:
ret['pchanges']['owner'] = owner
else:
try:
salt.utils.win_dacl.set_owner(path, owner)
salt.utils.win_dacl.set_owner(
obj_name=path,
principal=owner)
ret['changes']['owner'] = owner
except CommandExecutionError:
ret['result'] = False
@ -1586,7 +1652,7 @@ def check_perms(path,
'Failed to change owner to "{0}"'.format(owner))
# Check permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
cur_perms = salt.utils.win_dacl.get_permissions(obj_name=path)
# Verify Deny Permissions
changes = {}
@ -1594,7 +1660,7 @@ def check_perms(path,
for user in deny_perms:
# Check that user exists:
try:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
except CommandExecutionError:
ret['comment'].append(
'Deny Perms: User "{0}" missing from Target System'.format(user))
@ -1619,7 +1685,11 @@ def check_perms(path,
# Check Perms
if isinstance(deny_perms[user]['perms'], six.string_types):
if not salt.utils.win_dacl.has_permission(
path, user, deny_perms[user]['perms'], 'deny'):
obj_name=path,
principal=user,
permission=deny_perms[user]['perms'],
access_mode='deny',
exact=False):
changes[user] = {'perms': deny_perms[user]['perms']}
else:
for perm in deny_perms[user]['perms']:
@ -1640,9 +1710,10 @@ def check_perms(path,
changes[user]['applies_to'] = applies_to
if changes:
ret['pchanges']['deny_perms'] = {}
ret['changes']['deny_perms'] = {}
for user in changes:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
if __opts__['test'] is True:
ret['pchanges']['deny_perms'][user] = changes[user]
@ -1689,7 +1760,11 @@ def check_perms(path,
try:
salt.utils.win_dacl.set_permissions(
path, user, perms, 'deny', applies_to)
obj_name=path,
principal=user,
permissions=perms,
access_mode='deny',
applies_to=applies_to)
ret['changes']['deny_perms'][user] = changes[user]
except CommandExecutionError:
ret['result'] = False
@ -1703,7 +1778,7 @@ def check_perms(path,
for user in grant_perms:
# Check that user exists:
try:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
except CommandExecutionError:
ret['comment'].append(
'Grant Perms: User "{0}" missing from Target System'.format(user))
@ -1729,12 +1804,19 @@ def check_perms(path,
# Check Perms
if isinstance(grant_perms[user]['perms'], six.string_types):
if not salt.utils.win_dacl.has_permission(
path, user, grant_perms[user]['perms']):
obj_name=path,
principal=user,
permission=grant_perms[user]['perms'],
access_mode='grant'):
changes[user] = {'perms': grant_perms[user]['perms']}
else:
for perm in grant_perms[user]['perms']:
if not salt.utils.win_dacl.has_permission(
path, user, perm, exact=False):
obj_name=path,
principal=user,
permission=perm,
access_mode='grant',
exact=False):
if user not in changes:
changes[user] = {'perms': []}
changes[user]['perms'].append(grant_perms[user]['perms'])
@ -1750,11 +1832,12 @@ def check_perms(path,
changes[user]['applies_to'] = applies_to
if changes:
ret['pchanges']['grant_perms'] = {}
ret['changes']['grant_perms'] = {}
for user in changes:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
if __opts__['test'] is True:
ret['changes']['grant_perms'][user] = changes[user]
ret['pchanges']['grant_perms'][user] = changes[user]
else:
applies_to = None
if 'applies_to' not in changes[user]:
@ -1796,7 +1879,11 @@ def check_perms(path,
try:
salt.utils.win_dacl.set_permissions(
path, user, perms, 'grant', applies_to)
obj_name=path,
principal=user,
permissions=perms,
access_mode='grant',
applies_to=applies_to)
ret['changes']['grant_perms'][user] = changes[user]
except CommandExecutionError:
ret['result'] = False
@ -1806,12 +1893,14 @@ def check_perms(path,
# Check inheritance
if inheritance is not None:
if not inheritance == salt.utils.win_dacl.get_inheritance(path):
if not inheritance == salt.utils.win_dacl.get_inheritance(obj_name=path):
if __opts__['test'] is True:
ret['changes']['inheritance'] = inheritance
ret['pchanges']['inheritance'] = inheritance
else:
try:
salt.utils.win_dacl.set_inheritance(path, inheritance)
salt.utils.win_dacl.set_inheritance(
obj_name=path,
enabled=inheritance)
ret['changes']['inheritance'] = inheritance
except CommandExecutionError:
ret['result'] = False
@ -1819,6 +1908,45 @@ def check_perms(path,
'Failed to set inheritance for "{0}" to '
'{1}'.format(path, inheritance))
# Check reset
# If reset=True, which users will be removed as a result
if reset:
for user_name in cur_perms:
if user_name not in grant_perms:
if 'grant' in cur_perms[user_name] and not \
cur_perms[user_name]['grant']['inherited']:
if __opts__['test'] is True:
if 'remove_perms' not in ret['pchanges']:
ret['pchanges']['remove_perms'] = {}
ret['pchanges']['remove_perms'].update(
{user_name: cur_perms[user_name]})
else:
if 'remove_perms' not in ret['changes']:
ret['changes']['remove_perms'] = {}
salt.utils.win_dacl.rm_permissions(
obj_name=path,
principal=user_name,
ace_type='grant')
ret['changes']['remove_perms'].update(
{user_name: cur_perms[user_name]})
if user_name not in deny_perms:
if 'deny' in cur_perms[user_name] and not \
cur_perms[user_name]['deny']['inherited']:
if __opts__['test'] is True:
if 'remove_perms' not in ret['pchanges']:
ret['pchanges']['remove_perms'] = {}
ret['pchanges']['remove_perms'].update(
{user_name: cur_perms[user_name]})
else:
if 'remove_perms' not in ret['changes']:
ret['changes']['remove_perms'] = {}
salt.utils.win_dacl.rm_permissions(
obj_name=path,
principal=user_name,
ace_type='deny')
ret['changes']['remove_perms'].update(
{user_name: cur_perms[user_name]})
# Re-add the Original Comment if defined
if isinstance(orig_comment, six.string_types):
if orig_comment:
@ -1830,25 +1958,30 @@ def check_perms(path,
ret['comment'] = '\n'.join(ret['comment'])
# Set result for test = True
if __opts__['test'] is True and ret['changes']:
if __opts__['test'] and (ret['changes'] or ret['pchanges']):
ret['result'] = None
return ret
def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
def set_perms(path,
grant_perms=None,
deny_perms=None,
inheritance=True,
reset=False):
'''
Set permissions for the given path
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
set the ``applies_to`` setting here. The default for ``applise_to``
is ``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
@ -1863,7 +1996,10 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
To see a list of available attributes and applies to settings see
the documentation for salt.utils.win_dacl
the documentation for salt.utils.win_dacl.
A value of ``None`` will make no changes to the ``grant`` portion of
the DACL. Default is ``None``.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
@ -1871,13 +2007,27 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
A value of ``None`` will make no changes to the ``deny`` portion of
the DACL. Default is ``None``.
inheritance (bool):
If True the object will inherit permissions from the parent, if
False, inheritance will be disabled. Inheritance setting will not
apply to parent directories if they must be created
If ``True`` the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created. Default is
``False``.
reset (bool):
If ``True`` the existing DCL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded: Oxygen
Returns:
bool: True if successful, otherwise raise an error
bool: True if successful
Raises:
CommandExecutionError: If unsuccessful
CLI Example:
@ -1894,11 +2044,19 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
'''
ret = {}
# Get the DACL for the directory
dacl = salt.utils.win_dacl.dacl(path)
if reset:
# Get an empty DACL
dacl = salt.utils.win_dacl.dacl()
# Get current file/folder permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
# Get an empty perms dict
cur_perms = {}
else:
# Get the DACL for the directory
dacl = salt.utils.win_dacl.dacl(path)
# Get current file/folder permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
# Set 'deny' perms if any
if deny_perms is not None:

View File

@ -54,6 +54,7 @@ import salt.utils.args
import salt.utils.data
import salt.utils.files
import salt.utils.hashutils
import salt.utils.path
import salt.utils.pkg
import salt.utils.platform
import salt.utils.versions
@ -646,33 +647,10 @@ def _get_repo_details(saltenv):
# Do some safety checks on the repo_path as its contents can be removed,
# this includes check for bad coding
system_root = os.environ.get('SystemRoot', r'C:\Windows')
deny_paths = (
r'[a-z]\:\\$', # C:\, D:\, etc
r'\\$', # \
re.escape(system_root) # C:\Windows
)
if not salt.utils.path.safe_path(
path=local_dest,
allow_path='\\'.join([system_root, 'TEMP'])):
# Since the above checks anything in C:\Windows, there are some
# directories we may want to make exceptions for
allow_paths = (
re.escape('\\'.join([system_root, 'TEMP'])), # C:\Windows\TEMP
)
# Check the local_dest to make sure it's not one of the bad paths
good_path = True
for d_path in deny_paths:
if re.match(d_path, local_dest, flags=re.IGNORECASE) is not None:
# Found deny path
good_path = False
# If local_dest is one of the bad paths, check for exceptions
if not good_path:
for a_path in allow_paths:
if re.match(a_path, local_dest, flags=re.IGNORECASE) is not None:
# Found exception
good_path = True
if not good_path:
raise CommandExecutionError(
'Attempting to delete files from a possibly unsafe location: '
'{0}'.format(local_dest)

View File

@ -6,8 +6,7 @@ or for problem solving if your minion is having problems.
.. versionadded:: 0.12.0
:depends: - pythoncom
- wmi
:depends: - wmi
'''
# Import Python Libs

View File

@ -279,11 +279,23 @@ def _get_extra_options(**kwargs):
'''
ret = []
kwargs = salt.utils.args.clean_kwargs(**kwargs)
# Remove already handled options from kwargs
fromrepo = kwargs.pop('fromrepo', '')
repo = kwargs.pop('repo', '')
disablerepo = kwargs.pop('disablerepo', '')
enablerepo = kwargs.pop('enablerepo', '')
disable_excludes = kwargs.pop('disableexcludes', '')
branch = kwargs.pop('branch', '')
for key, value in six.iteritems(kwargs):
if isinstance(key, six.string_types):
if isinstance(value, six.string_types):
log.info('Adding extra option --%s=\'%s\'', key, value)
ret.append('--{0}=\'{1}\''.format(key, value))
elif value is True:
log.info('Adding extra option --%s', key)
ret.append('--{0}'.format(key))
log.info('Adding extra options %s', ret)
return ret

View File

@ -509,7 +509,7 @@ def destroy(zpool, force=False):
'''
ret = {}
ret[zpool] = {}
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
else:
zpool_cmd = _check_zpool()
@ -529,7 +529,7 @@ def destroy(zpool, force=False):
return ret
def scrub(zpool, stop=False):
def scrub(zpool, stop=False, pause=False):
'''
.. versionchanged:: 2016.3.0
@ -539,6 +539,13 @@ def scrub(zpool, stop=False):
name of storage pool
stop : boolean
if true, cancel ongoing scrub
pause : boolean
if true, pause ongoing scrub
.. versionadded:: Oxygen
.. note::
If both pause and stop are true, stop will win.
CLI Example:
@ -548,11 +555,18 @@ def scrub(zpool, stop=False):
'''
ret = {}
ret[zpool] = {}
if exists(zpool):
if __salt__['zpool.exists'](zpool):
zpool_cmd = _check_zpool()
cmd = '{zpool_cmd} scrub {stop}{zpool}'.format(
if stop:
action = '-s '
elif pause:
# NOTE: https://github.com/openzfs/openzfs/pull/407
action = '-p '
else:
action = ''
cmd = '{zpool_cmd} scrub {action}{zpool}'.format(
zpool_cmd=zpool_cmd,
stop='-s ' if stop else '',
action=action,
zpool=zpool
)
res = __salt__['cmd.run_all'](cmd, python_shell=False)
@ -567,7 +581,12 @@ def scrub(zpool, stop=False):
else:
ret[zpool]['error'] = res['stdout']
else:
ret[zpool]['scrubbing'] = True if not stop else False
if stop:
ret[zpool]['scrubbing'] = False
elif pause:
ret[zpool]['scrubbing'] = False
else:
ret[zpool]['scrubbing'] = True
else:
ret[zpool] = 'storage pool does not exist'
@ -595,6 +614,9 @@ def create(zpool, *vdevs, **kwargs):
additional pool properties
filesystem_properties : dict
additional filesystem properties
createboot : boolean
..versionadded:: Oxygen
create a boot partition
CLI Example:
@ -629,7 +651,7 @@ def create(zpool, *vdevs, **kwargs):
ret = {}
# Check if the pool_name is already being used
if exists(zpool):
if __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool already exists'
return ret
@ -641,14 +663,21 @@ def create(zpool, *vdevs, **kwargs):
zpool_cmd = _check_zpool()
force = kwargs.get('force', False)
altroot = kwargs.get('altroot', None)
createboot = kwargs.get('createboot', False)
mountpoint = kwargs.get('mountpoint', None)
properties = kwargs.get('properties', None)
filesystem_properties = kwargs.get('filesystem_properties', None)
cmd = '{0} create'.format(zpool_cmd)
# bootsize implies createboot
if properties and 'bootsize' in properties:
createboot = True
# apply extra arguments from kwargs
if force: # force creation
cmd = '{0} -f'.format(cmd)
if createboot: # create boot paritition
cmd = '{0} -B'.format(cmd)
if properties: # create "-o property=value" pairs
optlist = []
for prop in properties:
@ -712,7 +741,7 @@ def add(zpool, *vdevs, **kwargs):
ret = {}
# check for pool
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -765,7 +794,7 @@ def attach(zpool, device, new_device, force=False):
dlist = []
# check for pool
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -827,7 +856,7 @@ def detach(zpool, device):
dlist = []
# check for pool
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -848,6 +877,95 @@ def detach(zpool, device):
return ret
def split(zpool, newzpool, **kwargs):
'''
.. versionadded:: Oxygen
Splits devices off pool creating newpool.
.. note::
All vdevs in pool must be mirrors. At the time of the split,
newpool will be a replica of pool.
zpool : string
name of storage pool
newzpool : string
name of new storage pool
mountpoint : string
sets the mount point for the root dataset
altroot : string
sets altroot for newzpool
properties : dict
additional pool properties for newzpool
CLI Example:
.. code-block:: bash
salt '*' zpool.split datamirror databackup
salt '*' zpool.split datamirror databackup altroot=/backup
.. note::
Zpool properties can be specified at the time of creation of the pool by
passing an additional argument called "properties" and specifying the properties
with their respective values in the form of a python dictionary::
properties="{'property1': 'value1', 'property2': 'value2'}"
Example:
.. code-block:: bash
salt '*' zpool.split datamirror databackup properties="{'readonly': 'on'}"
'''
ret = {}
# Check if the pool_name is already being used
if __salt__['zpool.exists'](newzpool):
ret[newzpool] = 'storage pool already exists'
return ret
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exists'
return ret
zpool_cmd = _check_zpool()
altroot = kwargs.get('altroot', None)
properties = kwargs.get('properties', None)
cmd = '{0} split'.format(zpool_cmd)
# apply extra arguments from kwargs
if properties: # create "-o property=value" pairs
optlist = []
for prop in properties:
if isinstance(properties[prop], bool):
value = 'on' if properties[prop] else 'off'
else:
if ' ' in properties[prop]:
value = "'{0}'".format(properties[prop])
else:
value = properties[prop]
optlist.append('-o {0}={1}'.format(prop, value))
opts = ' '.join(optlist)
cmd = '{0} {1}'.format(cmd, opts)
if altroot: # set altroot
cmd = '{0} -R {1}'.format(cmd, altroot)
cmd = '{0} {1} {2}'.format(cmd, zpool, newzpool)
# Create storage pool
res = __salt__['cmd.run_all'](cmd, python_shell=False)
# Check and see if the pools is available
if res['retcode'] != 0:
ret[newzpool] = res['stderr'] if 'stderr' in res else res['stdout']
else:
ret[newzpool] = 'split off from {}'.format(zpool)
return ret
def replace(zpool, old_device, new_device=None, force=False):
'''
.. versionchanged:: 2016.3.0
@ -878,7 +996,7 @@ def replace(zpool, old_device, new_device=None, force=False):
'''
ret = {}
# Make sure pool is there
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -991,7 +1109,7 @@ def export(*pools, **kwargs):
return ret
for pool in pools:
if not exists(pool):
if not __salt__['zpool.exists'](pool):
ret[pool] = 'storage pool does not exist'
else:
pool_present.append(pool)
@ -1106,7 +1224,7 @@ def import_(zpool=None, new_name=None, **kwargs):
ret['error'] = res['stderr'] if 'stderr' in res else res['stdout']
else:
if zpool:
ret[zpool if not new_name else new_name] = 'imported' if exists(zpool if not new_name else new_name) else 'not found'
ret[zpool if not new_name else new_name] = 'imported' if __salt__['zpool.exists'](zpool if not new_name else new_name) else 'not found'
else:
ret = True
return ret
@ -1141,7 +1259,7 @@ def online(zpool, *vdevs, **kwargs):
dlist = []
# Check if the pool_name exists
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -1197,7 +1315,7 @@ def offline(zpool, *vdevs, **kwargs):
ret = {}
# Check if the pool_name exists
if not exists(zpool):
if not __salt__['zpool.exists'](zpool):
ret[zpool] = 'storage pool does not exist'
return ret
@ -1225,6 +1343,50 @@ def offline(zpool, *vdevs, **kwargs):
return ret
def labelclear(device, force=False):
'''
.. versionadded:: Oxygen
Removes ZFS label information from the specified device
.. warning::
The device must not be part of an active pool configuration.
device : string
device
force : boolean
treat exported or foreign devices as inactive
CLI Example:
.. code-block:: bash
salt '*' zpool.labelclear /path/to/dev
'''
ret = {}
zpool_cmd = _check_zpool()
cmd = '{zpool_cmd} labelclear {force}{device}'.format(
zpool_cmd=zpool_cmd,
force='-f ' if force else '',
device=device,
)
# Bring all specified devices offline
res = __salt__['cmd.run_all'](cmd, python_shell=False)
if res['retcode'] != 0:
## NOTE: skip the "use '-f' hint"
res['stderr'] = res['stderr'].split("\n")
if len(res['stderr']) >= 1:
if res['stderr'][0].startswith("use '-f'"):
del res['stderr'][0]
res['stderr'] = "\n".join(res['stderr'])
ret[device] = res['stderr'] if 'stderr' in res and res['stderr'] else res['stdout']
else:
ret[device] = 'cleared'
return ret
def reguid(zpool):
'''
.. versionadded:: 2016.3.0

View File

@ -399,6 +399,14 @@ def _systemd_scope():
and __salt__['config.get']('systemd.scope', True)
def _clean_cache():
'''
Clean cached results
'''
for cache_name in ['pkg.list_pkgs', 'pkg.list_provides']:
__context__.pop(cache_name, None)
def list_upgrades(refresh=True, **kwargs):
'''
List all available package upgrades on this system
@ -1049,6 +1057,10 @@ def install(name=None,
operator (<, >, <=, >=, =) and a version number (ex. '>1.2.3-4').
This parameter is ignored if ``pkgs`` or ``sources`` is passed.
resolve_capabilities
If this option is set to True zypper will take capabilites into
account. In this case names which are just provided by a package
will get installed. Default is False.
Multiple Package Installation Options:
@ -1164,7 +1176,10 @@ def install(name=None,
log.info('Targeting repo \'{0}\''.format(fromrepo))
else:
fromrepoopt = ''
cmd_install = ['install', '--name', '--auto-agree-with-licenses']
cmd_install = ['install', '--auto-agree-with-licenses']
cmd_install.append(kwargs.get('resolve_capabilities') and '--capability' or '--name')
if not refresh:
cmd_install.insert(0, '--no-refresh')
if skip_verify:
@ -1194,7 +1209,7 @@ def install(name=None,
downgrades = downgrades[500:]
__zypper__(no_repo_failure=ignore_repo_failure).call(*cmd)
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
new = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded()
# Handle packages which report multiple new versions
@ -1311,7 +1326,7 @@ def upgrade(refresh=True,
old = list_pkgs()
__zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update)
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
new = list_pkgs()
# Handle packages which report multiple new versions
@ -1360,7 +1375,7 @@ def _uninstall(name=None, pkgs=None):
__zypper__(systemd_scope=systemd_scope).call('remove', *targets[:500])
targets = targets[500:]
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
ret = salt.utils.data.compare_dicts(old, list_pkgs())
if errors:
@ -1750,7 +1765,7 @@ def list_installed_patterns():
return _get_patterns(installed_only=True)
def search(criteria, refresh=False):
def search(criteria, refresh=False, **kwargs):
'''
List known packags, available to the system.
@ -1759,26 +1774,94 @@ def search(criteria, refresh=False):
If set to False (default) it depends on zypper if a refresh is
executed.
match (str)
One of `exact`, `words`, `substrings`. Search for an `exact` match
or for the whole `words` only. Default to `substrings` to patch
partial words.
provides (bool)
Search for packages which provide the search strings.
recommends (bool)
Search for packages which recommend the search strings.
requires (bool)
Search for packages which require the search strings.
suggests (bool)
Search for packages which suggest the search strings.
conflicts (bool)
Search packages conflicting with search strings.
obsoletes (bool)
Search for packages which obsolete the search strings.
file_list (bool)
Search for a match in the file list of packages.
search_descriptions (bool)
Search also in package summaries and descriptions.
case_sensitive (bool)
Perform case-sensitive search.
installed_only (bool)
Show only installed packages.
not_installed_only (bool)
Show only packages which are not installed.
details (bool)
Show version and repository
CLI Examples:
.. code-block:: bash
salt '*' pkg.search <criteria>
'''
ALLOWED_SEARCH_OPTIONS = {
'provides': '--provides',
'recommends': '--recommends',
'requires': '--requires',
'suggests': '--suggests',
'conflicts': '--conflicts',
'obsoletes': '--obsoletes',
'file_list': '--file-list',
'search_descriptions': '--search-descriptions',
'case_sensitive': '--case-sensitive',
'installed_only': '--installed-only',
'not_installed_only': '-u',
'details': '--details'
}
if refresh:
refresh_db()
solvables = __zypper__.nolock.xml.call('se', criteria).getElementsByTagName('solvable')
cmd = ['search']
if kwargs.get('match') == 'exact':
cmd.append('--match-exact')
elif kwargs.get('match') == 'words':
cmd.append('--match-words')
elif kwargs.get('match') == 'substrings':
cmd.append('--match-substrings')
for opt in kwargs:
if opt in ALLOWED_SEARCH_OPTIONS:
cmd.append(ALLOWED_SEARCH_OPTIONS.get(opt))
cmd.append(criteria)
solvables = __zypper__.nolock.noraise.xml.call(*cmd).getElementsByTagName('solvable')
if not solvables:
raise CommandExecutionError(
'No packages found matching \'{0}\''.format(criteria)
)
out = {}
for solvable in [slv for slv in solvables
if slv.getAttribute('status') == 'not-installed'
and slv.getAttribute('kind') == 'package']:
out[solvable.getAttribute('name')] = {'summary': solvable.getAttribute('summary')}
for solvable in solvables:
out[solvable.getAttribute('name')] = dict()
for k, v in solvable.attributes.items():
out[solvable.getAttribute('name')][k] = v
return out
@ -2033,3 +2116,97 @@ def list_installed_patches():
salt '*' pkg.list_installed_patches
'''
return _get_patches(installed_only=True)
def list_provides(**kwargs):
'''
.. versionadded:: Oxygen
List package provides of installed packages as a dict.
{'<provided_name>': ['<package_name>', '<package_name>', ...]}
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_provides
'''
ret = __context__.get('pkg.list_provides')
if not ret:
cmd = ['rpm', '-qa', '--queryformat', '[%{PROVIDES}_|-%{NAME}\n]']
ret = dict()
for line in __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False).splitlines():
provide, realname = line.split('_|-')
if provide == realname:
continue
if provide not in ret:
ret[provide] = list()
ret[provide].append(realname)
__context__['pkg.list_provides'] = ret
return ret
def resolve_capabilities(pkgs, refresh, **kwargs):
'''
.. versionadded:: Oxygen
Convert name provides in ``pkgs`` into real package names if
``resolve_capabilities`` parameter is set to True. In case of
``resolve_capabilities`` is set to False the package list
is returned unchanged.
refresh
force a refresh if set to True.
If set to False (default) it depends on zypper if a refresh is
executed.
resolve_capabilities
If this option is set to True the input will be checked if
a package with this name exists. If not, this function will
search for a package which provides this name. If one is found
the output is exchanged with the real package name.
In case this option is set to False (Default) the input will
be returned unchanged.
CLI Examples:
.. code-block:: bash
salt '*' pkg.resolve_capabilities resolve_capabilities=True w3m_ssl
'''
if refresh:
refresh_db()
ret = list()
for pkg in pkgs:
if isinstance(pkg, dict):
name = next(iter(pkg))
version = pkg[name]
else:
name = pkg
version = None
if kwargs.get('resolve_capabilities', False):
try:
search(name, match='exact')
except CommandExecutionError:
# no package this such a name found
# search for a package which provides this name
try:
result = search(name, provides=True, match='exact')
if len(result) == 1:
name = result.keys()[0]
elif len(result) > 1:
log.warn("Found ambiguous match for capability '{0}'.".format(pkg))
except CommandExecutionError as exc:
# when search throws an exception stay with original name and version
log.debug("Search failed with: {0}".format(exc))
if version:
ret.append({name: version})
else:
ret.append(name)
return ret

View File

@ -138,7 +138,7 @@ class AsyncRemotePillar(RemotePillarMixin):
def __init__(self, opts, grains, minion_id, saltenv, ext=None, functions=None,
pillar_override=None, pillarenv=None, extra_minion_data=None):
self.opts = opts
self.opts['environment'] = saltenv
self.opts['saltenv'] = saltenv
self.ext = ext
self.grains = grains
self.minion_id = minion_id
@ -165,7 +165,7 @@ class AsyncRemotePillar(RemotePillarMixin):
'''
load = {'id': self.minion_id,
'grains': self.grains,
'saltenv': self.opts['environment'],
'saltenv': self.opts['saltenv'],
'pillarenv': self.opts['pillarenv'],
'pillar_override': self.pillar_override,
'extra_minion_data': self.extra_minion_data,
@ -198,7 +198,7 @@ class RemotePillar(RemotePillarMixin):
def __init__(self, opts, grains, minion_id, saltenv, ext=None, functions=None,
pillar_override=None, pillarenv=None, extra_minion_data=None):
self.opts = opts
self.opts['environment'] = saltenv
self.opts['saltenv'] = saltenv
self.ext = ext
self.grains = grains
self.minion_id = minion_id
@ -224,7 +224,7 @@ class RemotePillar(RemotePillarMixin):
'''
load = {'id': self.minion_id,
'grains': self.grains,
'saltenv': self.opts['environment'],
'saltenv': self.opts['saltenv'],
'pillarenv': self.opts['pillarenv'],
'pillar_override': self.pillar_override,
'extra_minion_data': self.extra_minion_data,
@ -310,25 +310,25 @@ class PillarCache(object):
return fresh_pillar.compile_pillar()
def compile_pillar(self, *args, **kwargs): # Will likely just be pillar_dirs
log.debug('Scanning pillar cache for information about minion {0} and saltenv {1}'.format(self.minion_id, self.saltenv))
log.debug('Scanning pillar cache for information about minion {0} and pillarenv {1}'.format(self.minion_id, self.pillarenv))
log.debug('Scanning cache: {0}'.format(self.cache._dict))
# Check the cache!
if self.minion_id in self.cache: # Keyed by minion_id
# TODO Compare grains, etc?
if self.saltenv in self.cache[self.minion_id]:
if self.pillarenv in self.cache[self.minion_id]:
# We have a cache hit! Send it back.
log.debug('Pillar cache hit for minion {0} and saltenv {1}'.format(self.minion_id, self.saltenv))
return self.cache[self.minion_id][self.saltenv]
log.debug('Pillar cache hit for minion {0} and pillarenv {1}'.format(self.minion_id, self.pillarenv))
return self.cache[self.minion_id][self.pillarenv]
else:
# We found the minion but not the env. Store it.
fresh_pillar = self.fetch_pillar()
self.cache[self.minion_id][self.saltenv] = fresh_pillar
log.debug('Pillar cache miss for saltenv {0} for minion {1}'.format(self.saltenv, self.minion_id))
self.cache[self.minion_id][self.pillarenv] = fresh_pillar
log.debug('Pillar cache miss for pillarenv {0} for minion {1}'.format(self.pillarenv, self.minion_id))
return fresh_pillar
else:
# We haven't seen this minion yet in the cache. Store it.
fresh_pillar = self.fetch_pillar()
self.cache[self.minion_id] = {self.saltenv: fresh_pillar}
self.cache[self.minion_id] = {self.pillarenv: fresh_pillar}
log.debug('Pillar cache miss for minion {0}'.format(self.minion_id))
log.debug('Current pillar cache: {0}'.format(self.cache._dict)) # FIXME hack!
return fresh_pillar
@ -445,9 +445,9 @@ class Pillar(object):
else:
opts['grains'] = grains
# Allow minion/CLI saltenv/pillarenv to take precedence over master
opts['environment'] = saltenv \
opts['saltenv'] = saltenv \
if saltenv is not None \
else opts.get('environment')
else opts.get('saltenv')
opts['pillarenv'] = pillarenv \
if pillarenv is not None \
else opts.get('pillarenv')

View File

@ -404,7 +404,7 @@ def ext_pillar(minion_id, pillar, *repos): # pylint: disable=unused-argument
# Map env if env == '__env__' before checking the env value
if env == '__env__':
env = opts.get('pillarenv') \
or opts.get('environment') \
or opts.get('saltenv') \
or opts.get('git_pillar_base')
log.debug('__env__ maps to %s', env)

View File

@ -6,8 +6,11 @@ from __future__ import absolute_import
# Import python libs
import os
import logging
import pickle
import logging
# Import Salt modules
import salt.utils.files
# Import Salt libs
import salt.utils.files
@ -22,7 +25,7 @@ DETAILS = {}
DETAILS['services'] = {'apache': 'running', 'ntp': 'running', 'samba': 'stopped'}
DETAILS['packages'] = {'coreutils': '1.0', 'apache': '2.4', 'tinc': '1.4', 'redbull': '999.99'}
FILENAME = os.tmpnam()
FILENAME = salt.utils.files.mkstemp()
# Want logging!
log = logging.getLogger(__file__)

View File

@ -196,9 +196,7 @@ def __virtual__():
Only return if all the modules are available
'''
if not salt.utils.path.which('racadm'):
log.critical('fx2 proxy minion needs "racadm" to be installed.')
return False
return False, 'fx2 proxy minion needs "racadm" to be installed.'
return True

View File

@ -16,9 +16,21 @@ Dependencies
The ``napalm`` proxy module requires NAPALM_ library to be installed: ``pip install napalm``
Please check Installation_ for complete details.
.. _NAPALM: https://napalm.readthedocs.io
.. _Installation: https://napalm.readthedocs.io/en/latest/installation.html
.. _NAPALM: https://napalm-automation.net/
.. _Installation: http://napalm.readthedocs.io/en/latest/installation/index.html
.. note::
Beginning with Salt release 2017.7.3, it is recommended to use
``napalm`` >= ``2.0.0``. The library has been unified into a monolithic
package, as in opposite to separate packages per driver. For more details
you can check `this document <https://napalm-automation.net/reunification/>`_.
While it will still work with the old packages, bear in mind that the NAPALM
core team will maintain only the main ``napalm`` package.
Moreover, for additional capabilities, the users can always define a
library that extends NAPALM's base capabilities and configure the
``provider`` option (see below).
Pillar
------
@ -59,7 +71,7 @@ always_alive: ``True``
.. versionadded:: 2017.7.0
provider: ``napalm_base``
The module that provides the ``get_network_device`` function.
The library that provides the ``get_network_device`` function.
This option is useful when the user has more specific needs and requires
to extend the NAPALM capabilities using a private library implementation.
The only constraint is that the alternative library needs to have the
@ -129,17 +141,7 @@ from __future__ import absolute_import
import logging
log = logging.getLogger(__file__)
# Import third party lib
try:
# will try to import NAPALM
# https://github.com/napalm-automation/napalm
# pylint: disable=W0611
import napalm_base
# pylint: enable=W0611
HAS_NAPALM = True
except ImportError:
HAS_NAPALM = False
# Import Salt modules
from salt.ext import six
import salt.utils.napalm
@ -163,7 +165,7 @@ DETAILS = {}
def __virtual__():
return HAS_NAPALM or (False, 'Please install the NAPALM library: `pip install napalm`!')
return salt.utils.napalm.virtual(__opts__, 'napalm', __file__)
# ----------------------------------------------------------------------------------------------------------------------
# helper functions -- will not be exported

View File

@ -294,9 +294,11 @@ def get_load(jid):
if not os.path.exists(jid_dir) or not os.path.exists(load_fn):
return {}
serial = salt.payload.Serial(__opts__)
ret = {}
with salt.utils.files.fopen(os.path.join(jid_dir, LOAD_P), 'rb') as rfh:
ret = serial.load(rfh)
if ret is None:
ret = {}
minions_cache = [os.path.join(jid_dir, MINIONS_P)]
minions_cache.extend(
glob.glob(os.path.join(jid_dir, SYNDIC_MINIONS_P.format('*')))

View File

@ -43,6 +43,7 @@ class RunnerClient(mixins.SyncClientMixin, mixins.AsyncClientMixin, object):
def __init__(self, opts):
self.opts = opts
self.context = {}
@property
def functions(self):
@ -51,11 +52,13 @@ class RunnerClient(mixins.SyncClientMixin, mixins.AsyncClientMixin, object):
self.utils = salt.loader.utils(self.opts)
# Must be self.functions for mixin to work correctly :-/
try:
self._functions = salt.loader.runner(self.opts, utils=self.utils)
self._functions = salt.loader.runner(
self.opts, utils=self.utils, context=self.context)
except AttributeError:
# Just in case self.utils is still not present (perhaps due to
# problems with the loader), load the runner funcs without them
self._functions = salt.loader.runner(self.opts)
self._functions = salt.loader.runner(
self.opts, context=self.context)
return self._functions

View File

@ -75,6 +75,7 @@ from __future__ import unicode_literals
# Import salt lib
import salt.output
import salt.utils.network
from salt.ext import six
from salt.ext.six.moves import map
@ -812,7 +813,25 @@ def find(addr, best=True, display=_DEFAULT_DISPLAY):
ip = '' # pylint: disable=invalid-name
ipnet = None
results = {}
results = {
'int_net': [],
'int_descr': [],
'int_name': [],
'int_ip': [],
'int_mac': [],
'int_device': [],
'lldp_descr': [],
'lldp_int': [],
'lldp_device': [],
'lldp_mac': [],
'lldp_device_int': [],
'mac_device': [],
'mac_int': [],
'arp_device': [],
'arp_int': [],
'arp_mac': [],
'arp_ip': []
}
if isinstance(addr, int):
results['mac'] = findmac(vlan=addr, display=display)
@ -826,6 +845,8 @@ def find(addr, best=True, display=_DEFAULT_DISPLAY):
except IndexError:
# no problem, let's keep searching
pass
if salt.utils.network.is_ipv6(addr):
mac = False
if not mac:
try:
ip = napalm_helpers.convert(napalm_helpers.ip, addr) # pylint: disable=invalid-name

View File

@ -52,6 +52,7 @@ def sync_all(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
ret['runners'] = sync_runners(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['wheel'] = sync_wheel(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['engines'] = sync_engines(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['thorium'] = sync_thorium(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['queues'] = sync_queues(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['pillar'] = sync_pillar(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['utils'] = sync_utils(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
@ -303,6 +304,32 @@ def sync_engines(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
extmod_blacklist=extmod_blacklist)[0]
def sync_thorium(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: Oxygen
Sync Thorium from ``salt://_thorium`` to the master
saltenv: ``base``
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
extmod_whitelist
comma-seperated list of modules to sync
extmod_blacklist
comma-seperated list of modules to blacklist based on type
CLI Example:
.. code-block:: bash
salt-run saltutil.sync_thorium
'''
return salt.utils.extmods.sync(__opts__, 'thorium', saltenv=saltenv, extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist)[0]
def sync_queues(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
Sync queue modules from ``salt://_queues`` to the master
@ -381,7 +408,7 @@ def sync_sdb(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_sdb`` to the master
Sync sdb modules from ``salt://_sdb`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -427,7 +454,7 @@ def sync_cache(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_cache`` to the master
Sync cache modules from ``salt://_cache`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -453,7 +480,7 @@ def sync_fileserver(saltenv='base', extmod_whitelist=None, extmod_blacklist=None
'''
.. versionadded:: Oxygen
Sync utils modules from ``salt://_fileserver`` to the master
Sync fileserver modules from ``salt://_fileserver`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -479,7 +506,7 @@ def sync_clouds(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_clouds`` to the master
Sync cloud modules from ``salt://_clouds`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -505,7 +532,7 @@ def sync_roster(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_roster`` to the master
Sync roster modules from ``salt://_roster`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than

View File

@ -22,7 +22,7 @@ def get(uri):
.. code-block:: bash
salt '*' sdb.get sdb://mymemcached/foo
salt-run sdb.get sdb://mymemcached/foo
'''
return salt.utils.sdb.sdb_get(uri, __opts__, __utils__)
@ -37,7 +37,7 @@ def set_(uri, value):
.. code-block:: bash
salt '*' sdb.set sdb://mymemcached/foo bar
salt-run sdb.set sdb://mymemcached/foo bar
'''
return salt.utils.sdb.sdb_set(uri, value, __opts__, __utils__)
@ -52,7 +52,7 @@ def delete(uri):
.. code-block:: bash
salt '*' sdb.delete sdb://mymemcached/foo
salt-run sdb.delete sdb://mymemcached/foo
'''
return salt.utils.sdb.sdb_delete(uri, __opts__, __utils__)

View File

@ -15,6 +15,24 @@ from salt.exceptions import SaltInvocationError
LOGGER = logging.getLogger(__name__)
def set_pause(jid, state_id, duration=None):
'''
Set up a state id pause, this instructs a running state to pause at a given
state id. This needs to pass in the jid of the running state and can
optionally pass in a duration in seconds.
'''
minion = salt.minion.MasterMinion(__opts__)
minion['state.set_pause'](jid, state_id, duration)
def rm_pause(jid, state_id, duration=None):
'''
Remove a pause from a jid, allowing it to continue
'''
minion = salt.minion.MasterMinion(__opts__)
minion['state.rm_pause'](jid, state_id)
def orchestrate(mods,
saltenv='base',
test=None,

View File

@ -33,6 +33,10 @@ Optional configuration:
merge:
strategy: smart
merge_list: false
gpg: true
Setting the ``gpg`` option to ``true`` (default is ``false``) will decrypt embedded
GPG-encrypted data using the :py:mod:`GPG renderer <salt.renderers.gpg>`.
'''
# import python libs
@ -44,6 +48,7 @@ import salt.loader
import salt.utils.data
import salt.utils.files
import salt.utils.dictupdate
import salt.renderers.gpg
log = logging.getLogger(__name__)
@ -52,7 +57,11 @@ __func_alias__ = {
}
def set_(*args, **kwargs):
def __virtual__():
return True
def set_(*args, **kwargs): # pylint: disable=W0613
'''
Setting a value is not supported; edit the YAML files directly
'''
@ -61,9 +70,14 @@ def set_(*args, **kwargs):
def get(key, profile=None): # pylint: disable=W0613
'''
Get a value from the REST interface
Get a value from the dictionary
'''
data = _get_values(profile)
# Decrypt SDB data if specified in the profile
if profile and profile.get('gpg', False):
return salt.utils.data.traverse_dict_and_list(_decrypt(data), key, None)
return salt.utils.data.traverse_dict_and_list(data, key, None)
@ -77,12 +91,19 @@ def _get_values(profile=None):
ret = {}
for fname in profile.get('files', []):
try:
with salt.utils.files.flopen(fname) as f:
contents = serializers.yaml.deserialize(f)
ret = salt.utils.dictupdate.merge(ret, contents,
**profile.get('merge', {}))
with salt.utils.files.flopen(fname) as yamlfile:
contents = serializers.yaml.deserialize(yamlfile)
ret = salt.utils.dictupdate.merge(
ret, contents, **profile.get('merge', {}))
except IOError:
log.error("File not found '{0}'".format(fname))
except TypeError:
log.error("Error deserializing sdb file '{0}'".format(fname))
return ret
def _decrypt(data):
'''
Pass the dictionary through the GPG renderer to decrypt encrypted values.
'''
return salt.loader.render(__opts__, __salt__)['gpg'](data)

View File

@ -763,7 +763,7 @@ class State(object):
self.opts,
self.opts[u'grains'],
self.opts[u'id'],
self.opts[u'environment'],
self.opts[u'saltenv'],
pillar_override=self._pillar_override,
pillarenv=self.opts.get(u'pillarenv'))
return pillar.compile_pillar()
@ -1892,20 +1892,27 @@ class State(object):
(u'onlyif' in low and u'{0[state]}.mod_run_check'.format(low) not in self.states):
ret.update(self._run_check(low))
if u'saltenv' in low:
inject_globals[u'__env__'] = six.text_type(low[u'saltenv'])
elif isinstance(cdata[u'kwargs'].get(u'env', None), six.string_types):
# User is using a deprecated env setting which was parsed by
# format_call.
# We check for a string type since module functions which
# allow setting the OS environ also make use of the "env"
# keyword argument, which is not a string
inject_globals[u'__env__'] = six.text_type(cdata[u'kwargs'][u'env'])
elif u'__env__' in low:
# The user is passing an alternative environment using __env__
# which is also not the appropriate choice, still, handle it
inject_globals[u'__env__'] = six.text_type(low[u'__env__'])
else:
if not self.opts.get(u'lock_saltenv', False):
# NOTE: Overriding the saltenv when lock_saltenv is blocked in
# salt/modules/state.py, before we ever get here, but this
# additional check keeps use of the State class outside of the
# salt/modules/state.py from getting around this setting.
if u'saltenv' in low:
inject_globals[u'__env__'] = six.text_type(low[u'saltenv'])
elif isinstance(cdata[u'kwargs'].get(u'env', None), six.string_types):
# User is using a deprecated env setting which was parsed by
# format_call.
# We check for a string type since module functions which
# allow setting the OS environ also make use of the "env"
# keyword argument, which is not a string
inject_globals[u'__env__'] = six.text_type(cdata[u'kwargs'][u'env'])
elif u'__env__' in low:
# The user is passing an alternative environment using
# __env__ which is also not the appropriate choice, still,
# handle it
inject_globals[u'__env__'] = six.text_type(low[u'__env__'])
if u'__env__' not in inject_globals:
# Let's use the default environment
inject_globals[u'__env__'] = u'base'
@ -1918,6 +1925,8 @@ class State(object):
if self.mocked:
ret = mock_ret(cdata)
else:
# Check if this low chunk is paused
self.check_pause(low)
# Execute the state function
if not low.get(u'__prereq__') and low.get(u'parallel'):
# run the state call in parallel, but only if not in a prereq
@ -2127,6 +2136,48 @@ class State(object):
return not running[tag][u'result']
return False
def check_pause(self, low):
'''
Check to see if this low chunk has been paused
'''
if not self.jid:
# Can't pause on salt-ssh since we can't track continuous state
return
pause_path = os.path.join(self.opts[u'cachedir'], 'state_pause', self.jid)
start = time.time()
if os.path.isfile(pause_path):
try:
while True:
tries = 0
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
try:
pdat = msgpack.loads(fp_.read())
except msgpack.UnpackValueError:
# Reading race condition
if tries > 10:
# Break out if there are a ton of read errors
return
tries += 1
time.sleep(1)
continue
id_ = low[u'__id__']
key = u''
if id_ in pdat:
key = id_
elif u'__all__' in pdat:
key = u'__all__'
if key:
if u'duration' in pdat[key]:
now = time.time()
if now - start > pdat[key][u'duration']:
return
else:
return
time.sleep(1)
except Exception as exc:
log.error('Failed to read in pause data for file located at: %s', pause_path)
return
def reconcile_procs(self, running):
'''
Check the running dict for processes and resolve them
@ -2682,6 +2733,14 @@ class State(object):
except OSError:
log.debug(u'File %s does not exist, no need to cleanup', accum_data_path)
_cleanup_accumulator_data()
if self.jid is not None:
pause_path = os.path.join(self.opts[u'cachedir'], u'state_pause', self.jid)
if os.path.isfile(pause_path):
try:
os.remove(pause_path)
except OSError:
# File is not present, all is well
pass
return ret
@ -2900,32 +2959,32 @@ class BaseHighState(object):
found = 0 # did we find any contents in the top files?
# Gather initial top files
merging_strategy = self.opts[u'top_file_merging_strategy']
if merging_strategy == u'same' and not self.opts[u'environment']:
if merging_strategy == u'same' and not self.opts[u'saltenv']:
if not self.opts[u'default_top']:
raise SaltRenderError(
u'top_file_merging_strategy set to \'same\', but no '
u'default_top configuration option was set'
)
if self.opts[u'environment']:
if self.opts[u'saltenv']:
contents = self.client.cache_file(
self.opts[u'state_top'],
self.opts[u'environment']
self.opts[u'saltenv']
)
if contents:
found = 1
tops[self.opts[u'environment']] = [
tops[self.opts[u'saltenv']] = [
compile_template(
contents,
self.state.rend,
self.state.opts[u'renderer'],
self.state.opts[u'renderer_blacklist'],
self.state.opts[u'renderer_whitelist'],
saltenv=self.opts[u'environment']
saltenv=self.opts[u'saltenv']
)
]
else:
tops[self.opts[u'environment']] = [{}]
tops[self.opts[u'saltenv']] = [{}]
else:
found = 0
@ -3257,8 +3316,8 @@ class BaseHighState(object):
matches = DefaultOrderedDict(OrderedDict)
# pylint: disable=cell-var-from-loop
for saltenv, body in six.iteritems(top):
if self.opts[u'environment']:
if saltenv != self.opts[u'environment']:
if self.opts[u'saltenv']:
if saltenv != self.opts[u'saltenv']:
continue
for match, data in six.iteritems(body):
def _filter_matches(_match, _data, _opts):

View File

@ -13,6 +13,7 @@ import os
import re
import shlex
import stat
import string
import tarfile
from contextlib import closing
@ -771,12 +772,24 @@ def extracted(name,
return ret
urlparsed_source = _urlparse(source_match)
source_hash_basename = urlparsed_source.path or urlparsed_source.netloc
urlparsed_scheme = urlparsed_source.scheme
urlparsed_path = os.path.join(
urlparsed_source.netloc,
urlparsed_source.path).rstrip(os.sep)
source_is_local = urlparsed_source.scheme in salt.utils.files.LOCAL_PROTOS
# urlparsed_scheme will be the drive letter if this is a Windows file path
# This checks for a drive letter as the scheme and changes it to file
if urlparsed_scheme and \
urlparsed_scheme.lower() in string.ascii_lowercase:
urlparsed_path = ':'.join([urlparsed_scheme, urlparsed_path])
urlparsed_scheme = 'file'
source_hash_basename = urlparsed_path or urlparsed_source.netloc
source_is_local = urlparsed_scheme in salt.utils.files.LOCAL_PROTOS
if source_is_local:
# Get rid of "file://" from start of source_match
source_match = os.path.realpath(os.path.expanduser(urlparsed_source.path))
source_match = os.path.realpath(os.path.expanduser(urlparsed_path))
if not os.path.isfile(source_match):
ret['comment'] = 'Source file \'{0}\' does not exist'.format(
salt.utils.url.redact_http_basic_auth(source_match))

View File

@ -1498,13 +1498,8 @@ def accept_vpc_peering_connection(name=None, conn_id=None, conn_name=None,
'''
log.debug('Called state to accept VPC peering connection')
pending = __salt__['boto_vpc.is_peering_connection_pending'](
conn_id=conn_id,
conn_name=conn_name,
region=region,
key=key,
keyid=keyid,
profile=profile
)
conn_id=conn_id, conn_name=conn_name, region=region, key=key,
keyid=keyid, profile=profile)
ret = {
'name': name,
@ -1515,30 +1510,25 @@ def accept_vpc_peering_connection(name=None, conn_id=None, conn_name=None,
if not pending:
ret['result'] = True
ret['changes'].update({
'old': 'No pending VPC peering connection found. '
'Nothing to be done.'
})
ret['changes'].update({'old':
'No pending VPC peering connection found. Nothing to be done.'})
return ret
if __opts__['test']:
ret['changes'].update({'old': 'Pending VPC peering connection found '
'and can be accepted'})
ret['changes'].update({'old':
'Pending VPC peering connection found and can be accepted'})
return ret
log.debug('Calling module to accept this VPC peering connection')
result = __salt__['boto_vpc.accept_vpc_peering_connection'](
conn_id=conn_id, name=conn_name, region=region, key=key,
fun = 'boto_vpc.accept_vpc_peering_connection'
log.debug('Calling `{0}()` to accept this VPC peering connection'.format(fun))
result = __salt__[fun](conn_id=conn_id, name=conn_name, region=region, key=key,
keyid=keyid, profile=profile)
if 'error' in result:
ret['comment'] = "Failed to request VPC peering: {0}".format(result['error'])
ret['comment'] = "Failed to accept VPC peering: {0}".format(result['error'])
ret['result'] = False
return ret
ret['changes'].update({
'old': '',
'new': result['msg']
})
ret['changes'].update({'old': '', 'new': result['msg']})
return ret

View File

@ -392,9 +392,9 @@ def absent(name,
The special keyword used in the job (eg. @reboot, @hourly...).
Quotes must be used, otherwise PyYAML will strip the '@' sign.
'''
### NOTE: The keyword arguments in **kwargs are ignored in this state, but
### cannot be removed from the function definition, otherwise the use
### of unsupported arguments will result in a traceback.
# NOTE: The keyword arguments in **kwargs are ignored in this state, but
# cannot be removed from the function definition, otherwise the use
# of unsupported arguments will result in a traceback.
name = name.strip()
if identifier is False:
@ -566,6 +566,7 @@ def file(name,
user,
group,
mode,
[], # no special attrs for cron
template,
context,
defaults,

View File

@ -761,7 +761,8 @@ def _check_directory_win(name,
win_owner,
win_perms=None,
win_deny_perms=None,
win_inheritance=None):
win_inheritance=None,
win_perms_reset=None):
'''
Check what changes need to be made on a directory
'''
@ -879,6 +880,20 @@ def _check_directory_win(name,
if not win_inheritance == salt.utils.win_dacl.get_inheritance(name):
changes['inheritance'] = win_inheritance
# Check reset
if win_perms_reset:
for user_name in perms:
if user_name not in win_perms:
if 'grant' in perms[user_name] and not perms[user_name]['grant']['inherited']:
if 'remove_perms' not in changes:
changes['remove_perms'] = {}
changes['remove_perms'].update({user_name: perms[user_name]})
if user_name not in win_deny_perms:
if 'deny' in perms[user_name] and not perms[user_name]['deny']['inherited']:
if 'remove_perms' not in changes:
changes['remove_perms'] = {}
changes['remove_perms'].update({user_name: perms[user_name]})
if changes:
return None, 'The directory "{0}" will be changed'.format(name), changes
@ -1566,6 +1581,7 @@ def managed(name,
win_perms=None,
win_deny_perms=None,
win_inheritance=True,
win_perms_reset=False,
**kwargs):
r'''
Manage a given file, this function allows for a file to be downloaded from
@ -2072,6 +2088,13 @@ def managed(name,
.. versionadded:: 2017.7.0
win_perms_reset : False
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Here's an example using the above ``win_*`` parameters:
.. code-block:: yaml
@ -2314,8 +2337,13 @@ def managed(name,
# Check and set the permissions if necessary
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
name, ret, user, group, mode, attrs, follow_symlinks)
@ -2356,8 +2384,13 @@ def managed(name,
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
if isinstance(ret['pchanges'], tuple):
ret['result'], ret['comment'] = ret['pchanges']
@ -2448,6 +2481,7 @@ def managed(name,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset,
encoding=encoding,
encoding_errors=encoding_errors,
**kwargs)
@ -2517,6 +2551,7 @@ def managed(name,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset,
encoding=encoding,
encoding_errors=encoding_errors,
**kwargs)
@ -2590,6 +2625,7 @@ def directory(name,
win_perms=None,
win_deny_perms=None,
win_inheritance=True,
win_perms_reset=False,
**kwargs):
r'''
Ensure that a named directory is present and has the right perms
@ -2751,6 +2787,13 @@ def directory(name,
.. versionadded:: 2017.7.0
win_perms_reset : False
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Here's an example using the above ``win_*`` parameters:
.. code-block:: yaml
@ -2855,13 +2898,23 @@ def directory(name,
elif force:
# Remove whatever is in the way
if os.path.isfile(name):
os.remove(name)
ret['changes']['forced'] = 'File was forcibly replaced'
if __opts__['test']:
ret['pchanges']['forced'] = 'File was forcibly replaced'
else:
os.remove(name)
ret['changes']['forced'] = 'File was forcibly replaced'
elif __salt__['file.is_link'](name):
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Symlink was forcibly replaced'
if __opts__['test']:
ret['pchanges']['forced'] = 'Symlink was forcibly replaced'
else:
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Symlink was forcibly replaced'
else:
__salt__['file.remove'](name)
if __opts__['test']:
ret['pchanges']['forced'] = 'Directory was forcibly replaced'
else:
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Directory was forcibly replaced'
else:
if os.path.isfile(name):
return _error(
@ -2874,17 +2927,26 @@ def directory(name,
# Check directory?
if salt.utils.platform.is_windows():
presult, pcomment, ret['pchanges'] = _check_directory_win(
name, win_owner, win_perms, win_deny_perms, win_inheritance)
presult, pcomment, pchanges = _check_directory_win(
name=name,
win_owner=win_owner,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset)
else:
presult, pcomment, ret['pchanges'] = _check_directory(
presult, pcomment, pchanges = _check_directory(
name, user, group, recurse or [], dir_mode, clean, require,
exclude_pat, max_depth, follow_symlinks)
if __opts__['test']:
if pchanges:
ret['pchanges'].update(pchanges)
# Don't run through the reset of the function if there are no changes to be
# made
if not ret['pchanges'] or __opts__['test']:
ret['result'] = presult
ret['comment'] = pcomment
ret['changes'] = ret['pchanges']
return ret
if not os.path.isdir(name):
@ -2900,8 +2962,13 @@ def directory(name,
if not os.path.isdir(drive):
return _error(
ret, 'Drive {0} is not mapped'.format(drive))
__salt__['file.makedirs'](name, win_owner, win_perms,
win_deny_perms, win_inheritance)
__salt__['file.makedirs'](
path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
__salt__['file.makedirs'](name, user=user, group=group,
mode=dir_mode)
@ -2910,8 +2977,13 @@ def directory(name,
ret, 'No directory to create {0} in'.format(name))
if salt.utils.platform.is_windows():
__salt__['file.mkdir'](name, win_owner, win_perms, win_deny_perms,
win_inheritance)
__salt__['file.mkdir'](
path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
__salt__['file.mkdir'](name, user=user, group=group, mode=dir_mode)
@ -2925,7 +2997,13 @@ def directory(name,
if not children_only:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None, win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, perms = __salt__['file.check_perms'](
name, ret, user, group, dir_mode, None, follow_symlinks)
@ -2996,8 +3074,13 @@ def directory(name,
try:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
full, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=full,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
full, ret, user, group, file_mode, None, follow_symlinks)
@ -3011,8 +3094,13 @@ def directory(name,
try:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
full, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=full,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
full, ret, user, group, dir_mode, None, follow_symlinks)
@ -3034,7 +3122,8 @@ def directory(name,
if children_only:
ret['comment'] = u'Directory {0}/* updated'.format(name)
else:
ret['comment'] = u'Directory {0} updated'.format(name)
if ret['changes']:
ret['comment'] = u'Directory {0} updated'.format(name)
if __opts__['test']:
ret['comment'] = 'Directory {0} not updated'.format(name)

View File

@ -82,8 +82,9 @@ import logging
# Import Salt Libs
from salt.exceptions import CommandExecutionError
import salt.utils.path
from salt.output import nested
import salt.utils.path
import salt.utils.versions
log = logging.getLogger(__name__)
@ -231,8 +232,10 @@ def present(name,
# if prune_services == None, set to True and log a deprecation warning
if prune_services is None:
prune_services = True
salt.utils.warn_until('Neon',
'The \'prune_services\' argument default is currently True, but will be changed to True in future releases.')
salt.utils.versions.warn_until(
'Neon',
'The \'prune_services\' argument default is currently True, '
'but will be changed to True in future releases.')
ret = _present(name, block_icmp, prune_block_icmp, default, masquerade, ports, prune_ports,
port_fwd, prune_port_fwd, services, prune_services, interfaces, prune_interfaces,

View File

@ -293,7 +293,7 @@ def absent(name, entry=None, entries=None, family='ipv4', **kwargs):
kwargs['set_name'],
family)
else:
command = __salt__['ipset.delete'](kwargs['set_name'], _entry, family, **kwargs)
command = __salt__['ipset.delete'](kwargs['set_name'], entry, family, **kwargs)
if 'Error' not in command:
ret['changes'] = {'locale': name}
ret['result'] = True

View File

@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Domains
========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create domain:
keystone_domain.present:
- name: domain1
create domain with optional params:
keystone_domain.present:
- name: domain1
- enabled: False
- description: 'my domain'
delete domain:
keystone_domain.absent:
- name: domain1
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_domain'
def __virtual__():
if 'keystoneng.domain_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure domain exists and is up-to-date
name
Name of the domain
enabled
Boolean to control if domain is enabled
description
An arbitrary description of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
domain = __salt__['keystoneng.domain_get'](name=name)
if not domain:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be created.'.format(name)
return ret
kwargs['name'] = name
domain = __salt__['keystoneng.domain_create'](**kwargs)
ret['changes'] = domain
ret['comment'] = 'Created domain'
return ret
changes = __salt__['keystoneng.compare_changes'](domain, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be updated.'.format(name)
return ret
kwargs['domain_id'] = domain.id
__salt__['keystoneng.domain_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated domain'
return ret
def absent(name, auth=None):
'''
Ensure domain does not exist
name
Name of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
domain = __salt__['keystoneng.domain_get'](name=name)
if domain:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'name': name}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be deleted.'.format(name)
return ret
__salt__['keystoneng.domain_delete'](name=domain)
ret['changes']['id'] = domain.id
ret['comment'] = 'Deleted domain'
return ret

View File

@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Endpoints
==========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create endpoint:
keystone_endpoint.present:
- name: public
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
destroy endpoint:
keystone_endpoint.absent:
- name: public
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
create multiple endpoints:
keystone_endpoint.absent:
- names:
- public
- admin
- internal
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_endpoint'
def __virtual__():
if 'keystoneng.endpoint_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(ret, name, service_name, kwargs):
'''
Returns: tuple whose first element is a bool indicating success or failure
and the second element is either a ret dict for salt or an object
'''
if 'interface' not in kwargs and 'public_url' not in kwargs:
kwargs['interface'] = name
service = __salt__['keystoneng.service_get'](name_or_id=service_name)
if not service:
ret['comment'] = 'Cannot find service'
ret['result'] = False
return (False, ret)
filters = kwargs.copy()
filters.pop('enabled', None)
filters.pop('url', None)
filters['service_id'] = service.id
kwargs['service_name_or_id'] = service.id
endpoints = __salt__['keystoneng.endpoint_search'](filters=filters)
if len(endpoints) > 1:
ret['comment'] = "Multiple endpoints match criteria"
ret['result'] = False
return ret
endpoint = endpoints[0] if endpoints else None
return (True, endpoint)
def present(name, service_name, auth=None, **kwargs):
'''
Ensure an endpoint exists and is up-to-date
name
Interface name
url
URL of the endpoint
service_name
Service name or ID
region
The region name to assign the endpoint
enabled
Boolean to control if endpoint is enabled
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
success, val = _, endpoint = _common(ret, name, service_name, kwargs)
if not success:
return val
if not endpoint:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be created.'
return ret
# NOTE(SamYaple): Endpoints are returned as a list which can contain
# several items depending on the options passed
endpoints = __salt__['keystoneng.endpoint_create'](**kwargs)
if len(endpoints) == 1:
ret['changes'] = endpoints[0]
else:
for i, endpoint in enumerate(endpoints):
ret['changes'][i] = endpoint
ret['comment'] = 'Created endpoint'
return ret
changes = __salt__['keystoneng.compare_changes'](endpoint, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be updated.'
return ret
kwargs['endpoint_id'] = endpoint.id
__salt__['keystoneng.endpoint_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated endpoint'
return ret
def absent(name, service_name, auth=None, **kwargs):
'''
Ensure an endpoint does not exists
name
Interface name
url
URL of the endpoint
service_name
Service name or ID
region
The region name to assign the endpoint
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
success, val = _, endpoint = _common(ret, name, service_name, kwargs)
if not success:
return val
if endpoint:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': endpoint.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be deleted.'
return ret
__salt__['keystoneng.endpoint_delete'](id=endpoint.id)
ret['changes']['id'] = endpoint.id
ret['comment'] = 'Deleted endpoint'
return ret

View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Groups
=======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create group:
keystone_group.present:
- name: group1
delete group:
keystone_group.absent:
- name: group1
create group with optional params:
keystone_group.present:
- name: group1
- domain: domain1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_endpoint'
def __virtual__():
if 'keystoneng.group_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(kwargs):
'''
Returns: None if group wasn't found, otherwise a group object
'''
search_kwargs = {'name': kwargs['name']}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['filters'] = {'domain_id': domain_id}
kwargs['domain'] = domain
return __salt__['keystoneng.group_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure an group exists and is up-to-date
name
Name of the group
domain
The name or id of the domain
description
An arbitrary description of the group
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_cloud'](auth)
kwargs['name'] = name
group = _common(kwargs)
if group is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be created.'
return ret
group = __salt__['keystoneng.group_create'](**kwargs)
ret['changes'] = group
ret['comment'] = 'Created group'
return ret
changes = __salt__['keystoneng.compare_changes'](group, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be updated.'
return ret
__salt__['keystoneng.group_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated group'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure group does not exist
name
Name of the group
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_cloud'](auth)
kwargs['name'] = name
group = _common(kwargs)
if group:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': group.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be deleted.'
return ret
__salt__['keystoneng.group_delete'](name=group)
ret['changes']['id'] = group.id
ret['comment'] = 'Deleted group'
return ret

View File

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Projects
=========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create project:
keystone_project.present:
- name: project1
delete project:
keystone_project.absent:
- name: project1
create project with optional params:
keystone_project.present:
- name: project1
- domain: domain1
- enabled: False
- description: 'my project'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_project'
def __virtual__():
if 'keystoneng.project_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(name, kwargs):
'''
Returns: None if project wasn't found, otherwise a group object
'''
search_kwargs = {'name': name}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['domain_id'] = domain_id
kwargs['domain_id'] = domain_id
return __salt__['keystoneng.project_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure a project exists and is up-to-date
name
Name of the project
domain
The name or id of the domain
description
An arbitrary description of the project
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
project = _common(name, kwargs)
if project is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be created.'
return ret
project = __salt__['keystoneng.project_create'](**kwargs)
ret['changes'] = project
ret['comment'] = 'Created project'
return ret
changes = __salt__['keystoneng.compare_changes'](project, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be updated.'
return ret
__salt__['keystoneng.project_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated project'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure a project does not exists
name
Name of the project
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
project = _common(name, kwargs)
if project:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': project.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be deleted.'
return ret
__salt__['keystoneng.project_delete'](name=project)
ret['changes']['id'] = project.id
ret['comment'] = 'Deleted project'
return ret

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Roles
======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create role:
keystone_role.present:
- name: role1
delete role:
keystone_role.absent:
- name: role1
create role with optional params:
keystone_role.present:
- name: role1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_role'
def __virtual__():
if 'keystoneng.role_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure an role exists
name
Name of the role
description
An arbitrary description of the role
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
role = __salt__['keystoneng.role_get'](**kwargs)
if not role:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Role will be created.'
return ret
role = __salt__['keystoneng.role_create'](**kwargs)
ret['changes']['id'] = role.id
ret['changes']['name'] = role.name
ret['comment'] = 'Created role'
return ret
# NOTE(SamYaple): Update support pending https://review.openstack.org/#/c/496992/
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure role does not exist
name
Name of the role
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
role = __salt__['keystoneng.role_get'](**kwargs)
if role:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': role.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Role will be deleted.'
return ret
__salt__['keystoneng.role_delete'](name=role)
ret['changes']['id'] = role.id
ret['comment'] = 'Deleted role'
return ret

View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Role Grants
============================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create group:
keystone_group.present:
- name: group1
delete group:
keystone_group.absent:
- name: group1
create group with optional params:
keystone_group.present:
- name: group1
- domain: domain1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_role_grant'
def __virtual__():
if 'keystoneng.role_grant' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _get_filters(kwargs):
role_kwargs = {'name': kwargs.pop('role')}
if 'role_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('role_domain'))
if domain:
role_kwargs['domain_id'] = domain.id \
if hasattr(domain, 'id') else domain
role = __salt__['keystoneng.role_get'](**role_kwargs)
kwargs['name'] = role
filters = {'role': role.id if hasattr(role, 'id') else role}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
kwargs['domain'] = filters['domain'] = \
domain.id if hasattr(domain, 'id') else domain
if 'project' in kwargs:
project_kwargs = {'name': kwargs.pop('project')}
if 'project_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('project_domain'))
if domain:
project_kwargs['domain_id'] = domain.id
project = __salt__['keystoneng.get_entity'](
'project', **project_kwargs)
kwargs['project'] = project
filters['project'] = project.id if hasattr(project, 'id') else project
if 'user' in kwargs:
user_kwargs = {'name': kwargs.pop('user')}
if 'user_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('user_domain'))
if domain:
user_kwargs['domain_id'] = domain.id
user = __salt__['keystoneng.get_entity']('user', **user_kwargs)
kwargs['user'] = user
filters['user'] = user.id if hasattr(user, 'id') else user
if 'group' in kwargs:
group_kwargs = {'name': kwargs['group']}
if 'group_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('group_domain'))
if domain:
group_kwargs['domain_id'] = domain.id
group = __salt__['keystoneng.get_entity']('group', **group_kwargs)
kwargs['group'] = group
filters['group'] = group.id if hasattr(group, 'id') else group
return filters, kwargs
def present(name, auth=None, **kwargs):
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
if 'role' not in kwargs:
kwargs['role'] = name
filters, kwargs = _get_filters(kwargs)
grants = __salt__['keystoneng.role_assignment_list'](filters=filters)
if not grants:
__salt__['keystoneng.role_grant'](**kwargs)
for k, v in filters.items():
ret['changes'][k] = v
ret['comment'] = 'Granted role assignment'
return ret
def absent(name, auth=None, **kwargs):
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
if 'role' not in kwargs:
kwargs['role'] = name
filters, kwargs = _get_filters(kwargs)
grants = __salt__['keystoneng.role_assignment_list'](filters=filters)
if grants:
__salt__['keystoneng.role_revoke'](**kwargs)
for k, v in filters.items():
ret['changes'][k] = v
ret['comment'] = 'Revoked role assignment'
return ret

View File

@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Services
=========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create service:
keystone_service.present:
- name: glance
- type: image
delete service:
keystone_service.absent:
- name: glance
create service with optional params:
keystone_service.present:
- name: glance
- type: image
- enabled: False
- description: 'OpenStack Image'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_service'
def __virtual__():
if 'keystoneng.service_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure an service exists and is up-to-date
name
Name of the group
type
Service type
enabled
Boolean to control if service is enabled
description
An arbitrary description of the service
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
service = __salt__['keystoneng.service_get'](name=name)
if service is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be created.'
return ret
kwargs['name'] = name
service = __salt__['keystoneng.service_create'](**kwargs)
ret['changes'] = service
ret['comment'] = 'Created service'
return ret
changes = __salt__['keystoneng.compare_changes'](service, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be updated.'
return ret
kwargs['name'] = service
__salt__['keystoneng.service_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated service'
return ret
def absent(name, auth=None):
'''
Ensure service does not exist
name
Name of the service
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
service = __salt__['keystoneng.service_get'](name=name)
if service:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': service.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be deleted.'
return ret
__salt__['keystoneng.service_delete'](name=service)
ret['changes']['id'] = service.id
ret['comment'] = 'Deleted service'
return ret

View File

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Users
======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create user:
keystone_user.present:
- name: user1
delete user:
keystone_user.absent:
- name: user1
create user with optional params:
keystone_user.present:
- name: user1
- domain: domain1
- enabled: False
- password: password123
- email: "user1@example.org"
- description: 'my user'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_user'
def __virtual__():
if 'keystoneng.user_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(kwargs):
'''
Returns: None if user wasn't found, otherwise a user object
'''
search_kwargs = {'name': kwargs['name']}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['domain_id'] = domain_id
kwargs['domain_id'] = domain_id
return __salt__['keystoneng.user_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure domain exists and is up-to-date
name
Name of the domain
domain
The name or id of the domain
enabled
Boolean to control if domain is enabled
description
An arbitrary description of the domain
password
The user password
email
The users email address
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
user = _common(kwargs)
if user is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be created.'
return ret
user = __salt__['keystoneng.user_create'](**kwargs)
ret['changes'] = user
ret['comment'] = 'Created user'
return ret
changes = __salt__['keystoneng.compare_changes'](user, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be updated.'
return ret
kwargs['name'] = user
__salt__['keystoneng.user_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated user'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure user does not exists
name
Name of the user
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
user = _common(kwargs)
if user:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': user.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be deleted.'
return ret
__salt__['keystoneng.user_delete'](name=user)
ret['changes']['id'] = user.id
ret['comment'] = 'Deleted user'
return ret

View File

@ -508,8 +508,10 @@ def _find_install_targets(name=None,
# add it to the kwargs.
kwargs['refresh'] = refresh
resolve_capabilities = kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__
try:
cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
cur_prov = resolve_capabilities and __salt__['pkg.list_provides'](**kwargs) or dict()
except CommandExecutionError as exc:
return {'name': name,
'changes': {},
@ -669,6 +671,9 @@ def _find_install_targets(name=None,
failed_verify = False
for key, val in six.iteritems(desired):
cver = cur_pkgs.get(key, [])
if resolve_capabilities and not cver and key in cur_prov:
cver = cur_pkgs.get(cur_prov.get(key)[0], [])
# Package not yet installed, so add to targets
if not cver:
targets[key] = val
@ -786,13 +791,15 @@ def _find_install_targets(name=None,
warnings, was_refreshed)
def _verify_install(desired, new_pkgs, ignore_epoch=False):
def _verify_install(desired, new_pkgs, ignore_epoch=False, new_caps=None):
'''
Determine whether or not the installed packages match what was requested in
the SLS file.
'''
ok = []
failed = []
if not new_caps:
new_caps = dict()
for pkgname, pkgver in desired.items():
# FreeBSD pkg supports `openjdk` and `java/openjdk7` package names.
# Homebrew for Mac OSX does something similar with tap names
@ -809,6 +816,8 @@ def _verify_install(desired, new_pkgs, ignore_epoch=False):
cver = new_pkgs.get(pkgname.split('=')[0])
else:
cver = new_pkgs.get(pkgname)
if not cver and pkgname in new_caps:
cver = new_pkgs.get(new_caps.get(pkgname)[0])
if not cver:
failed.append(pkgname)
@ -873,6 +882,26 @@ def _nested_output(obj):
return ret
def _resolve_capabilities(pkgs, refresh=False, **kwargs):
'''
Resolve capabilities in ``pkgs`` and exchange them with real package
names, when the result is distinct.
This feature can be turned on while setting the paramter
``resolve_capabilities`` to True.
Return the input dictionary with replaced capability names and as
second return value a bool which say if a refresh need to be run.
In case of ``resolve_capabilities`` is False (disabled) or not
supported by the implementation the input is returned unchanged.
'''
if not pkgs or 'pkg.resolve_capabilities' not in __salt__:
return pkgs, refresh
ret = __salt__['pkg.resolve_capabilities'](pkgs, refresh=refresh, **kwargs)
return ret, False
def installed(
name,
version=None,
@ -1105,6 +1134,11 @@ def installed(
.. versionadded:: 2014.1.1
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
:param bool allow_updates:
Allow the package to be updated outside Salt's control (e.g. auto
updates on Windows). This means a package on the Minion can have a
@ -1448,6 +1482,12 @@ def installed(
kwargs['saltenv'] = __env__
refresh = salt.utils.pkg.check_refresh(__opts__, refresh)
# check if capabilities should be checked and modify the requested packages
# accordingly.
if pkgs:
pkgs, refresh = _resolve_capabilities(pkgs, refresh=refresh, **kwargs)
if not isinstance(pkg_verify, list):
pkg_verify = pkg_verify is True
if (pkg_verify or isinstance(pkg_verify, list)) \
@ -1707,8 +1747,13 @@ def installed(
if __grains__['os'] == 'FreeBSD':
kwargs['with_origin'] = True
new_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
if kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__:
new_caps = __salt__['pkg.list_provides'](**kwargs)
else:
new_caps = {}
ok, failed = _verify_install(desired, new_pkgs,
ignore_epoch=ignore_epoch)
ignore_epoch=ignore_epoch,
new_caps=new_caps)
modified = [x for x in ok if x in targets]
not_modified = [x for x in ok
if x not in targets
@ -1927,6 +1972,11 @@ def downloaded(name,
- dos2unix
- salt-minion: 2015.8.5-1.el6
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
CLI Example:
.. code-block:: yaml
@ -1952,11 +2002,22 @@ def downloaded(name,
ret['comment'] = 'No packages to download provided'
return ret
# If just a name (and optionally a version) is passed, just pack them into
# the pkgs argument.
if name and not pkgs:
if version:
pkgs = [{name: version}]
version = None
else:
pkgs = [name]
# It doesn't make sense here to received 'downloadonly' as kwargs
# as we're explicitely passing 'downloadonly=True' to execution module.
if 'downloadonly' in kwargs:
del kwargs['downloadonly']
pkgs, _refresh = _resolve_capabilities(pkgs, **kwargs)
# Only downloading not yet downloaded packages
targets = _find_download_targets(name,
version,
@ -2203,6 +2264,10 @@ def latest(
This parameter is available only on Debian based distributions and
has no effect on the rest.
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
Multiple Package Installation Options:
@ -2300,6 +2365,10 @@ def latest(
kwargs['saltenv'] = __env__
# check if capabilities should be checked and modify the requested packages
# accordingly.
desired_pkgs, refresh = _resolve_capabilities(desired_pkgs, refresh=refresh, **kwargs)
try:
avail = __salt__['pkg.latest_version'](*desired_pkgs,
fromrepo=fromrepo,
@ -2822,6 +2891,11 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs):
This parameter available only on Debian based distributions, and
have no effect on the rest.
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
kwargs
Any keyword arguments to pass through to ``pkg.upgrade``.
@ -2842,6 +2916,7 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs):
return ret
if isinstance(refresh, bool):
pkgs, refresh = _resolve_capabilities(pkgs, refresh=refresh, **kwargs)
try:
packages = __salt__['pkg.list_upgrades'](refresh=refresh, **kwargs)
if isinstance(pkgs, list):

View File

@ -351,7 +351,6 @@ def state(name,
changes = {}
fail = set()
failures = {}
no_change = set()
if fail_minions is None:
@ -393,7 +392,7 @@ def state(name,
if not m_state:
if minion not in fail_minions:
fail.add(minion)
failures[minion] = m_ret or 'Minion did not respond'
changes[minion] = m_ret
continue
try:
for state_item in six.itervalues(m_ret):
@ -418,18 +417,6 @@ def state(name,
state_ret['comment'] += ' Updating {0}.'.format(', '.join(changes))
if no_change:
state_ret['comment'] += ' No changes made to {0}.'.format(', '.join(no_change))
if failures:
state_ret['comment'] += '\nFailures:\n'
for minion, failure in six.iteritems(failures):
state_ret['comment'] += '\n'.join(
(' ' * 4 + l)
for l in salt.output.out_format(
{minion: failure},
'highstate',
__opts__,
).splitlines()
)
state_ret['comment'] += '\n'
if test or __opts__.get('test'):
if state_ret['changes'] and state_ret['result'] is True:
# Test mode with changes is the only case where result should ever be none
@ -570,7 +557,6 @@ def function(
changes = {}
fail = set()
failures = {}
if fail_minions is None:
fail_minions = ()
@ -598,7 +584,7 @@ def function(
if not m_func:
if minion not in fail_minions:
fail.add(minion)
failures[minion] = m_ret and m_ret or 'Minion did not respond'
changes[minion] = m_ret
continue
changes[minion] = m_ret
if not cmd_ret:
@ -614,18 +600,6 @@ def function(
func_ret['comment'] = 'Function ran successfully.'
if changes:
func_ret['comment'] += ' Function {0} ran on {1}.'.format(name, ', '.join(changes))
if failures:
func_ret['comment'] += '\nFailures:\n'
for minion, failure in six.iteritems(failures):
func_ret['comment'] += '\n'.join(
(' ' * 4 + l)
for l in salt.output.out_format(
{minion: failure},
'highstate',
__opts__,
).splitlines()
)
func_ret['comment'] += '\n'
return func_ret
@ -787,28 +761,15 @@ def runner(name, **kwargs):
runner_return = out.get('return')
if isinstance(runner_return, dict) and 'Error' in runner_return:
out['success'] = False
if not out.get('success', True):
cmt = "Runner function '{0}' failed{1}.".format(
name,
' with return {0}'.format(runner_return) if runner_return else '',
)
ret = {
'name': name,
'result': False,
'changes': {},
'comment': cmt,
}
else:
cmt = "Runner function '{0}' executed{1}.".format(
name,
' with return {0}'.format(runner_return) if runner_return else '',
)
ret = {
'name': name,
'result': True,
'changes': {},
'comment': cmt,
}
success = out.get('success', True)
ret = {'name': name,
'changes': {'return': runner_return},
'result': success}
ret['comment'] = "Runner function '{0}' {1}.".format(
name,
'executed' if success else 'failed',
)
ret['__orchestration__'] = True
if 'jid' in out:
@ -1039,15 +1000,21 @@ def wheel(name, **kwargs):
__env__=__env__,
**kwargs)
ret['result'] = True
wheel_return = out.get('return')
if isinstance(wheel_return, dict) and 'Error' in wheel_return:
out['success'] = False
success = out.get('success', True)
ret = {'name': name,
'changes': {'return': wheel_return},
'result': success}
ret['comment'] = "Wheel function '{0}' {1}.".format(
name,
'executed' if success else 'failed',
)
ret['__orchestration__'] = True
if 'jid' in out:
ret['__jid__'] = out['jid']
runner_return = out.get('return')
ret['comment'] = "Wheel function '{0}' executed{1}.".format(
name,
' with return {0}'.format(runner_return) if runner_return else '',
)
return ret

View File

@ -451,10 +451,10 @@ def format_call(fun,
continue
extra[key] = copy.deepcopy(value)
# We'll be showing errors to the users until Salt Oxygen comes out, after
# We'll be showing errors to the users until Salt Fluorine comes out, after
# which, errors will be raised instead.
salt.utils.versions.warn_until(
'Oxygen',
'Fluorine',
'It\'s time to start raising `SaltInvocationError` instead of '
'returning warnings',
# Let's not show the deprecation warning on the console, there's no
@ -491,7 +491,7 @@ def format_call(fun,
'{0}. If you were trying to pass additional data to be used '
'in a template context, please populate \'context\' with '
'\'key: value\' pairs. Your approach will work until Salt '
'Oxygen is out.{1}'.format(
'Fluorine is out.{1}'.format(
msg,
'' if 'full' not in ret else ' Please update your state files.'
)

View File

@ -910,7 +910,7 @@ class GitProvider(object):
'''
if self.branch == '__env__':
target = self.opts.get('pillarenv') \
or self.opts.get('environment') \
or self.opts.get('saltenv') \
or 'base'
return self.opts['{0}_base'.format(self.role)] \
if target == 'base' \

View File

@ -27,13 +27,14 @@ from jinja2.exceptions import TemplateRuntimeError
from jinja2.ext import Extension
# Import salt libs
from salt.exceptions import TemplateError
import salt.fileclient
import salt.utils.data
import salt.utils.files
import salt.utils.url
import salt.utils.yamldumper
from salt.utils.decorators.jinja import jinja_filter, jinja_test, jinja_global
from salt.utils.odict import OrderedDict
from salt.exceptions import TemplateError
log = logging.getLogger(__name__)
@ -44,18 +45,6 @@ __all__ = [
GLOBAL_UUID = uuid.UUID('91633EBF-1C86-5E33-935A-28061F4B480E')
# To dump OrderedDict objects as regular dicts. Used by the yaml
# template filter.
class OrderedDictDumper(yaml.Dumper): # pylint: disable=W0232
pass
yaml.add_representer(OrderedDict,
yaml.representer.SafeRepresenter.represent_dict,
Dumper=OrderedDictDumper)
class SaltCacheLoader(BaseLoader):
'''
@ -796,8 +785,8 @@ class SerializerExtension(Extension, object):
return Markup(json.dumps(value, sort_keys=sort_keys, indent=indent).strip())
def format_yaml(self, value, flow_style=True):
yaml_txt = yaml.dump(value, default_flow_style=flow_style,
Dumper=OrderedDictDumper).strip()
yaml_txt = salt.utils.yamldumper.safe_dump(
value, default_flow_style=flow_style).strip()
if yaml_txt.endswith('\n...'):
yaml_txt = yaml_txt[:len(yaml_txt)-4]
return Markup(yaml_txt)

View File

@ -14,6 +14,7 @@ Utils for the NAPALM modules and proxy.
.. versionadded:: 2017.7.0
'''
# Import Python libs
from __future__ import absolute_import
import traceback
@ -22,20 +23,31 @@ import importlib
from functools import wraps
# Import Salt libs
from salt.ext import six as six
import salt.output
import salt.utils.platform
# Import 3rd-party libs
from salt.ext import six
# Import third party libs
try:
# will try to import NAPALM
# https://github.com/napalm-automation/napalm
# pylint: disable=W0611
import napalm_base
import napalm
import napalm.base as napalm_base
# pylint: enable=W0611
HAS_NAPALM = True
HAS_NAPALM_BASE = False # doesn't matter anymore, but needed for the logic below
try:
NAPALM_MAJOR = int(napalm.__version__.split('.')[0])
except AttributeError:
NAPALM_MAJOR = 0
except ImportError:
HAS_NAPALM = False
try:
import napalm_base
HAS_NAPALM_BASE = True
except ImportError:
HAS_NAPALM_BASE = False
try:
# try importing ConnectionClosedException
@ -81,7 +93,7 @@ def virtual(opts, virtualname, filename):
'''
Returns the __virtual__.
'''
if HAS_NAPALM and (is_proxy(opts) or is_minion(opts)):
if ((HAS_NAPALM and NAPALM_MAJOR >= 2) or HAS_NAPALM_BASE) and (is_proxy(opts) or is_minion(opts)):
return virtualname
else:
return (

View File

@ -344,3 +344,60 @@ def sanitize_win_path(winpath):
elif isinstance(winpath, six.text_type):
winpath = winpath.translate(dict((ord(c), u'_') for c in intab))
return winpath
def safe_path(path, allow_path=None):
r'''
.. versionadded:: 2017.7.3
Checks that the path is safe for modification by Salt. For example, you
wouldn't want to have salt delete the contents of ``C:\Windows``. The
following directories are considered unsafe:
- C:\, D:\, E:\, etc.
- \
- C:\Windows
Args:
path (str): The path to check
allow_paths (str, list): A directory or list of directories inside of
path that may be safe. For example: ``C:\Windows\TEMP``
Returns:
bool: True if safe, otherwise False
'''
# Create regex definitions for directories that may be unsafe to modify
system_root = os.environ.get('SystemRoot', 'C:\\Windows')
deny_paths = (
r'[a-z]\:\\$', # C:\, D:\, etc
r'\\$', # \
re.escape(system_root) # C:\Windows
)
# Make allow_path a list
if allow_path and not isinstance(allow_path, list):
allow_path = [allow_path]
# Create regex definition for directories we may want to make exceptions for
allow_paths = list()
if allow_path:
for item in allow_path:
allow_paths.append(re.escape(item))
# Check the path to make sure it's not one of the bad paths
good_path = True
for d_path in deny_paths:
if re.match(d_path, path, flags=re.IGNORECASE) is not None:
# Found deny path
good_path = False
# If local_dest is one of the bad paths, check for exceptions
if not good_path:
for a_path in allow_paths:
if re.match(a_path, path, flags=re.IGNORECASE) is not None:
# Found exception
good_path = True
return good_path

View File

@ -104,11 +104,12 @@ def dict_search_and_replace(d, old, new, expanded):
def find_value_to_expand(x, v):
a = x
for i in v[2:-1].split(':'):
if a is None:
return v
if i in a:
a = a.get(i)
else:
a = v
return a
return v
return a

View File

@ -89,6 +89,28 @@ localtime.
This will schedule the command: ``state.sls httpd test=True`` at 5:00 PM on
Monday, Wednesday and Friday, and 3:00 PM on Tuesday and Thursday.
.. code-block:: yaml
schedule:
job1:
function: state.sls
args:
- httpd
kwargs:
test: True
when:
- 'tea time'
.. code-block:: yaml
whens:
tea time: 1:40pm
deployment time: Friday 5:00pm
The Salt scheduler also allows custom phrases to be used for the `when`
parameter. These `whens` can be stored as either pillar values or
grain values.
.. code-block:: yaml
schedule:
@ -333,7 +355,6 @@ import logging
import errno
import random
import yaml
import copy
# Import Salt libs
import salt.config
@ -409,6 +430,7 @@ class Schedule(object):
self.proxy = proxy
self.functions = functions
self.standalone = standalone
self.skip_function = None
if isinstance(intervals, dict):
self.intervals = intervals
else:
@ -745,6 +767,69 @@ class Schedule(object):
evt.fire_event({'complete': True},
tag='/salt/minion/minion_schedule_saved')
def postpone_job(self, name, data):
'''
Postpone a job in the scheduler.
Ignores jobs from pillar
'''
time = data['time']
new_time = data['new_time']
# ensure job exists, then disable it
if name in self.opts['schedule']:
if 'skip_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['skip_explicit'] = []
self.opts['schedule'][name]['skip_explicit'].append(time)
if 'run_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['run_explicit'] = []
self.opts['schedule'][name]['run_explicit'].append(new_time)
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_postpone_job_complete')
def skip_job(self, name, data):
'''
Skip a job at a specific time in the scheduler.
Ignores jobs from pillar
'''
time = data['time']
# ensure job exists, then disable it
if name in self.opts['schedule']:
if 'skip_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['skip_explicit'] = []
self.opts['schedule'][name]['skip_explicit'].append(time)
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_skip_job_complete')
def get_next_fire_time(self, name):
'''
Disable a job in the scheduler. Ignores jobs from pillar
'''
schedule = self._get_schedule()
if schedule:
_next_fire_time = schedule[name]['_next_fire_time']
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'next_fire_time': _next_fire_time},
tag='/salt/minion/minion_schedule_next_fire_time_complete')
def handle_func(self, multiprocessing_enabled, func, data):
'''
Execute this method in a multiprocess or thread
@ -948,11 +1033,16 @@ class Schedule(object):
# Let's make sure we exit the process!
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
def eval(self):
def eval(self, now=None):
'''
Evaluate and execute the schedule
:param int now: Override current time with a Unix timestamp``
'''
log.trace('==== evaluating schedule =====')
def _splay(splaytime):
'''
Calculate splaytime
@ -974,9 +1064,13 @@ class Schedule(object):
raise ValueError('Schedule must be of type dict.')
if 'enabled' in schedule and not schedule['enabled']:
return
if 'skip_function' in schedule:
self.skip_function = schedule['skip_function']
for job, data in six.iteritems(schedule):
if job == 'enabled' or not data:
continue
if job == 'skip_function' or not data:
continue
if not isinstance(data, dict):
log.error('Scheduled job "{0}" should have a dict value, not {1}'.format(job, type(data)))
continue
@ -1011,7 +1105,8 @@ class Schedule(object):
'_run_on_start' not in data:
data['_run_on_start'] = True
now = int(time.time())
if not now:
now = int(time.time())
if 'until' in data:
if not _WHEN_SUPPORTED:
@ -1065,6 +1160,23 @@ class Schedule(object):
'", "'.join(scheduling_elements)))
continue
if 'run_explicit' in data:
_run_explicit = data['run_explicit']
if isinstance(_run_explicit, six.string_types):
_run_explicit = [_run_explicit]
# Copy the list so we can loop through it
for i in copy.deepcopy(_run_explicit):
if len(_run_explicit) > 1:
if i < now - self.opts['loop_interval']:
_run_explicit.remove(i)
if _run_explicit:
if _run_explicit[0] <= now < (_run_explicit[0] + self.opts['loop_interval']):
run = True
data['_next_fire_time'] = _run_explicit[0]
if True in [True for item in time_elements if item in data]:
if '_seconds' not in data:
interval = int(data.get('seconds', 0))
@ -1153,10 +1265,11 @@ class Schedule(object):
# Copy the list so we can loop through it
for i in copy.deepcopy(_when):
if i < now and len(_when) > 1:
# Remove all missed schedules except the latest one.
# We need it to detect if it was triggered previously.
_when.remove(i)
if len(_when) > 1:
if i < now - self.opts['loop_interval']:
# Remove all missed schedules except the latest one.
# We need it to detect if it was triggered previously.
_when.remove(i)
if _when:
# Grab the first element, which is the next run time or
@ -1258,19 +1371,21 @@ class Schedule(object):
seconds = data['_next_fire_time'] - now
if data['_splay']:
seconds = data['_splay'] - now
if seconds <= 0:
if '_seconds' in data:
if '_seconds' in data:
if seconds <= 0:
run = True
elif 'when' in data and data['_run']:
elif 'when' in data and data['_run']:
if data['_next_fire_time'] <= now <= (data['_next_fire_time'] + self.opts['loop_interval']):
data['_run'] = False
run = True
elif 'cron' in data:
# Reset next scheduled time because it is in the past now,
# and we should trigger the job run, then wait for the next one.
elif 'cron' in data:
# Reset next scheduled time because it is in the past now,
# and we should trigger the job run, then wait for the next one.
if seconds <= 0:
data['_next_fire_time'] = None
run = True
elif seconds == 0:
run = True
elif seconds == 0:
run = True
if '_run_on_start' in data and data['_run_on_start']:
run = True
@ -1312,7 +1427,11 @@ class Schedule(object):
if start <= now <= end:
run = True
else:
run = False
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
log.error('schedule.handle_func: Invalid range, end must be larger than start. \
Ignoring job {0}.'.format(job))
@ -1322,6 +1441,62 @@ class Schedule(object):
Ignoring job {0}.'.format(job))
continue
if 'skip_during_range' in data:
if not _RANGE_SUPPORTED:
log.error('Missing python-dateutil. Ignoring job {0}'.format(job))
continue
else:
if isinstance(data['skip_during_range'], dict):
try:
start = int(time.mktime(dateutil_parser.parse(data['skip_during_range']['start']).timetuple()))
except ValueError:
log.error('Invalid date string for start in skip_during_range. Ignoring job {0}.'.format(job))
continue
try:
end = int(time.mktime(dateutil_parser.parse(data['skip_during_range']['end']).timetuple()))
except ValueError:
log.error('Invalid date string for end in skip_during_range. Ignoring job {0}.'.format(job))
log.error(data)
continue
if end > start:
if start <= now <= end:
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
run = True
else:
log.error('schedule.handle_func: Invalid range, end must be larger than start. \
Ignoring job {0}.'.format(job))
continue
else:
log.error('schedule.handle_func: Invalid, range must be specified as a dictionary. \
Ignoring job {0}.'.format(job))
continue
if 'skip_explicit' in data:
_skip_explicit = data['skip_explicit']
if isinstance(_skip_explicit, six.string_types):
_skip_explicit = [_skip_explicit]
# Copy the list so we can loop through it
for i in copy.deepcopy(_skip_explicit):
if i < now - self.opts['loop_interval']:
_skip_explicit.remove(i)
if _skip_explicit:
if _skip_explicit[0] <= now <= (_skip_explicit[0] + self.opts['loop_interval']):
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
run = True
if not run:
continue
@ -1374,6 +1549,7 @@ class Schedule(object):
finally:
if '_seconds' in data:
data['_next_fire_time'] = now + data['_seconds']
data['_last_run'] = now
data['_splay'] = None
if salt.utils.platform.is_windows():
# Restore our function references.

View File

@ -30,9 +30,12 @@ import salt.defaults.exitcodes
import salt.utils.files
import salt.utils.platform
import salt.utils.user
import salt.utils.versions
log = logging.getLogger(__name__)
ROOT_DIR = 'c:\\salt' if salt.utils.platform.is_windows() else '/'
def zmq_version():
'''
@ -194,13 +197,34 @@ def verify_files(files, user):
return True
def verify_env(dirs, user, permissive=False, sensitive_dirs=None, skip_extra=False):
def verify_env(
dirs,
user,
permissive=False,
pki_dir='',
skip_extra=False,
root_dir=ROOT_DIR,
sensitive_dirs=None):
'''
Verify that the named directories are in place and that the environment
can shake the salt
'''
if pki_dir:
salt.utils.versions.warn_until(
'Neon',
'Use of \'pki_dir\' was detected: \'pki_dir\' has been deprecated '
'in favor of \'sensitive_dirs\'. Support for \'pki_dir\' will be '
'removed in Salt Neon.'
)
sensitive_dirs = sensitive_dirs or []
sensitive_dirs.append(list(pki_dir))
if salt.utils.platform.is_windows():
return win_verify_env(dirs, permissive, sensitive_dirs, skip_extra)
return win_verify_env(root_dir,
dirs,
permissive=permissive,
skip_extra=skip_extra,
sensitive_dirs=sensitive_dirs)
import pwd # after confirming not running Windows
try:
pwnam = pwd.getpwnam(user)
@ -526,18 +550,37 @@ def verify_log(opts):
log.warning('Insecure logging configuration detected! Sensitive data may be logged.')
def win_verify_env(dirs, permissive=False, sensitive_dirs=None, skip_extra=False):
def win_verify_env(
path,
dirs,
permissive=False,
pki_dir='',
skip_extra=False,
sensitive_dirs=None):
'''
Verify that the named directories are in place and that the environment
can shake the salt
'''
if pki_dir:
salt.utils.versions.warn_until(
'Neon',
'Use of \'pki_dir\' was detected: \'pki_dir\' has been deprecated '
'in favor of \'sensitive_dirs\'. Support for \'pki_dir\' will be '
'removed in Salt Neon.'
)
sensitive_dirs = sensitive_dirs or []
sensitive_dirs.append(list(pki_dir))
import salt.utils.win_functions
import salt.utils.win_dacl
import salt.utils.path
# Get the root path directory where salt is installed
path = dirs[0]
while os.path.basename(path) not in ['salt', 'salt-tests-tmpdir']:
path, base = os.path.split(path)
# Make sure the file_roots is not set to something unsafe since permissions
# on that directory are reset
if not salt.utils.path.safe_path(path=path):
raise CommandExecutionError(
'`file_roots` set to a possibly unsafe location: {0}'.format(path)
)
# Create the root path directory if missing
if not os.path.isdir(path):

View File

@ -1133,9 +1133,14 @@ def get_name(principal):
try:
return win32security.LookupAccountSid(None, sid_obj)[0]
except TypeError:
raise CommandExecutionError(
'Could not find User for {0}'.format(principal))
except (pywintypes.error, TypeError) as exc:
if type(exc) == pywintypes.error:
win_error = win32api.FormatMessage(exc.winerror).rstrip('\n')
message = 'Error resolving {0} ({1})'.format(principal, win_error)
else:
message = 'Error resolving {0}'.format(principal)
raise CommandExecutionError(message)
def get_owner(obj_name):
@ -1173,7 +1178,7 @@ def get_owner(obj_name):
owner_sid = 'S-1-1-0'
else:
raise CommandExecutionError(
'Failed to set permissions: {0}'.format(exc.strerror))
'Failed to get owner: {0}'.format(exc.strerror))
return get_name(win32security.ConvertSidToStringSid(owner_sid))

View File

@ -43,7 +43,8 @@ class WheelClient(salt.client.mixins.SyncClientMixin,
def __init__(self, opts=None):
self.opts = opts
self.functions = salt.loader.wheels(opts)
self.context = {}
self.functions = salt.loader.wheels(opts, context=self.context)
# TODO: remove/deprecate
def call_func(self, fun, **kwargs):

View File

@ -986,7 +986,9 @@ class TestDaemon(object):
RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
TMP,
],
RUNTIME_VARS.RUNNING_TESTS_USER)
RUNTIME_VARS.RUNNING_TESTS_USER,
root_dir=master_opts['root_dir'],
)
cls.master_opts = master_opts
cls.minion_opts = minion_opts

View File

@ -125,3 +125,26 @@ def modules_available(*names):
if not fnmatch.filter(list(__salt__), name):
not_found.append(name)
return not_found
def nonzero_retcode_return_true():
'''
Sets a nonzero retcode before returning. Designed to test orchestration.
'''
__context__['retcode'] = 1
return True
def nonzero_retcode_return_false():
'''
Sets a nonzero retcode before returning. Designed to test orchestration.
'''
__context__['retcode'] = 1
return False
def fail_function(*args, **kwargs): # pylint: disable=unused-argument
'''
Return False no matter what is passed to it
'''
return False

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
'''
Runner functions for integration tests
'''
# Import python libs
from __future__ import absolute_import
def failure():
__context__['retcode'] = 1
return False
def success():
return True

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
'''
Wheel functions for integration tests
'''
# Import python libs
from __future__ import absolute_import
def failure():
__context__['retcode'] = 1
return False
def success():
return True

View File

@ -0,0 +1,2 @@
test fail with changes:
test.fail_with_changes

View File

@ -0,0 +1,11 @@
Step01:
salt.state:
- tgt: 'minion'
- sls:
- orch.issue43204.fail_with_changes
Step02:
salt.function:
- name: runtests_helpers.nonzero_retcode_return_false
- tgt: 'minion'
- fail_function: runtests_helpers.fail_function

View File

@ -0,0 +1,15 @@
test_runner_success:
salt.runner:
- name: runtests_helpers.success
test_runner_failure:
salt.runner:
- name: runtests_helpers.failure
test_wheel_success:
salt.wheel:
- name: runtests_helpers.success
test_wheel_failure:
salt.wheel:
- name: runtests_helpers.failure

View File

@ -0,0 +1,4 @@
test_file:
file.managed:
- name: /tmp/nonbase_env
- source: salt://nonbase_env

View File

@ -0,0 +1 @@
it worked - new environment!

Some files were not shown because too many files have changed in this diff Show More