Merge pull request #27198 from basepi/merge-forward-develop

Merge forward from 2015.8 to develop
This commit is contained in:
Colton Myers 2015-09-17 15:33:15 -06:00
commit 9ddc88c6ba
68 changed files with 1297 additions and 661 deletions

View File

@ -570,7 +570,7 @@
# master config file that can then be used on minions.
#pillar_opts: False
# The pillar_safe_render_error option prevents the master from passing piller
# The pillar_safe_render_error option prevents the master from passing pillar
# render errors to the minion. This is set on by default because the error could
# contain templating data which would give that minion information it shouldn't
# have, like a password! When set true the error message will only show:

View File

@ -21,7 +21,7 @@
{% set script_files = [
'_static/js/core.min.js',
'_static/js/webhelp.min_v1.4.1.js',
'_static/js/webhelp.min_v1.4.2.js',
] %}
{% set css_files = [
@ -160,6 +160,12 @@
</div>
{% endif %}
{% if build_type == "inactive" and on_saltstack %}
<div id="dev-notification">
<div class="alert alert-warning dev-notification-text releaselinks" role="alert"><i class="glyphicon glyphicon-cog"></i> You are viewing docs from a branch that is no longer active. You might want to view docs for the <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the latest stable release" href="/en/latest/">{{ latest_release }}</a> release instead.</div>
</div>
{% endif %}
{%- block document %}
<div class="body-content">
{% block body %} {% endblock %}
@ -192,8 +198,11 @@
{% elif build_type == "previous" %}
<p>You are viewing docs for the previous stable release, {{ previous_release }}. Switch to docs for the latest stable release, <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the latest stable release" href="/en/latest/">{{ latest_release }}</a>, or to a recent doc build from the <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Latest docs from the develop branch" href="/en/develop/">develop</a> branch.</p>
{% elif build_type == "inactive" %}
<p>You are viewing docs for an inactive release, {{ previous_release }}. Switch to docs for the latest stable release, <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the latest stable release" href="/en/latest/">{{ latest_release }}</a>, or to a recent doc build from the <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Latest docs from the develop branch" href="/en/develop/">develop</a> branch.</p>
{% elif build_type == "develop" %}
<p>You are viewing docs built from a recent snapshot of the develop branch. Switch to docs for the latest stable release, <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the latest stable release" href="/en/latest/">{{ latest_release }}</a>, or to docs for the previous stable release, <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the previous stable release" href="/en/{{ previous_release_dir }}/">{{ previous_release }}</a>.</p>
<p>You are viewing docs built from a recent snapshot of the develop branch. Switch to docs for the latest stable release, <a data-container="body" data-toggle="tooltip" data-placement="bottom" title="Docs for the latest stable release" href="/en/latest/">{{ latest_release }}</a>.</p>
{% endif %}
<br>
@ -203,12 +212,9 @@
<div class="col-sm-6">
<a href="http://saltstack.com/saltstack-enterprise-4-0-now-with-gui/" target="_blank"><img class="nolightbox nav-banner" src="{{ pathto('_static/images/saltStack_enterprise_350x125.jpg', 1) }}"/></a>
<a href="http://saltstack.com/events/" target="_blank"><img class="nolightbox nav-banner center" src="{{ pathto('_static/images/saltStack_events_300x300.jpg', 1) }}"/></a>
<br/><br/>
<p><b>SaltStack Training</b></p>
<p><a href="http://www.saltstack.com/training/">Now offering remote attendee training!</a></p>
</div>
</div>
{% endif %}
@ -224,9 +230,9 @@
<a class="ss-logo" href="http://saltstack.com"><img width="250" height="63" class="nolightbox center" src="{{ pathto('_static/images/saltstack_logo.svg', 1) }}"></a>
{% if on_saltstack %}
<div class="versions {{ build_type }}">
<div class="releaselinks versions {{ build_type }}">
<a class="btn btn-secondary{% if build_type == "previous" %} active{% endif %}" id="previous"{% if build_type == "previous" %} title="View release notes"{% else %} title="Switch to docs for the previous stable release"{% endif %} data-container="body" data-toggle="tooltip" data-placement="bottom" href="/en/{{ previous_release_dir }}/">{{ previous_release }}{% if build_type == "previous" %} <i class="glyphicon glyphicon-ok"></i>{%- endif %}</a>
<a class="btn btn-secondary{% if build_type == "previous" or build_type == "inactive" %} active{% endif %}" id="previous"{% if build_type == "previous" or build_type == "inactive" %} title="View release notes"{% else %} title="Switch to docs for the previous stable release"{% endif %} data-container="body" data-toggle="tooltip" data-placement="bottom" href="/en/{{ previous_release_dir }}/">{{ previous_release }}{% if build_type == "previous" or build_type == "inactive" %} <i class="glyphicon glyphicon-ok"></i>{%- endif %}</a>
<a class="btn btn-secondary{% if build_type == "latest" %} active{% endif %}" id="latest"{% if build_type == "latest" %} title="View release notes"{% else %} title="Switch to docs for the latest stable release"{% endif %} data-container="body" data-toggle="tooltip" data-placement="bottom" href="/en/latest/">{{ latest_release }}{% if build_type == "latest" %} <i class="glyphicon glyphicon-ok"></i>{% endif %}</a>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -115,7 +115,7 @@ $( document ).ready(function() {
});
/*version page selector*/
$( 'div.versions' ).on('click', 'a', function (e) {
$( 'div.releaselinks' ).on('click', 'a', function (e) {
e.preventDefault();
var clickedVer = $(this).attr("href");
var $currentVer = $( 'div.versions' ).find( 'a.active' ).first();
@ -188,4 +188,4 @@ function resizeend() {
function last(list) {
return list[list.length - 1];
}
}

View File

@ -50,6 +50,7 @@ MOCK_MODULES = [
'Crypto.PublicKey',
'Crypto.Random',
'Crypto.Signature',
'Crypto.Signature.PKCS1_v1_5',
'M2Crypto',
'msgpack',
'yaml',
@ -87,6 +88,7 @@ MOCK_MODULES = [
'tornado.httpserver',
'tornado.httputil',
'tornado.ioloop',
'tornado.simple_httpclient',
'tornado.web',
'tornado.websocket',
@ -104,7 +106,6 @@ MOCK_MODULES = [
'MySQLdb.cursors',
'nagios_json',
'psutil',
'psutil.version_info',
'pycassa',
'pymongo',
'rabbitmq_server',
@ -162,9 +163,9 @@ project = 'Salt'
copyright = '2015 SaltStack, Inc.'
version = salt.version.__version__
latest_release = '2015.5.5' # latest release
previous_release = '2014.7.6' # latest release from previous branch
previous_release_dir = '2014.7' # path on web server for previous branch
latest_release = '2015.8.0' # latest release
previous_release = '2015.5.5' # latest release from previous branch
previous_release_dir = '2015.5' # path on web server for previous branch
build_type = 'develop' # latest, previous, develop
# set release to 'version' for develop so sha is used
@ -176,11 +177,11 @@ release = latest_release # version, latest_release, previous_release
# Set google custom search engine
if release == latest_release:
search_cx = '004624818632696854117:yfmprrbw3pk'
search_cx = '004624818632696854117:yfmprrbw3pk' # latest
elif release.startswith('2014.7'):
search_cx = '004624818632696854117:thhslradbru'
search_cx = '004624818632696854117:thhslradbru' # 2014.7
elif release.startswith('2015.5'):
search_cx = '004624818632696854117:ovogwef29do'
search_cx = '004624818632696854117:ovogwef29do' # 2015.5
else:
search_cx = '004624818632696854117:haj7bjntf4s' # develop
@ -228,11 +229,11 @@ rst_prolog = """\
.. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers
.. |windownload| raw:: html
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe"><strong>Salt-Minion-{release}-x86-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe.md5"><strong>md5</strong></a></p>
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-x86-Setup.exe"><strong>Salt-Minion-{release}-3-x86-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-x86-Setup.exe.md5"><strong>md5</strong></a></p>
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe"><strong>Salt-Minion-{release}-AMD64-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-AMD64-Setup.exe"><strong>Salt-Minion-{release}-3-AMD64-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
""".format(release=release)

View File

@ -247,6 +247,7 @@ Glossary
whether or not the module should be available to a minion. This
function commonly contains logic to determine if all requirements
for a module are available, such as external libraries.
Worker
A master process which can send notices and receive replies from
minions. *See also*:

View File

@ -46,7 +46,7 @@ Salt's Loader Interface
Modules in the Salt ecosystem are loaded into memory using a custom loader
system. This allows modules to have conditional requirements (OS, OS version,
installed libraries, etc) and allows Salt to inject special variables
(``__salt__``, ``__opts``, etc).
(``__salt__``, ``__opts__``, etc).
Most modules can be manually loaded. This is often useful in third-party Python
apps or when writing tests. However some modules require and expect a full,

View File

@ -1,6 +0,0 @@
==================================
salt.cloud.clouds.digital_ocean_v2
==================================
.. automodule:: salt.cloud.clouds.digital_ocean_v2
:members:

View File

@ -340,6 +340,7 @@ You can call the logger from custom modules to write messages to the minion
logs. The following code snippet demonstrates writing log messages:
.. code-block:: python
import logging
log = logging.getLogger(__name__)

View File

@ -1,6 +0,0 @@
===============
salt.states.apt
===============
.. automodule:: salt.states.apt
:members:

View File

@ -4,6 +4,8 @@
Requisites and Other Global State Arguments
===========================================
.. _requisites-fire-event:
Fire Event Notifications
========================

View File

@ -239,6 +239,7 @@ You can call the logger from custom modules to write messages to the minion
logs. The following code snippet demonstrates writing log messages:
.. code-block:: python
import logging
log = logging.getLogger(__name__)

View File

@ -4,20 +4,35 @@
Beacons
=======
The beacon system allows the minion to hook into system processes and
continually translate external events into the salt event bus. The
primary example of this is the :py:mod:`~salt.beacons.inotify` beacon. This
beacon uses inotify to watch configured files or directories on the minion for
changes, creation, deletion etc.
The beacon system allows the minion to hook into a variety of system processes
and continually monitor these processes. When monitored activity occurs in
a system process, an event is sent on the Salt event bus that can
be used to trigger a :ref:`reactor <reactor>`.
This allows for the changes to be sent up to the master where the
reactor can respond to changes.
Salt beacons can currently monitor and send Salt events for many system
activities, including:
Configuring The Beacons
=======================
- file system changes
- system load
- service status
- shell activity, such as user login
- network and disk usage
The beacon system, like many others in Salt, can be configured via the
minion pillar, grains, or local config file:
See :ref:`beacon modules <all-salt.beacons>` for a current list.
.. note::
Salt beacons are an event generation mechanism. Beacons leverage the Salt
:ref:`reactor <reactor>` system to make changes when beacon events occur.
Configuring Beacons
===================
Salt beacons do not require any changes to the system process that
is being monitored, everything is configured using Salt.
Beacons are typically enabled by placing a ``beacons:`` top level block in the
minion configuration file:
.. code-block:: yaml
@ -26,11 +41,15 @@ minion pillar, grains, or local config file:
/etc/httpd/conf.d: {}
/opt: {}
Optionally, a beacon can be run on an interval other than the default
``loop_interval``, which is typically set to 1 second.
The beacon system, like many others in Salt, can also be configured via the
minion pillar, grains, or local config file.
To run a beacon every 5 seconds, for example, provide an ``interval`` argument
to a beacon.
Beacon Monitoring Interval
--------------------------
Beacons monitor on a 1-second interval by default. To set a different interval,
provide an ``interval`` argument to a beacon. The following beacons run on
5- and 10-second intervals:
.. code-block:: yaml
@ -51,6 +70,123 @@ to a beacon.
- 1.0
- interval: 10
Beacon Example
==============
This example demonstrates configuring the :py:mod:`~salt.beacons.inotify`
beacon to monitor a file for changes, and then create a backup each time
a change is detected.
.. note::
The inotify beacon requires Pyinotify on the minion, install it using
``salt myminion pkg.install python-inotify``.
First, on the Salt minion, add the following beacon configuration to
``/ect/salt/minion``:
.. code-block:: yaml
beacons:
inotify:
home/user/importantfile:
mask:
- modify
Replace ``user`` in the previous example with the name of your user account,
and then save the configuration file and restart the minion service.
Next, create a file in your home directory named ``importantfile`` and add some
simple content. The beacon is now set up to monitor this file for
modifications.
View Events on the Master
-------------------------
On your Salt master, start the event runner using the following command:
.. code-block:: bash
salt-run state.event pretty=true
This runner displays events as they are received on the Salt event bus. To test
the beacon you set up in the previous section, make and save
a modification to the ``importantfile`` you created. You'll see an event
similar to the following on the event bus:
.. code-block:: json
salt/beacon/minion1/inotify/home/user/importantfile {
"_stamp": "2015-09-09T15:59:37.972753",
"data": {
"change": "IN_IGNORED",
"id": "minion1",
"path": "/home/user/importantfile"
},
"tag": "salt/beacon/minion1/inotify/home/user/importantfile"
}
This indicates that the event is being captured and sent correctly. Now you can
create a reactor to take action when this event occurs.
Create a Reactor
----------------
On your Salt master, create a file named ``srv/reactor/backup.sls``. If the
``reactor`` directory doesn't exist, create it. Add the following to ``backup.sls``:
.. code-block:: yaml
backup file:
cmd.file.copy:
- tgt: {{ data['data']['id'] }}
- arg:
- {{ data['data']['path'] }}
- {{ data['data']['path'] }}.bak
Next, add the code to trigger the reactor to ``ect/salt/master``:
.. code-block:: yaml
reactor:
- salt/beacon/*/inotify/*/importantfile:
- /srv/reactor/backup.sls
This reactor creates a backup each time a file named ``importantfile`` is
modified on a minion that has the :py:mod:`~salt.beacons.inotify` beacon
configured as previously shown.
.. note::
You can have only one top level ``reactor`` section, so if one already
exists, add this code to the existing section. See :ref:`Understanding
the Structure of Reactor Formulas <reactor-structure>` to learn more about
reactor SLS syntax.
Start the Salt Master in Debug Mode
-----------------------------------
To help with troubleshooting, start the Salt master in debug mode:
.. code-block:: yaml
service salt-master stop
salt-master -l debug
When debug logging is enabled, event and reactor data are displayed so you can
discover syntax and other issues.
Trigger the Reactor
-------------------
On your minion, make and save another change to ``importantfile``. On the Salt
master, you'll see debug messages that indicate the event was received and the
``file.copy`` job was sent. When you list the directory on the minion, you'll now
see ``importantfile.bak``.
All beacons are configured using a similar process of enabling the beacon,
writing a reactor SLS, and mapping a beacon event to the reactor SLS.
Writing Beacon Plugins
======================

View File

@ -11,7 +11,7 @@ available at PyPI:
https://pypi.python.org/pypi/SoftLayer
This package can be installed using `pip` or `easy_install`:
This package can be installed using ``pip`` or ``easy_install``:
.. code-block:: bash
@ -61,13 +61,13 @@ Set up the cloud config at ``/etc/salt/cloud.providers``:
Access Credentials
==================
The `user` setting is the same user as is used to log into the SoftLayer
Administration area. The `apikey` setting is found inside the Admin area after
The ``user`` setting is the same user as is used to log into the SoftLayer
Administration area. The ``apikey`` setting is found inside the Admin area after
logging in:
* Hover over the `Administrative` menu item.
* Click the `API Access` link.
* The `apikey` is located next to the `user` setting.
* Hover over the ``Account`` menu item.
* Click the ``Users`` link.
* Find the ``API Key`` column and click ``View``.
Profiles
@ -102,13 +102,13 @@ Most of the above items are required; optional items are specified below.
image
-----
Images to build an instance can be found using the `--list-images` option:
Images to build an instance can be found using the ``--list-images`` option:
.. code-block:: bash
# salt-cloud --list-images my-softlayer
The setting used will be labeled as `template`.
The setting used will be labeled as ``template``.
cpu_number
----------
@ -140,7 +140,34 @@ instance.
disk_size
---------
The amount of disk space that will be allocated to this image, in megabytes.
The amount of disk space that will be allocated to this image, in gigabytes.
.. code-block:: yaml
base_softlayer_ubuntu:
disk_size: 100
Using Multiple Disks
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2015.8.1
SoftLayer allows up to 5 disks to be specified for a virtual machine upon
creation. Multiple disks can be specified either as a list or a comma-delimited
string. The first ``disk_size`` specified in the string or list will be the first
disk size assigned to the VM.
List Example:
.. code-block:: yaml
base_softlayer_ubuntu:
disk_size: ['100', '20', '20']
String Example:
.. code-block:: yaml
base_softlayer_ubuntu:
disk_size: '100, 20, 20'
local_disk
----------

View File

@ -1,14 +1,46 @@
.. _installation-debian:
===================
Debian Installation
===================
Currently the latest packages for Debian Old Stable, Stable, and
Unstable (Squeeze, Wheezy, and Sid) are published in our
(saltstack.com) Debian repository.
.. _installation-debian-repo:
Installation from the SaltStack Repository
==========================================
Debian 8 packages are available in the SaltStack Debian repository.
To install using the SaltStack Debian repository:
#. Run the following command to import the SaltStack repository key:
.. code-block:: bash
wget -O - https://repo.saltstack.com/apt/debian/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
#. Add the following line to ``/etc/apt/sources.list``:
.. code-block:: bash
deb http://repo.saltstack.com/apt/debian jessie contrib
#. Run ``sudo apt-get update``
#. Install the salt-minion, salt-master, or other Salt components:
- ``apt-get install salt-master``
- ``apt-get install salt-minion``
- ``apt-get install salt-ssh``
- ``apt-get install salt-syndic``
- ``apt-get install salt-cloud``
Configure Apt
-------------
Currently the latest packages for Debian Old Stable, Stable, and
Unstable (Squeeze, Wheezy, and Sid) are published in our
(saltstack.com) Debian repository.
Squeeze (Old Old Stable)
------------------------

View File

@ -1,26 +1,98 @@
.. _installation-rhel:
==============================================================
RHEL / CentOS / Scientific Linux / Amazon Linux / Oracle Linux
==============================================================
.. _installation-rhel-repo:
Installation from the SaltStack Repository
==========================================
To install using the SaltStack yum repository:
#. Run one of the following commands based on your version to import the SaltStack repository key:
Version 7:
.. code-block:: bash
wget https://repo.saltstack.com/yum/rhel7/SALTSTACK-GPG-KEY.pub
rpm --import SALTSTACK-GPG-KEY.pub
rm -f SALTSTACK-GPG-KEY.pub
Version 6:
.. code-block:: bash
wget https://repo.saltstack.com/yum/rhel6/SALTSTACK-GPG-KEY.pub
rpm --import SALTSTACK-GPG-KEY.pub
rm -f SALTSTACK-GPG-KEY.pub
Version 5:
.. code-block:: bash
wget https://repo.saltstack.com/yum/rhel5/SALTSTACK-EL5-GPG-KEY.pub
rpm --import SALTSTACK-EL5-GPG-KEY.pub
rm -f SALTSTACK-EL5-GPG-KEY.pub
#. Save the following file to ``/etc/yum.repos.d/saltstack.repo``:
Versions 6 / 7:
.. code-block:: config
####################
# Enable SaltStack's package repository
[saltstack-repo]
name=SaltStack repo for RHEL/CentOS $releasever
baseurl=https://repo.saltstack.com/yum/rhel$releasever
enabled=1
gpgcheck=1
gpgkey=https://repo.saltstack.com/yum/rhel$releasever/SALTSTACK-GPG-KEY.pub
Version 5:
.. code-block:: config
####################
# Enable SaltStack's package repository
[saltstack-repo]
name=SaltStack repo for RHEL/CentOS $releasever
baseurl=https://repo.saltstack.com/yum/rhel$releasever
enabled=1
gpgcheck=1
gpgkey=https://repo.saltstack.com/yum/rhel$releasever/SALTSTACK-EL5-GPG-KEY.pub
#. Run ``sudo yum clean expire-cache``
#. Run ``sudo yum update``
#. Install the salt-minion, salt-master, or other Salt components:
- ``yum install salt-master``
- ``yum install salt-minion``
- ``yum install salt-ssh``
- ``yum install salt-syndic``
- ``yum install salt-cloud``
.. note::
EPEL support is not required when installing using the SaltStack repository
on Red Hat 6 and 7. EPEL must be enabled when installing on Red Hat 5.
Installation from Repository
============================
.. _installation-rhel-5:
RHEL/CentOS 5
-------------
Due to the removal of some of Salt's dependencies from EPEL5, we have created a
repository on `Fedora COPR`_. Moving forward, this will be the official means
of installing Salt on RHEL5-based systems. Information on how to enable this
repository can be found here__.
.. _`Fedora COPR`: https://copr.fedoraproject.org/
.. __: https://copr.fedoraproject.org/coprs/saltstack/salt-el5/
RHEL/CentOS 6 and 7, Scientific Linux, etc.
-------------------------------------------
.. warning::
Salt 2015.8 requires ``python-crypto`` 2.6.1 or higher, and ``python-tornado`` version
4.2.1 or higher. These packages are not currently available in EPEL for
Red Hat 5 and 6. You must install these dependencies from another location
or use the SaltStack repository documented above.
Beginning with version 0.9.4, Salt has been available in `EPEL`_. It is
installable using yum. Salt should work properly with all mainstream
derivatives of RHEL, including CentOS, Scientific Linux, Oracle Linux and
@ -34,6 +106,16 @@ installing salt on RHEL6.
.. _`EPEL`: http://fedoraproject.org/wiki/EPEL
.. _installation-rhel-5:
RHEL/CentOS 5
-------------
Due to the removal of some of Salt's dependencies from EPEL5, we recommend
using the :ref:`SaltStack Repository <installation-rhel-repo>` or
the repository on `Fedora COPR`_.
.. _`Fedora COPR`: https://copr.fedoraproject.org/coprs/saltstack/salt-el5/
Enabling EPEL
*************
@ -119,12 +201,10 @@ ZeroMQ 4
========
We recommend using ZeroMQ 4 where available. SaltStack provides ZeroMQ 4.0.4
and pyzmq 14.3.1 in a COPR_ repository. Instructions for adding this repository
(as well as for upgrading ZeroMQ and pyzmq on existing minions) can be found
here__.
and pyzmq 14.3.1 in the :ref:`SaltStack Repository <installation-rhel-repo>`
as well as a COPR_ repository.
.. _COPR: http://copr.fedoraproject.org/
.. __: http://copr.fedoraproject.org/coprs/saltstack/zeromq4/
.. _COPR: http://copr.fedoraproject.org/coprs/saltstack/zeromq4/
If this repo is added *before* Salt is installed, then installing either
``salt-master`` or ``salt-minion`` will automatically pull in ZeroMQ 4.0.4, and
@ -136,7 +216,6 @@ additional states to upgrade ZeroMQ and pyzmq are unnecessary.
because YUM will not be able to process the SHA256 checksums used by COPR.
.. note::
For RHEL/CentOS 5 installations, if using the new repository to install
Salt (as detailed :ref:`above <installation-rhel-5>`), then it is not
necessary to enable the zeromq4 COPR, as the new EL5 repository includes

View File

@ -9,7 +9,7 @@ all package development will be done there.
Installation
============
Salt can be installed using ``zypper`` and is available in the standard openSUSE 13.1
Salt can be installed using ``zypper`` and is available in the standard openSUSE
repositories.
Stable Release
@ -101,6 +101,16 @@ For openSUSE Factory run the following as root:
zypper refresh
zypper install salt salt-minion salt-master
For openSUSE 13.2 run the following as root:
.. code-block:: bash
zypper addrepo http://download.opensuse.org/repositories/devel:languages:python/openSUSE_13.2/devel:languages:python.repo
zypper refresh
zypper install salt salt-minion salt-master
For openSUSE 13.1 run the following as root:
.. code-block:: bash

View File

@ -1,3 +1,5 @@
.. _windows:
=======
Windows
=======
@ -11,6 +13,7 @@ to control your Salt Minions on Windows.
Many of the standard Salt modules have been ported to work on Windows and many
of the Salt States currently work on Windows, as well.
.. _windows-installer:
Windows Installer
=================
@ -46,9 +49,10 @@ Windows service.
If the minion won't start, try installing the Microsoft Visual C++ 2008 x64 SP1
redistributable. Allow all Windows updates to run salt-minion smoothly.
.. _windows-installer-options:
Silent Installer option
=======================
Silent Installer Options
========================
The installer can be run silently by providing the `/S` option at the command
line. The options `/master` and `/minion-name` allow for configuring the master

View File

@ -32,6 +32,8 @@ The event system fires events with a very specific criteria. Every event has a
addition to the tag, each event has a data structure. This data structure is a
dict, which contains information about the event.
.. _reactor-mapping-events:
Mapping Events to Reactor SLS Files
===================================
@ -183,6 +185,8 @@ rendered SLS file (or any errors generated while rendering the SLS file).
view the result of referencing Jinja variables. If the result is empty then
Jinja produced an empty result and the Reactor will ignore it.
.. _reactor-structure:
Understanding the Structure of Reactor Formulas
===============================================
@ -273,10 +277,10 @@ Any other parameters in the :py:meth:`LocalClient().cmd()
Calling Runner modules and Wheel modules
----------------------------------------
Calling Runner modules and wheel modules from the Reactor uses a more direct
Calling Runner modules and Wheel modules from the Reactor uses a more direct
syntax since the function is being executed locally instead of sending a
command to a remote system to be executed there. There are no 'arg' or 'kwarg'
parameters (unless the Runner function or Wheel function accepts a paramter
parameters (unless the Runner function or Wheel function accepts a parameter
with either of those names.)
For example:

View File

@ -10,12 +10,23 @@ usual the release notes are not exhaustive and primarily include the most
notable additions and improvements. Hundreds of bugs have been fixed and many
modules have been substantially updated and added.
New SaltStack Installation Repositories
=======================================
SaltStack now provides installation repositories for several platforms, with more to come.
See the following links for instructions:
- :ref:`Red Hat / CentOS 5, 6, 7 <installation-rhel-repo>`
- :ref:`Debian 8 <installation-debian-repo>`
- :ref:`Windows <windows-installer>`
- FreeBSD
Send Event on State Completion
==============================
A `fire_event` global state keyword argument was added that allows any state to
send an event upon completion. Useful for custom progress bars and checking in
on long state runs.
on long state runs. See :ref:`fire_event <requisites-fire-event>`.
ZeroMQ socket monitoring
========================
@ -35,7 +46,7 @@ A new :conf_master:`default_top` option was added to load the state top file
from a single, specific environment, rather than merging top data across all
environments. Additionally, new :conf_master:`top_file_merge_strategy` and
:conf_master:`env_order` options were added for more control over top file
merging.
merging. See :ref:`The Top File <states-top>`.
Tornado TCP Transport
=====================
@ -47,12 +58,12 @@ performance.
.. note::
Tornado is considered early-access in this release. The following known
Tornado is considered expiremental in this release. The following known
issues were being investigated at the time of release:
- TCP tests show
performance degredation over time (:issue:`26051`)
- TCP transport stacktrace on windows minion: Future exception was never
retrieved (:issue:`25718)
retrieved (:issue:`25718`)
- [freebsd] TCP transport not working in 2015.8.0rc3 (:issue:`26364`)
Proxy Minion Enhancements
@ -80,19 +91,16 @@ with :mod:`gitfs <salt.fileserver.gitfs>`. Support for pygit2_ has been added,
bringing with it the ability to access authenticated repositories.
Using the new features will require updates to the git ext_pillar
configuration, further details can be found :ref:`here
<git-pillar-2015-8-0-and-later>`.
configuration, further details can be found in the :ref:`pillar.git_pillar
<git-pillar-2015-8-0-and-later>` docs.
Salt Cloud Improvements
=======================
- Pricing data from several cloud providers (GCE, DigitalOcean, SoftLayer_HW, EC2)
- All cloud providers now use standardized bootstrapping code
- All cloud providers now use standardized bootstrapping code.
- Modified the Linode Salt Cloud driver to use Linode's native API instead of
depending on apache-libcloud or linode-python.
- When querying for VMs in ``digital_ocean.py``, the number of VMs to include in
a page was changed from 20 (default) to 200 to reduce the number of API calls
to Digital
Salt Cloud Changes
------------------
@ -103,9 +111,7 @@ Salt Cloud Changes
duplicate names of VMs before trying to create a new VM. Will now throw an
error similarly to other salt-cloud drivers when trying to create a VM of the
same name, even if the VM is in the ``terminated`` state.
- Modified the Linode Salt Cloud driver to use Linode's native API instead of
depending on apache-libcloud or linode-python.
- When querying for VMs in ``ditigal_ocean.py``, the number of VMs to include in
- When querying for VMs in ``digital_ocean.py``, the number of VMs to include in
a page was changed from 20 (default) to 200 to reduce the number of API calls
to Digital Ocean.Ocean.
@ -125,7 +131,7 @@ State and Execution Module Improvements
- Deprecate ``enabled`` argument in ``pkgrepo.managed`` in favor of ``disabled``.
- Archive module changes: In the ``archive.tar`` and ``archive.cmd_unzip``
module functions, remove the arbitrary prefixing of the options string with
``-``. An options string beginning with a ``--long-option``, would have
``-``. An options string beginning with a ``--long-option``, would have
uncharacteristically needed its first ``-`` removed under the former scheme.
Also, tar will parse its options differently if short options are used with or
without a preceding ``-``, so it is better to not confuse the user into
@ -138,10 +144,12 @@ State and Execution Module Improvements
Windows Improvements
====================
- Templatize Windows Software package definitions
- Added additional capabilities to the user module for windows
- Added new module for managing windows updates (win_wua)
- Turned on multi-processing by default for windows in minion conf
- Enhanced the windows minion silent installation with command line parameters
to configure the salt master and minion name. See :ref:`Silent Installer
Options <windows-installer-options>`.
- Improved user management with additional capabilities in the user module for Windows.
- Improved patch management with a new module for managing windows updates (:mod:`win_wua <modules.win_wua>`).
- Turned on multi-processing by default for windows in minion configuration.
Windows Software Repo Changes
-----------------------------
@ -190,7 +198,6 @@ seconds by specifying ``in_seconds=True``.
Other Improvements
==================
- Python 3
- Sanitize sensitive fields in http.query
- Allow authorization to be read from Django and eauth
- Add templating to SMTP returner
@ -201,6 +208,7 @@ Other Improvements
- Add end time to master job cache for jobs (optional, off by default)
- Tornado is now the default backend for http.request
- Support pillarenv selection as it's done for saltenv
- salt was updated to use python-crypto version 2.6.1, which removes the dependency on python-m2crypto.
Deprecations
============
@ -282,3 +290,4 @@ Major Bug Fixes
- Improve process management in proxy minion (:issue:`12024`)
- Proxy minion never comes up with message ' I am XXX and I am not supposed to
start any proxies.' (:issue:`25908`)
- Fixed an issue that caused an exception when using Salt mine from pillar. (:issue:`11509`)

View File

@ -9,15 +9,6 @@ LXC Management with Salt
This walkthrough assumes basic knowledge of Salt. To get up to speed, check
out the :doc:`Salt Walkthrough </topics/tutorials/walkthrough>`.
.. warning::
Some features are only currently available in the ``develop`` branch, and
are new in the upcoming 2015.5.0 release. These new features will be
clearly labeled.
Even in 2015.5 release, you will need up to the last changeset of this
stable branch for the salt-cloud stuff to work correctly.
Dependencies
============
@ -106,7 +97,7 @@ Consider the following container profile data:
size: 20G
Any minion with the above Pillar data would have the **size** parameter in the
**centos** profile overriden to 20G, while those minions without the above
**centos** profile overridden to 20G, while those minions without the above
Pillar data would have the 10G **size** value. This is another way of achieving
the same result as the **centos_big** profile above, without having to define
another whole profile that differs in just one value.
@ -155,7 +146,7 @@ get connectivity.
.. warning::
on pre **2015.5.2**, you need to specify explitly the network bridge
on pre **2015.5.2**, you need to specify explicitly the network bridge
.. code-block:: yaml
@ -249,7 +240,7 @@ container-by-container basis, for instance using the ``nic_opts`` argument to
Old lxc support (<1.0.7)
---------------------------
------------------------
With saltstack **2015.5.2** and above, normally the setting is autoselected, but
before, you'll need to teach your network profile to set
@ -265,7 +256,7 @@ Thus you'll need
ipv4.gateway: auto
Tricky network setups Examples
-----------------------------------
------------------------------
This example covers how to make a container with both an internal ip and a
public routable ip, wired on two veth pairs.
@ -521,7 +512,7 @@ To run a command and return all information:
Container Management Using salt-cloud
========================================
=====================================
Salt cloud uses under the hood the salt runner and module to manage containers,
Please look at :ref:`this chapter <config_lxc>`

View File

@ -5,16 +5,16 @@ Provide authentication using Stormpath.
This driver requires some extra configuration beyond that which Stormpath
normally requires.
.. code-block:: yaml
.. code-block:: yaml
stormpath:
apiid: 1234567890
apikey: 1234567890/ABCDEF
# Can use an application ID
application: 6789012345
# Or can use a directory ID
directory: 3456789012
# But not both
stormpath:
apiid: 1234567890
apikey: 1234567890/ABCDEF
# Can use an application ID
application: 6789012345
# Or can use a directory ID
directory: 3456789012
# But not both
.. versionadded:: 2015.8.0
'''

View File

@ -348,16 +348,19 @@ class SyncClientMixin(object):
data['success'] = False
namespaced_event.fire_event(data, 'ret')
salt.utils.job.store_job(
self.opts,
{'id': self.opts['id'],
'tgt': self.opts['id'],
'jid': data['jid'],
'return': data,
},
event=None,
mminion=self.mminion,
)
try:
salt.utils.job.store_job(
self.opts,
{'id': self.opts['id'],
'tgt': self.opts['id'],
'jid': data['jid'],
'return': data,
},
event=None,
mminion=self.mminion,
)
except salt.exceptions.SaltCacheError:
log.error('Could not store job cache info. Job details for this run may be unavailable.')
# if we fired an event, make sure to delete the event object.
# This will ensure that we call destroy, which will do the 0MQ linger
log.info('Runner completed: {0}'.format(data['jid']))

View File

@ -137,7 +137,7 @@ def avail_images(call=None):
for image in items['images']:
ret[image['id']] = {}
for item in six.iterkeys(image):
ret[image['id']][item] = str(image[item])
ret[image['id']][item] = image[item]
page += 1
try:
@ -184,10 +184,36 @@ def list_nodes(call=None):
while fetch:
items = query(method='droplets', command='?page=' + str(page) + '&per_page=200')
for node in items['droplets']:
networks = node['networks']
v4s = networks.get('v4')
v6s = networks.get('v6')
public_ips = []
private_ips = []
if v4s:
for item in v4s:
ip_type = item.get('type')
ip_address = item.get('ip_address')
if ip_type == 'public':
public_ips.append(ip_address)
if ip_type == 'private':
private_ips.append(ip_address)
if v6s:
for item in v6s:
ip_type = item.get('type')
ip_address = item.get('ip_address')
if ip_type == 'public':
public_ips.append(ip_address)
if ip_type == 'private':
private_ips.append(ip_address)
ret[node['name']] = {
'id': node['id'],
'image': node['image']['name'],
'networks': str(node['networks']),
'name': node['name'],
'private_ips': private_ips,
'public_ips': public_ips,
'size': node['size_slug'],
'state': str(node['status']),
}
@ -244,9 +270,9 @@ def get_image(vm_):
Return the image object to use
'''
images = avail_images()
vm_image = str(config.get_cloud_config_value(
vm_image = config.get_cloud_config_value(
'image', vm_, __opts__, search_global=False
))
)
for image in images:
if vm_image in (images[image]['name'],
images[image]['slug'],

View File

@ -3188,6 +3188,7 @@ def list_nodes(call=None):
ret[node] = {
'id': nodes[node]['id'],
'image': nodes[node]['image'],
'name': nodes[node]['name'],
'size': nodes[node]['size'],
'state': nodes[node]['state'],
'private_ips': nodes[node]['private_ips'],

View File

@ -1518,6 +1518,7 @@ def _validate_name(name):
name
The VM name to validate
'''
name = str(name)
name_length = len(name)
regex = re.compile(r'^[a-zA-Z0-9][A-Za-z0-9_-]*[a-zA-Z0-9]$')

View File

@ -222,7 +222,7 @@ def list_nodes(conn=None, call=None):
nodes = list_nodes_full(conn, call)
for node in nodes:
ret[node] = {}
for prop in 'id', 'image', 'size', 'state', 'private_ips', 'public_ips':
for prop in 'id', 'image', 'name', 'size', 'state', 'private_ips', 'public_ips':
ret[node][prop] = nodes[node][prop]
return ret

View File

@ -275,16 +275,43 @@ def create(vm_):
'domain': vm_['domain'],
'startCpus': vm_['cpu_number'],
'maxMemory': vm_['ram'],
'localDiskFlag': vm_['local_disk'],
'hourlyBillingFlag': vm_['hourly_billing'],
}
local_disk_flag = config.get_cloud_config_value(
'local_disk', vm_, __opts__, default=False
)
kwargs['localDiskFlag'] = local_disk_flag
if 'image' in vm_:
kwargs['operatingSystemReferenceCode'] = vm_['image']
kwargs['blockDevices'] = [{
'device': '0',
'diskImage': {'capacity': vm_['disk_size']},
}]
kwargs['blockDevices'] = []
disks = vm_['disk_size']
if isinstance(disks, int):
disks = [str(disks)]
elif isinstance(disks, str):
disks = [size.strip() for size in disks.split(',')]
count = 0
for disk in disks:
# device number '1' is reserved for the SWAP disk
if count == 1:
count += 1
block_device = {'device': str(count),
'diskImage': {'capacity': str(disk)}}
kwargs['blockDevices'].append(block_device)
count += 1
# Upper bound must be 5 as we're skipping '1' for the SWAP disk ID
if count > 5:
log.warning('More that 5 disks were specified for {0} .'
'The first 5 disks will be applied to the VM, '
'but the remaining disks will be ignored.\n'
'Please adjust your cloud configuration to only '
'specify a maximum of 5 disks.'.format(vm_['name']))
break
elif 'global_identifier' in vm_:
kwargs['blockDeviceTemplateGroup'] = {
'globalIdentifier': vm_['global_identifier']

View File

@ -436,6 +436,7 @@ def list_nodes(conn=None, call=None):
ret[node.name] = {
'id': node.id,
'image': node.image,
'name': node.name,
'private_ips': node.private_ips,
'public_ips': node.public_ips,
'size': node.size,

View File

@ -32,6 +32,7 @@ from salt.ext.six.moves.urllib.parse import urlparse
# Import salt libs
import salt.utils
import salt.utils.dictupdate
import salt.utils.network
import salt.syspaths
import salt.utils.validate.path
@ -1481,7 +1482,7 @@ def include_config(include, orig_path, verbose):
for fn_ in sorted(glob.glob(path)):
log.debug('Including configuration from \'{0}\''.format(fn_))
configuration.update(_read_conf_file(fn_))
salt.utils.dictupdate.update(configuration, _read_conf_file(fn_))
return configuration

View File

@ -157,6 +157,12 @@ class SaltClientTimeout(SaltException):
self.jid = jid
class SaltCacheError(SaltException):
'''
Thrown when a problem was encountered trying to read or write from the salt cache
'''
class SaltReqTimeoutError(SaltException):
'''
Thrown when a salt master request call fails to return within the timeout

View File

@ -6,7 +6,6 @@ from __future__ import absolute_import
# Import python libs
import contextlib
import errno
import logging
import hashlib
import os
@ -23,6 +22,7 @@ import salt.payload
import salt.transport
import salt.fileserver
import salt.utils
import salt.utils.files
import salt.utils.templates
import salt.utils.url
import salt.utils.gzip_util
@ -512,7 +512,8 @@ class Client(object):
ret.sort()
return ret
def get_url(self, url, dest, makedirs=False, saltenv='base', env=None, no_cache=False):
def get_url(self, url, dest, makedirs=False, saltenv='base',
env=None, no_cache=False):
'''
Get a single file from a URL.
'''
@ -617,27 +618,59 @@ class Client(object):
password=url_data.password,
**get_kwargs
)
if 'handle' not in query:
raise MinionError('Error: {0}'.format(query['error']))
try:
content_length = int(query['handle'].headers['Content-Length'])
except (AttributeError, KeyError, ValueError):
# Shouldn't happen but don't let this raise an exception.
# Instead, just don't do content length checking below.
log.warning(
'No Content-Length header in HTTP response from fetch of '
'{0}, or Content-Length is non-numeric'.format(fixed_url)
)
content_length = None
if no_cache:
return ''.join(result)
content = ''.join(result)
if content_length is not None \
and len(content) > content_length:
return content[-content_length:]
else:
return content
else:
destfp.close()
destfp = None
# Can't just do an os.rename() here, this results in a
# WindowsError being raised when the destination path exists on
# a Windows machine. Have to remove the file.
try:
os.remove(dest)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise MinionError(
'Error: Unable to remove {0}: {1}'.format(
dest,
exc.strerror
)
dest_tmp_size = os.path.getsize(dest_tmp)
if content_length is not None \
and dest_tmp_size > content_length:
log.warning(
'Size of file downloaded from {0} ({1}) does not '
'match the Content-Length ({2}). This is probably due '
'to an upstream bug in tornado '
'(https://github.com/tornadoweb/tornado/issues/1518). '
'Re-writing the file to correct this.'.format(
fixed_url,
dest_tmp_size,
content_length
)
os.rename(dest_tmp, dest)
)
dest_tmp_bak = dest_tmp + '.bak'
salt.utils.files.rename(dest_tmp, dest_tmp_bak)
with salt.utils.fopen(dest_tmp_bak, 'rb') as fp_bak:
fp_bak.seek(dest_tmp_size - content_length)
with salt.utils.fopen(dest_tmp, 'wb') as fp_new:
while True:
chunk = fp_bak.read(
self.opts['file_buffer_size']
)
if not chunk:
break
fp_new.write(chunk)
os.remove(dest_tmp_bak)
salt.utils.files.rename(dest_tmp, dest)
return dest
except HTTPError as exc:
raise MinionError('HTTP error {0} reading {1}: {3}'.format(

View File

@ -1738,7 +1738,7 @@ def _hw_data(osdata):
for serial in ('system-serial-number', 'chassis-serial-number', 'baseboard-serial-number'):
serial = __salt__['smbios.get'](serial)
if serial is not None:
grains['serial'] = serial
grains['serialnumber'] = serial
break
elif osdata['kernel'] == 'FreeBSD':
# On FreeBSD /bin/kenv (already in base system)

View File

@ -1213,8 +1213,11 @@ class AESFuncs(object):
:param dict load: The minion payload
'''
salt.utils.job.store_job(
self.opts, load, event=self.event, mminion=self.mminion)
try:
salt.utils.job.store_job(
self.opts, load, event=self.event, mminion=self.mminion)
except salt.exception.SaltCacheError:
log.error('Could not store job information for load: {0}'.format(load))
def _syndic_return(self, load):
'''

View File

@ -74,7 +74,7 @@ def __virtual__():
'''
if not HAS_BOTO:
return False
__utils__['boto.assign_funcs'](__name__, 'dynamodb')
__utils__['boto.assign_funcs'](__name__, 'dynamodb2')
return True

View File

@ -3342,6 +3342,7 @@ def worktree_prune(cwd,
.. versionadded:: 2015.8.0
.. _`git-worktree(1)`: http://git-scm.com/docs/git-worktree
.. _`git-config(1)`: http://git-scm.com/docs/git-config/2.5.1
CLI Examples:

View File

@ -5,11 +5,15 @@ Manage groups on Linux, OpenBSD and NetBSD
# Import python libs
from __future__ import absolute_import
import logging
try:
import grp
except ImportError:
pass
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'group'
@ -142,12 +146,17 @@ def adduser(name, username):
Verifies if a valid username 'bar' as a member of an existing group 'foo',
if not then adds it.
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
if __grains__['kernel'] == 'Linux':
retcode = __salt__['cmd.retcode']('gpasswd --add {0} {1}'.format(
username, name), python_shell=False)
if on_redhat_5:
cmd = 'gpasswd -a {0} {1}'.format(username, name)
else:
cmd = 'gpasswd --add {0} {1}'.format(username, name)
else:
retcode = __salt__['cmd.retcode']('usermod -G {0} {1}'.format(
name, username), python_shell=False)
cmd = 'usermod -G {0} {1}'.format(name, username)
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
return not retcode
@ -165,21 +174,27 @@ def deluser(name, username):
Removes a member user 'bar' from a group 'foo'. If group is not present
then returns True.
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
grp_info = __salt__['group.info'](name)
try:
if username in grp_info['members']:
if __grains__['kernel'] == 'Linux':
retcode = __salt__['cmd.retcode']('gpasswd --del {0} {1}'
.format(username, name), python_shell=False)
if on_redhat_5:
cmd = 'gpasswd -d {0} {1}'.format(username, name)
else:
cmd = 'gpasswd --del {0} {1}'.format(username, name)
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
elif __grains__['kernel'] == 'OpenBSD':
cmd = 'usermod -S '
out = __salt__['cmd.run_stdout']('id -Gn {0}'.format(username),
python_shell=False)
for group in out.split(" "):
if group != format(name):
cmd += '{0},'.format(group)
retcode = __salt__['cmd.retcode']('{0} {1}'.format(
cmd, username), python_shell=False)
cmd = 'usermod -S '
cmd += ','.join([g for g in out.split() if g != str(name)])
cmd += ' {0}'.format(username)
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
else:
log.error('group.deluser is not yet supported on this platform')
return False
return not retcode
else:
return True
@ -198,9 +213,14 @@ def members(name, members_list):
Replaces a membership list for a local group 'foo'.
foo:x:1234:user1,user2,user3,...
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
if __grains__['kernel'] == 'Linux':
retcode = __salt__['cmd.retcode']('gpasswd --members {0} {1}'.format(
members_list, name), python_shell=False)
if on_redhat_5:
cmd = 'gpasswd -M {0} {1}'.format(members_list, name)
else:
cmd = 'gpasswd --members {0} {1}'.format(members_list, name)
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
elif __grains__['kernel'] == 'OpenBSD':
retcode = 1
grp_info = __salt__['group.info'](name)
@ -219,5 +239,8 @@ def members(name, members_list):
# provided list is '': users previously deleted from group
else:
retcode = 0
else:
log.error('group.members is not yet supported on this platform')
return False
return not retcode

View File

@ -8,6 +8,12 @@ from __future__ import absolute_import
import logging
import re
import os
HAS_DBUS = False
try:
import dbus
HAS_DBUS = True
except ImportError:
pass
# Import salt libs
import salt.utils
@ -25,38 +31,55 @@ def __virtual__():
'''
Only work on POSIX-like systems
'''
if HAS_DBUS is False and _uses_dbus():
return False
if salt.utils.is_windows():
return False
return __virtualname__
def _parse_localectl():
def _uses_dbus():
if 'Arch' in __grains__['os_family']:
return True
elif 'RedHat' in __grains__['os_family']:
return False
elif 'Debian' in __grains__['os_family']:
return False
elif 'Gentoo' in __grains__['os_family']:
return False
def _parse_dbus_locale():
'''
Get the 'System Locale' parameters from localectl
Get the 'System Locale' parameters from dbus
'''
ret = {}
for line in __salt__['cmd.run']('localectl').splitlines():
cols = [x.strip() for x in line.split(':', 1)]
if len(cols) > 1:
cur_param = cols.pop(0)
if cur_param == 'System Locale':
try:
key, val = re.match('^([A-Z_]+)=(.*)$', cols[0]).groups()
except AttributeError:
log.error('Odd locale parameter "{0}" detected in localectl '
'output. This should not happen. localectl should '
'catch this. You should probably investigate what '
'caused this.'.format(cols[0]))
else:
ret[key] = val.replace('"', '')
bus = dbus.SystemBus()
localed = bus.get_object('org.freedesktop.locale1',
'/org/freedesktop/locale1')
properties = dbus.Interface(localed, 'org.freedesktop.DBus.Properties')
system_locale = properties.Get('org.freedesktop.locale1', 'Locale')
try:
key, val = re.match('^([A-Z_]+)=(.*)$', system_locale[0]).groups()
except AttributeError:
log.error('Odd locale parameter "{0}" detected in dbus locale '
'output. This should not happen. You should '
'probably investigate what caused this.'.format(
system_locale[0]))
else:
ret[key] = val.replace('"', '')
return ret
def _localectl_get():
def _locale_get():
'''
Use systemd's localectl command to get the current locale
Use dbus to get the current locale
'''
return _parse_localectl().get('LANG', '')
return _parse_dbus_locale().get('LANG', '')
def _localectl_set(locale=''):
@ -64,7 +87,7 @@ def _localectl_set(locale=''):
Use systemd's localectl command to set the LANG locale parameter, making
sure not to trample on other params that have been set.
'''
locale_params = _parse_localectl()
locale_params = _parse_dbus_locale()
locale_params['LANG'] = str(locale)
args = ' '.join(['{0}="{1}"'.format(k, v)
for k, v in six.iteritems(locale_params)])
@ -99,12 +122,12 @@ def get_locale():
'''
cmd = ''
if 'Arch' in __grains__['os_family']:
return _localectl_get()
return _locale_get()
elif 'RedHat' in __grains__['os_family']:
cmd = 'grep "^LANG=" /etc/sysconfig/i18n'
elif 'Debian' in __grains__['os_family']:
if salt.utils.which('localectl'):
return _localectl_get()
return _locale_get()
cmd = 'grep "^LANG=" /etc/default/locale'
elif 'Gentoo' in __grains__['os_family']:
@ -236,10 +259,12 @@ def gen_locale(locale, **kwargs):
locale_info['territory']) in os.listdir(search)
except OSError as ex:
log.error(ex)
raise CommandExecutionError("Locale \"{0}\" is not available.".format(locale))
raise CommandExecutionError(
"Locale \"{0}\" is not available.".format(locale))
if not valid:
log.error('The provided locale "{0}" is not found in {1}'.format(locale, search))
log.error(
'The provided locale "{0}" is not found in {1}'.format(locale, search))
return False
if os.path.exists('/etc/locale.gen'):
@ -267,7 +292,8 @@ def gen_locale(locale, **kwargs):
cmd.append(locale)
elif salt.utils.which("localedef") is not None:
cmd = ['localedef', '--force',
'-i', "{0}_{1}".format(locale_info['language'], locale_info['territory']),
'-i', "{0}_{1}".format(locale_info['language'],
locale_info['territory']),
'-f', locale_info['codeset'],
locale]
cmd.append(kwargs.get('verbose', False) and '--verbose' or '--quiet')

View File

@ -866,7 +866,8 @@ def _network_conf(conf_tuples=None, **kwargs):
# (lxc.network.ipv4.gateway: auto)
if (
distutils.version.LooseVersion(version()) <= '1.0.7' and
True not in ['lxc.network.ipv4.gateway' in a for a in ret]
True not in ['lxc.network.ipv4.gateway' in a for a in ret] and
True in ['lxc.network.ipv4' in a for a in ret]
):
ret.append({'lxc.network.ipv4.gateway': 'auto'})
return ret

View File

@ -50,14 +50,25 @@ import salt.ext.six as six
# pylint: disable=import-error
from salt.ext.six.moves import range, zip # pylint: disable=no-name-in-module,redefined-builtin
try:
# Try to import MySQLdb
import MySQLdb
import MySQLdb.cursors
import MySQLdb.converters
from MySQLdb.constants import FIELD_TYPE, FLAG
HAS_MYSQLDB = True
except ImportError:
HAS_MYSQLDB = False
# pylint: enable=import-error
try:
# MySQLdb import failed, try to import PyMySQL
import pymysql
pymysql.install_as_MySQLdb()
import MySQLdb
import MySQLdb.cursors
import MySQLdb.converters
from MySQLdb.constants import FIELD_TYPE, FLAG
HAS_MYSQLDB = True
except ImportError:
# No MySQL Connector installed, return False
HAS_MYSQLDB = False
log = logging.getLogger(__name__)

View File

@ -457,6 +457,15 @@ def delete_key_recursive(hive, key):
A dictionary listing the keys that deleted successfully as well as those
that failed to delete.
:rtype: dict
The following example will remove ``salt`` and all its subkeys from the
``SOFTWARE`` key in ``HKEY_LOCAL_MACHINE``:
CLI Example:
.. code-block:: bash
salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt
'''
# Functions for traversing the registry tree
def subkeys(key):

View File

@ -50,7 +50,7 @@ _ETHTOOL_CONFIG_OPTS = [
'gso', 'gro', 'lro'
]
_RH_CONFIG_OPTS = [
'domain', 'peerdns', 'defroute',
'domain', 'peerdns', 'peerntp', 'defroute',
'mtu', 'static-routes', 'gateway'
]
_RH_CONFIG_BONDING_OPTS = [

View File

@ -161,6 +161,13 @@ def _sync(form, saltenv=None):
mod_file = os.path.join(__opts__['cachedir'], 'module_refresh')
with salt.utils.fopen(mod_file, 'a+') as ofile:
ofile.write('')
if form == 'grains' and \
__opts__.get('grains_cache') and \
os.path.isfile(os.path.join(__opts__['cachedir'], 'grains.cache.p')):
try:
os.remove(os.path.join(__opts__['cachedir'], 'grains.cache.p'))
except OSError:
log.error('Could not remove grains cache!')
return ret

View File

@ -6,8 +6,10 @@ from __future__ import absolute_import
# Import python libs
import os
import errno
import logging
import re
import string
# Import salt libs
import salt.utils
@ -25,6 +27,67 @@ def __virtual__():
return True
def _get_zone_solaris():
tzfile = '/etc/TIMEZONE'
with salt.utils.fopen(tzfile, 'r') as fp_:
for line in fp_:
if 'TZ=' in line:
zonepart = line.rstrip('\n').split('=')[-1]
return zonepart.strip('\'"') or 'UTC'
raise CommandExecutionError('Unable to get timezone from ' + tzfile)
def _get_zone_sysconfig():
tzfile = '/etc/sysconfig/clock'
with salt.utils.fopen(tzfile, 'r') as fp_:
for line in fp_:
if re.match(r'^\s*#', line):
continue
if 'ZONE' in line and '=' in line:
zonepart = line.rstrip('\n').split('=')[-1]
return zonepart.strip('\'"') or 'UTC'
raise CommandExecutionError('Unable to get timezone from ' + tzfile)
def _get_zone_etc_localtime():
tzfile = '/etc/localtime'
tzdir = '/usr/share/zoneinfo/'
tzdir_len = len(tzdir)
try:
olson_name = os.path.normpath(
os.path.join('/etc', os.readlink(tzfile))
)
if olson_name.startswith(tzdir):
return olson_name[tzdir_len:]
except OSError as exc:
if exc.errno == errno.ENOENT:
raise CommandExecutionError(tzfile + ' does not exist')
elif exc.errno == errno.EINVAL:
log.warning(
tzfile + ' is not a symbolic link, attempting to match ' +
tzfile + ' to zoneinfo files'
)
# Regular file. Try to match the hash.
hash_type = __opts__.get('hash_type', 'md5')
tzfile_hash = salt.utils.get_hash(tzfile, hash_type)
# Not a link, just a copy of the tzdata file
for root, dirs, files in os.walk(tzdir):
for filename in files:
full_path = os.path.join(root, filename)
olson_name = full_path[tzdir_len:]
if olson_name[0] in string.ascii_lowercase:
continue
if tzfile_hash == \
salt.utils.get_hash(full_path, hash_type):
return olson_name
raise CommandExecutionError('Unable to determine timezone')
def _get_zone_etc_timezone():
with salt.utils.fopen('/etc/timezone', 'r') as fp_:
return fp_.read().strip()
def get_zone():
'''
Get current timezone (i.e. America/Denver)
@ -37,7 +100,7 @@ def get_zone():
'''
cmd = ''
if salt.utils.which('timedatectl'):
out = __salt__['cmd.run']('timedatectl')
out = __salt__['cmd.run'](['timedatectl'], python_shell=False)
for line in (x.strip() for x in out.splitlines()):
try:
return re.match(r'Time ?zone:\s+(\S+)', line).group(1)
@ -46,23 +109,21 @@ def get_zone():
raise CommandExecutionError(
'Failed to parse timedatectl output, this is likely a bug'
)
elif 'RedHat' in __grains__['os_family']:
cmd = 'grep ZONE /etc/sysconfig/clock | grep -vE "^#"'
elif 'Suse' in __grains__['os_family']:
cmd = 'grep ZONE /etc/sysconfig/clock | grep -vE "^#"'
elif 'Debian' in __grains__['os_family']:
with salt.utils.fopen('/etc/timezone', 'r') as ofh:
return ofh.read().strip()
elif 'Gentoo' in __grains__['os_family']:
with salt.utils.fopen('/etc/timezone', 'r') as ofh:
return ofh.read().strip()
elif __grains__['os_family'] in ('FreeBSD', 'OpenBSD', 'NetBSD'):
return os.readlink('/etc/localtime').lstrip('/usr/share/zoneinfo/')
elif 'Solaris' in __grains__['os_family']:
cmd = 'grep "TZ=" /etc/TIMEZONE'
out = __salt__['cmd.run'](cmd, python_shell=True).split('=')
ret = out[1].replace('"', '')
return ret
else:
if __grains__['os'].lower() == 'centos':
return _get_zone_etc_localtime()
os_family = __grains__['os_family']
for family in ('RedHat', 'Suse'):
if family in os_family:
return _get_zone_sysconfig()
for family in ('Debian', 'Gentoo'):
if family in os_family:
return _get_zone_etc_timezone()
if os_family in ('FreeBSD', 'OpenBSD', 'NetBSD'):
return _get_zone_etc_localtime()
elif 'Solaris' in os_family:
return _get_zone_solaris()
raise CommandExecutionError('Unable to get timezone')
def get_zonecode():
@ -75,9 +136,7 @@ def get_zonecode():
salt '*' timezone.get_zonecode
'''
cmd = 'date +%Z'
out = __salt__['cmd.run'](cmd)
return out
return __salt__['cmd.run'](['date', '+%Z'], python_shell=False)
def get_offset():
@ -90,9 +149,7 @@ def get_offset():
salt '*' timezone.get_offset
'''
cmd = 'date +%z'
out = __salt__['cmd.run'](cmd)
return out
return __salt__['cmd.run'](['date', '+%z'], python_shell=False)
def set_zone(timezone):
@ -101,7 +158,8 @@ def set_zone(timezone):
The timezone is crucial to several system processes, each of which SHOULD
be restarted (for instance, whatever you system uses as its cron and
syslog daemons). This will not be magically done for you!
syslog daemons). This will not be automagically done and must be done
manually!
CLI Example:
@ -206,7 +264,7 @@ def get_hwclock():
'''
cmd = ''
if salt.utils.which('timedatectl'):
out = __salt__['cmd.run']('timedatectl')
out = __salt__['cmd.run'](['timedatectl'], python_shell=False)
for line in (x.strip() for x in out.splitlines()):
if 'rtc in local tz' in line.lower():
try:
@ -219,39 +277,64 @@ def get_hwclock():
raise CommandExecutionError(
'Failed to parse timedatectl output, this is likely a bug'
)
elif 'RedHat' in __grains__['os_family']:
cmd = 'tail -n 1 /etc/adjtime'
return __salt__['cmd.run'](cmd)
elif 'Suse' in __grains__['os_family']:
cmd = 'tail -n 1 /etc/adjtime'
return __salt__['cmd.run'](cmd)
elif 'Debian' in __grains__['os_family']:
#Original way to look up hwclock on Debian-based systems
cmd = 'grep "UTC=" /etc/default/rcS | grep -vE "^#"'
out = __salt__['cmd.run'](
cmd, ignore_retcode=True, python_shell=True).split('=')
if len(out) > 1:
if out[1] == 'yes':
return 'UTC'
else:
return 'localtime'
else:
#Since Wheezy
cmd = 'tail -n 1 /etc/adjtime'
return __salt__['cmd.run'](cmd)
elif 'Gentoo' in __grains__['os_family']:
cmd = 'grep "^clock=" /etc/conf.d/hwclock | grep -vE "^#"'
out = __salt__['cmd.run'](cmd, python_shell=True).split('=')
return out[1].replace('"', '')
elif 'Solaris' in __grains__['os_family']:
if os.path.isfile('/etc/rtc_config'):
with salt.utils.fopen('/etc/rtc_config', 'r') as fp_:
for line in fp_:
if line.startswith('zone_info=GMT'):
return 'UTC'
return 'localtime'
else:
return 'UTC'
else:
os_family = __grains__['os_family']
for family in ('RedHat', 'Suse'):
if family in os_family:
cmd = ['tail', '-n', '1', '/etc/adjtime']
return __salt__['cmd.run'](cmd, python_shell=False)
if 'Debian' in __grains__['os_family']:
# Original way to look up hwclock on Debian-based systems
try:
with salt.utils.fopen('/etc/default/rcS', 'r') as fp_:
for line in fp_:
if re.match(r'^\s*#', line):
continue
if 'UTC=' in line:
is_utc = line.rstrip('\n').split('=')[-1].lower()
if is_utc == 'yes':
return 'UTC'
else:
return 'localtime'
except IOError as exc:
pass
# Since Wheezy
cmd = ['tail', '-n', '1', '/etc/adjtime']
return __salt__['cmd.run'](cmd, python_shell=False)
elif 'Gentoo' in __grains__['os_family']:
offset_file = '/etc/conf.d/hwclock'
try:
with salt.utils.fopen(offset_file, 'r') as fp_:
for line in fp_:
if line.startswith('clock='):
line = line.rstrip('\n')
return line.split('=')[-1].strip('\'"')
raise CommandExecutionError(
'Offset information not found in {0}'.format(
offset_file
)
)
except IOError as exc:
raise CommandExecutionError(
'Problem reading offset file {0}: {1}'
.format(offset_file, exc.strerror)
)
elif 'Solaris' in __grains__['os_family']:
offset_file = '/etc/rtc_config'
try:
with salt.utils.fopen(offset_file, 'r') as fp_:
for line in fp_:
if line.startswith('zone_info=GMT'):
return 'UTC'
return 'localtime'
except IOError as exc:
if exc.errno == errno.ENOENT:
# offset file does not exist
return 'UTC'
raise CommandExecutionError(
'Problem reading offset file {0}: {1}'
.format(offset_file, exc.strerror)
)
def set_hwclock(clock):
@ -267,33 +350,31 @@ def set_hwclock(clock):
timezone = get_zone()
if 'Solaris' in __grains__['os_family']:
if clock.lower() not in ('localtime', 'utc'):
raise SaltInvocationError(
'localtime and UTC are the only permitted values'
)
if 'sparc' in __grains__['cpuarch']:
return 'UTC is the only choice for SPARC architecture'
if clock == 'localtime':
cmd = 'rtc -z {0}'.format(timezone)
__salt__['cmd.run'](cmd)
return True
elif clock == 'UTC':
cmd = 'rtc -z GMT'
__salt__['cmd.run'](cmd)
return True
else:
zonepath = '/usr/share/zoneinfo/{0}'.format(timezone)
raise SaltInvocationError(
'UTC is the only choice for SPARC architecture'
)
cmd = ['rtc', '-z', 'GMT' if clock.lower() == 'utc' else timezone]
return __salt__['cmd.retcode'](cmd, python_shell=False) == 0
zonepath = '/usr/share/zoneinfo/{0}'.format(timezone)
if not os.path.exists(zonepath):
return 'Zone does not exist: {0}'.format(zonepath)
raise CommandExecutionError(
'Zone \'{0}\' does not exist'.format(zonepath)
)
if 'Solaris' not in __grains__['os_family']:
os.unlink('/etc/localtime')
os.symlink(zonepath, '/etc/localtime')
os.unlink('/etc/localtime')
os.symlink(zonepath, '/etc/localtime')
if 'Arch' in __grains__['os_family']:
if clock == 'localtime':
cmd = 'timezonectl set-local-rtc true'
__salt__['cmd.run'](cmd)
else:
cmd = 'timezonectl set-local-rtc false'
__salt__['cmd.run'](cmd)
cmd = ['timezonectl', 'set-local-rtc',
'true' if clock == 'localtime' else 'false']
return __salt__['cmd.retcode'](cmd, python_shell=False) == 0
elif 'RedHat' in __grains__['os_family']:
__salt__['file.sed'](
'/etc/sysconfig/clock', '^ZONE=.*', 'ZONE="{0}"'.format(timezone))

View File

@ -17,7 +17,7 @@ import copy
# Import salt libs
import salt.utils
from salt.ext.six import string_types
from salt.ext import six
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
@ -68,6 +68,27 @@ def _build_gecos(gecos_dict):
gecos_dict.get('homephone', ''))
def _update_gecos(name, key, value):
'''
Common code to change a user's GECOS information
'''
if value is None:
value = ''
elif not isinstance(value, six.string_types):
value = str(value)
pre_info = _get_gecos(name)
if not pre_info:
return False
if value == pre_info[key]:
return True
gecos_data = copy.deepcopy(pre_info)
gecos_data[key] = value
cmd = ['usermod', '-c', _build_gecos(gecos_data), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
return _get_gecos(name).get(key) == value
def add(name,
uid=None,
gid=None,
@ -99,47 +120,51 @@ def add(name,
if gid not in (None, ''):
cmd.extend(['-g', str(gid)])
elif groups is not None and name in groups:
defs_file = '/etc/login.defs'
if __grains__['kernel'] != 'OpenBSD':
try:
for line in salt.utils.fopen('/etc/login.defs'):
if 'USERGROUPS_ENAB' not in line[:15]:
continue
with salt.utils.fopen(defs_file) as fp_:
for line in fp_:
if 'USERGROUPS_ENAB' not in line[:15]:
continue
if 'yes' in line:
cmd.extend([
'-g', str(__salt__['file.group_to_gid'](name))
])
if 'yes' in line:
cmd.extend([
'-g', str(__salt__['file.group_to_gid'](name))
])
# We found what we wanted, let's break out of the loop
break
# We found what we wanted, let's break out of the loop
break
except OSError:
log.debug('Error reading /etc/login.defs', exc_info=True)
log.debug(
'Error reading ' + defs_file,
exc_info_on_loglevel=logging.DEBUG
)
else:
usermgmt_file = '/etc/usermgmt.conf'
try:
for line in salt.utils.fopen('/etc/usermgmt.conf'):
if 'group' not in line[:5]:
continue
with salt.utils.fopen(usermgmt_file) as fp_:
for line in fp_:
if 'group' not in line[:5]:
continue
for val in line.split(" "):
cmd.extend([
'-g', str(val[1])
])
for val in line.split(' '):
cmd.extend([
'-g', str(val[1])
])
# We found what we wanted, let's break out of the loop
break
# We found what we wanted, let's break out of the loop
break
except OSError:
# /etc/usermgmt.conf not present: defaults will be used
pass
if isinstance(createhome, bool):
if salt.utils.is_true(createhome):
if createhome:
cmd.append('-m')
elif (__grains__['kernel'] != 'NetBSD'
and __grains__['kernel'] != 'OpenBSD'):
cmd.append('-M')
else:
log.error('Value passes to ``createhome`` must be a boolean')
return False
if home is not None:
cmd.extend(['-d', home])
@ -220,7 +245,7 @@ def delete(name, remove=False, force=False):
if RETCODE_12_ERROR_REGEX.match(ret['stderr']) is not None:
# We've hit the bug, let's log it and not fail
log.debug(
'While the userdel exited with code 12, this is a know bug on '
'While the userdel exited with code 12, this is a known bug on '
'debian based distributions. See http://goo.gl/HH3FzT'
)
return True
@ -263,10 +288,7 @@ def chuid(name, uid):
return True
cmd = ['usermod', '-u', '{0}'.format(uid), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['uid'] != pre_info['uid']:
return post_info['uid'] == uid
return False
return info(name).get('uid') == uid
def chgid(name, gid):
@ -284,10 +306,7 @@ def chgid(name, gid):
return True
cmd = ['usermod', '-g', '{0}'.format(gid), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['gid'] != pre_info['gid']:
return post_info['gid'] == gid
return False
return info(name).get('gid') == gid
def chshell(name, shell):
@ -305,10 +324,7 @@ def chshell(name, shell):
return True
cmd = ['usermod', '-s', shell, name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['shell'] != pre_info['shell']:
return post_info['shell'] == shell
return False
return info(name).get('shell') == shell
def chhome(name, home, persist=False):
@ -325,58 +341,64 @@ def chhome(name, home, persist=False):
pre_info = info(name)
if home == pre_info['home']:
return True
cmd = 'usermod -d {0} '.format(home)
cmd = ['usermod', '-d', '{0}'.format(home)]
if persist and __grains__['kernel'] != 'OpenBSD':
cmd += ' -m '
cmd += name
cmd.append('-m')
cmd.append(name)
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['home'] != pre_info['home']:
return post_info['home'] == home
return False
return info(name).get('home') == home
def chgroups(name, groups, append=False):
'''
Change the groups this user belongs to, add append to append the specified
groups
Change the groups to which this user belongs
CLI Example:
name
User to modify
groups
Groups to set for the user
append : False
If ``True``, append the specified group(s). Otherwise, this function
will replace the user's groups with the specified group(s).
CLI Examples:
.. code-block:: bash
salt '*' user.chgroups foo wheel,root True
salt '*' user.chgroups foo wheel,root
salt '*' user.chgroups foo wheel,root append=True
'''
if isinstance(groups, string_types):
if isinstance(groups, six.string_types):
groups = groups.split(',')
ugrps = set(list_groups(name))
if ugrps == set(groups):
return True
cmd = 'usermod '
cmd = ['usermod']
if __grains__['kernel'] != 'OpenBSD':
if append:
cmd += '-a '
cmd.append('-a')
else:
if append:
cmd += '-G '
cmd.append('-G')
else:
cmd += '-S '
cmd.append('-S')
if __grains__['kernel'] != 'OpenBSD':
cmd += '-G '
cmd += '"{0}" {1}'.format(','.join(groups), name)
cmdret = __salt__['cmd.run_all'](cmd, python_shell=False)
ret = not cmdret['retcode']
cmd.append('-G')
cmd.extend([','.join(groups), name])
result = __salt__['cmd.run_all'](cmd, python_shell=False)
# try to fallback on gpasswd to add user to localgroups
# for old lib-pamldap support
if __grains__['kernel'] != 'OpenBSD':
if not ret and ('not found in' in cmdret['stderr']):
if result['retcode'] != 0 and 'not found in' in result['stderr']:
ret = True
for group in groups:
cmd = 'gpasswd -a {0} {1}'.format(name, group)
cmdret = __salt__['cmd.run_all'](cmd, python_shell=False)
if cmdret['retcode']:
cmd = ['gpasswd', '-a', '{0}'.format(name), '{1}'.format(group)]
if __salt__['cmd.retcode'](cmd, python_shell=False) != 0:
ret = False
return ret
return ret
return result['retcode'] == 0
def chfullname(name, fullname):
@ -389,24 +411,7 @@ def chfullname(name, fullname):
salt '*' user.chfullname foo "Foo Bar"
'''
if fullname is None:
fullname = ''
else:
fullname = str(fullname)
pre_info = _get_gecos(name)
if not pre_info:
return False
if fullname == pre_info['fullname']:
return True
gecos_field = copy.deepcopy(pre_info)
gecos_field['fullname'] = fullname
cmd = ['usermod', '-c', _build_gecos(gecos_field), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['fullname'] != pre_info['fullname']:
return post_info['fullname'] == fullname
return False
return _update_gecos(name, 'fullname', fullname)
def chroomnumber(name, roomnumber):
@ -419,24 +424,7 @@ def chroomnumber(name, roomnumber):
salt '*' user.chroomnumber foo 123
'''
if roomnumber is None:
roomnumber = ''
else:
roomnumber = str(roomnumber)
pre_info = _get_gecos(name)
if not pre_info:
return False
if roomnumber == pre_info['roomnumber']:
return True
gecos_field = copy.deepcopy(pre_info)
gecos_field['roomnumber'] = roomnumber
cmd = ['usermod', '-c', _build_gecos(gecos_field), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['roomnumber'] != pre_info['roomnumber']:
return post_info['roomnumber'] == roomnumber
return False
return _update_gecos(name, 'roomnumber', roomnumber)
def chworkphone(name, workphone):
@ -447,26 +435,9 @@ def chworkphone(name, workphone):
.. code-block:: bash
salt '*' user.chworkphone foo "7735550123"
salt '*' user.chworkphone foo 7735550123
'''
if workphone is None:
workphone = ''
else:
workphone = str(workphone)
pre_info = _get_gecos(name)
if not pre_info:
return False
if workphone == pre_info['workphone']:
return True
gecos_field = copy.deepcopy(pre_info)
gecos_field['workphone'] = workphone
cmd = ['usermod', '-c', _build_gecos(gecos_field), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['workphone'] != pre_info['workphone']:
return post_info['workphone'] == workphone
return False
return _update_gecos(name, 'workphone', workphone)
def chhomephone(name, homephone):
@ -477,32 +448,18 @@ def chhomephone(name, homephone):
.. code-block:: bash
salt '*' user.chhomephone foo "7735551234"
salt '*' user.chhomephone foo 7735551234
'''
if homephone is None:
homephone = ''
else:
homephone = str(homephone)
pre_info = _get_gecos(name)
if not pre_info:
return False
if homephone == pre_info['homephone']:
return True
gecos_field = copy.deepcopy(pre_info)
gecos_field['homephone'] = homephone
cmd = ['usermod', '-c', _build_gecos(gecos_field), name]
__salt__['cmd.run'](cmd, python_shell=False)
post_info = info(name)
if post_info['homephone'] != pre_info['homephone']:
return post_info['homephone'] == homephone
return False
return _update_gecos(name, 'homephone', homephone)
def chloginclass(name, loginclass):
'''
Change the default login class of the user
.. note::
This function only applies to OpenBSD systems.
CLI Example:
.. code-block:: bash
@ -511,15 +468,11 @@ def chloginclass(name, loginclass):
'''
if __grains__['kernel'] != 'OpenBSD':
return False
pre_info = get_loginclass(name)
if loginclass == pre_info['loginclass']:
if loginclass == get_loginclass(name):
return True
cmd = 'usermod -L {0} {1}'.format(loginclass, name)
__salt__['cmd.run'](cmd)
post_info = get_loginclass(name)
if post_info['loginclass'] != pre_info['loginclass']:
return post_info['loginclass'] == loginclass
return False
cmd = ['usermod', '-L', '{0}'.format(loginclass), '{0}'.format(name)]
__salt__['cmd.run'](cmd, python_shell=False)
return get_loginclass(name) == loginclass
def info(name):
@ -544,6 +497,9 @@ def get_loginclass(name):
'''
Get the login class of the user
.. note::
This function only applies to OpenBSD systems.
CLI Example:
.. code-block:: bash
@ -552,15 +508,19 @@ def get_loginclass(name):
'''
if __grains__['kernel'] != 'OpenBSD':
return False
userinfo = __salt__['cmd.run_stdout']('userinfo {0}'.format(name),
output_loglevel='debug')
userinfo = __salt__['cmd.run_stdout'](
['userinfo', name],
python_shell=False)
for line in userinfo.splitlines():
if line.startswith("class"):
loginclass = line.split()
if len(loginclass) == 2:
return {'loginclass': loginclass[1]}
if line.startswith('class'):
try:
ret = line.split(None, 1)[1]
break
except ValueError:
continue
else:
return {'loginclass': '""'}
ret = ''
return ret
def _format_info(data):
@ -624,13 +584,12 @@ def rename(name, new_name):
'''
current_info = info(name)
if not current_info:
raise CommandExecutionError('User {0!r} does not exist'.format(name))
raise CommandExecutionError('User \'{0}\' does not exist'.format(name))
new_info = info(new_name)
if new_info:
raise CommandExecutionError('User {0!r} already exists'.format(new_name))
cmd = 'usermod -l {0} {1}'.format(new_name, name)
__salt__['cmd.run'](cmd)
post_info = info(new_name)
if post_info['name'] != current_info['name']:
return post_info['name'] == new_name
return False
raise CommandExecutionError(
'User \'{0}\' already exists'.format(new_name)
)
cmd = ['usermod', '-l', '{0}'.format(new_name), '{0}'.format(name)]
__salt__['cmd.run'](cmd, python_shell=False)
return info(name).get('name') == new_name

View File

@ -152,11 +152,11 @@ def add(path, index=0):
# Add it to the Path
sysPath.insert(index, path)
regedit = __salt__['reg.set_key'](
regedit = __salt__['reg.set_value'](
'HKEY_LOCAL_MACHINE',
'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
'PATH',
';'.join(sysPath),
'PATH',
'REG_EXPAND_SZ'
)
@ -194,11 +194,11 @@ def remove(path):
except ValueError:
return True
regedit = __salt__['reg.set_key'](
regedit = __salt__['reg.set_value'](
'HKEY_LOCAL_MACHINE',
'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
'PATH',
';'.join(sysPath),
'PATH',
'REG_EXPAND_SZ'
)
if regedit:

View File

@ -36,6 +36,7 @@ import shlex
from salt.exceptions import CommandExecutionError, SaltRenderError
import salt.utils
import salt.syspaths
from salt.exceptions import MinionError
log = logging.getLogger(__name__)
@ -677,7 +678,10 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
if installer.startswith('salt:'):
if __salt__['cp.hash_file'](installer, saltenv) != \
__salt__['cp.hash_file'](cached_pkg):
cached_pkg = __salt__['cp.cache_file'](installer, saltenv)
try:
cached_pkg = __salt__['cp.cache_file'](installer, saltenv)
except MinionError as exc:
return '{0}: {1}'.format(exc, installer)
# Check if the installer was cached successfully
if not cached_pkg:

View File

@ -30,7 +30,10 @@ log = logging.getLogger(__name__)
try:
import pywintypes
import wmi
import pythoncom
import win32api
import win32con
import win32net
import win32netcon
import win32profile
@ -802,15 +805,96 @@ def rename(name, new_name):
salt '*' user.rename jsnuffy jshmoe
'''
# Load information for the current name
current_info = info(name)
if not current_info:
raise CommandExecutionError('User {0!r} does not exist'.format(name))
# Look for an existing user with the new name
new_info = info(new_name)
if new_info:
raise CommandExecutionError('User {0!r} already exists'.format(new_name))
cmd = 'wmic useraccount where name="{0}" rename {1}'.format(name, new_name)
__salt__['cmd.run'](cmd)
# Rename the user account
# Connect to WMI
pythoncom.CoInitialize()
c = wmi.WMI(find_classes=0)
# Get the user object
try:
user = c.Win32_UserAccount(Name=name)[0]
except IndexError:
raise CommandExecutionError('User {0!r} does not exist'.format(name))
# Rename the user
result = user.Rename(new_name)[0]
# Check the result (0 means success)
if not result == 0:
# Define Error Dict
error_dict = {0: 'Success',
1: 'Instance not found',
2: 'Instance required',
3: 'Invalid parameter',
4: 'User not found',
5: 'Domain not found',
6: 'Operation is allowed only on the primary domain controller of the domain',
7: 'Operation is not allowed on the last administrative account',
8: 'Operation is not allowed on specified special groups: user, admin, local, or guest',
9: 'Other API error',
10: 'Internal error'}
raise CommandExecutionError('There was an error renaming {0!r} to {1!r}. Error: {2}'.format(name, new_name, error_dict[result]))
# Load information for the new name
post_info = info(new_name)
# Verify that the name has changed
if post_info['name'] != current_info['name']:
return post_info['name'] == new_name
return False
def current(sam=False):
'''
Get the username that salt-minion is running under. If salt-minion is
running as a service it should return the Local System account. If salt is
running from a command prompt it should return the username that started the
command prompt.
.. versionadded:: 2015.5.6
:param bool sam:
False returns just the username without any domain notation. True
returns the domain with the username in the SAM format. Ie:
``domain\\username``
:return:
Returns False if the username cannot be returned. Otherwise returns the
username.
:rtype: bool str
CLI Example:
.. code-block:: bash
salt '*' user.current
'''
try:
if sam:
user_name = win32api.GetUserNameEx(win32con.NameSamCompatible)
else:
user_name = win32api.GetUserName()
except pywintypes.error as exc:
(number, context, message) = exc
log.error('Failed to get current user')
log.error('nbr: {0}'.format(number))
log.error('ctx: {0}'.format(context))
log.error('msg: {0}'.format(message))
return False
if not user_name:
return False
return user_name

View File

@ -764,13 +764,13 @@ def check_db(*names, **kwargs):
__context__['pkg._avail'] = avail
ret = {}
repoquery_cmd = repoquery_base + ' {0}'.format(" ".join(names))
provides = sorted(
set(x.name for x in _repoquery_pkginfo(repoquery_cmd))
)
for name in names:
ret.setdefault(name, {})['found'] = name in avail
if not ret[name]['found']:
repoquery_cmd = repoquery_base + ' {0}'.format(name)
provides = sorted(
set(x.name for x in _repoquery_pkginfo(repoquery_cmd))
)
if name in provides:
# Package was not in avail but was found by the repoquery_cmd
ret[name]['found'] = True

View File

@ -131,24 +131,23 @@ The token may be sent in one of two ways:
For example, using curl:
.. code-block:: bash
.. code-block:: bash
curl -sSk https://localhost:8000/login \
-H 'Accept: application/x-yaml' \
-d username=saltdev \
-d password=saltdev \
curl -sSk https://localhost:8000/login \\
-H 'Accept: application/x-yaml' \\
-d username=saltdev \\
-d password=saltdev \\
-d eauth=auto
Copy the ``token`` value from the output and include it in subsequent
requests:
Copy the ``token`` value from the output and include it in subsequent requests:
.. code-block:: bash
.. code-block:: bash
curl -sSk https://localhost:8000 \
-H 'Accept: application/x-yaml' \
-H 'X-Auth-Token: 697adbdc8fe971d09ae4c2a3add7248859c87079'\
-d client=local \
-d tgt='*' \
curl -sSk https://localhost:8000 \\
-H 'Accept: application/x-yaml' \\
-H 'X-Auth-Token: 697adbdc8fe971d09ae4c2a3add7248859c87079'\\
-d client=local \\
-d tgt='*' \\
-d fun=test.ping
* Sent via a cookie. This option is a convenience for HTTP clients that
@ -159,19 +158,19 @@ The token may be sent in one of two ways:
.. code-block:: bash
# Write the cookie file:
curl -sSk https://localhost:8000/login \
-c ~/cookies.txt \
-H 'Accept: application/x-yaml' \
-d username=saltdev \
-d password=saltdev \
curl -sSk https://localhost:8000/login \\
-c ~/cookies.txt \\
-H 'Accept: application/x-yaml' \\
-d username=saltdev \\
-d password=saltdev \\
-d eauth=auto
# Read the cookie file:
curl -sSk https://localhost:8000 \
-b ~/cookies.txt \
-H 'Accept: application/x-yaml' \
-d client=local \
-d tgt='*' \
curl -sSk https://localhost:8000 \\
-b ~/cookies.txt \\
-H 'Accept: application/x-yaml' \\
-d client=local \\
-d tgt='*' \\
-d fun=test.ping
.. seealso:: You can bypass the session handling via the :py:class:`Run` URL.

View File

@ -128,7 +128,7 @@ def ext_pillar(minion_id,
prefix='',
service_url=None,
kms_keyid=None,
s3_cache_expire=30, # cache for 30 seconds
s3_cache_expire=30, # cache for 30 seconds
s3_sync_on_update=True): # sync cache on update rather than jit
'''
@ -382,6 +382,8 @@ def _get_file_from_s3(creds, metadata, saltenv, bucket, path,
cached_md5 = salt.utils.get_hash(cached_file_path, 'md5')
log.debug("Cached file: path={0}, md5={1}, etag={2}".format(cached_file_path, cached_md5, file_md5))
# hashes match we have a cache hit
log.debug("Cached file: path={0}, md5={1}, etag={2}".format(cached_file_path, cached_md5, file_md5))
if cached_md5 == file_md5:

View File

@ -18,6 +18,7 @@ import bisect
import salt.payload
import salt.utils
import salt.utils.jid
import salt.exceptions
# Import 3rd-party libs
import salt.ext.six as six
@ -79,12 +80,16 @@ def _walk_through(job_dir):
#TODO: add to returner docs-- this is a new one
def prep_jid(nocache=False, passed_jid=None):
def prep_jid(nocache=False, passed_jid=None, recurse_count=0):
'''
Return a job id and prepare the job id directory
This is the function responsible for making sure jids don't collide (unless its passed a jid)
So do what you have to do to make sure that stays the case
'''
if recurse_count >= 5:
err = 'prep_jid could not store a jid after {0} tries.'.format(recurse_count)
log.error(err)
raise salt.exceptions.SaltCacheError(err)
if passed_jid is None: # this can be a None of an empty string
jid = salt.utils.jid.gen_jid()
else:
@ -97,18 +102,25 @@ def prep_jid(nocache=False, passed_jid=None):
try:
os.makedirs(jid_dir_)
except OSError:
# TODO: some sort of sleep or something? Spinning is generally bad practice
time.sleep(0.1)
if passed_jid is None:
recurse_count += recurse_count
return prep_jid(nocache=nocache)
with salt.utils.fopen(os.path.join(jid_dir_, 'jid'), 'wb+') as fn_:
if six.PY2:
fn_.write(jid)
else:
fn_.write(bytes(jid, 'utf-8'))
if nocache:
with salt.utils.fopen(os.path.join(jid_dir_, 'nocache'), 'wb+') as fn_:
fn_.write('')
try:
with salt.utils.fopen(os.path.join(jid_dir_, 'jid'), 'wb+') as fn_:
if six.PY2:
fn_.write(jid)
else:
fn_.write(bytes(jid, 'utf-8'))
if nocache:
with salt.utils.fopen(os.path.join(jid_dir_, 'nocache'), 'wb+') as fn_:
fn_.write('')
except IOError:
log.warn('Could not write out jid file for job {0}. Retrying.'.format(jid))
time.sleep(0.1)
recurse_count += recurse_count
return prep_jid(passed_jid=jid, nocache=nocache)
return jid

View File

@ -816,6 +816,16 @@ def symlink(
user = __opts__['user']
if salt.utils.is_windows():
# Make sure the user exists in Windows
# Salt default is 'root'
if not __salt__['user.info'](user):
# User not found, use the account salt is running under
# If username not found, use System
user = __salt__['user.current']()
if not user:
user = 'SYSTEM'
if group is not None:
log.warning(
'The group argument for {0} has been ignored as this '

View File

@ -81,6 +81,9 @@ def user_exists(name, password=None, htpasswd_file=None, options='',
ret['comment'] = useradd_ret['stderr']
return ret
ret['result'] = True
if __opts__['test'] and ret['changes']:
ret['result'] = None
else:
ret['result'] = True
ret['comment'] = 'User already known'
return ret

View File

@ -23,6 +23,9 @@ A state module to manage LVMs
'''
from __future__ import absolute_import
# Import python libs
import os
# Import salt libs
import salt.utils
@ -122,16 +125,17 @@ def vg_present(name, devices=None, **kwargs):
if __salt__['lvm.vgdisplay'](name):
ret['comment'] = 'Volume Group {0} already present'.format(name)
for device in devices.split(','):
pvs = __salt__['lvm.pvdisplay'](device)
if pvs and pvs.get(device, None):
if pvs[device]['Volume Group Name'] == name:
realdev = os.path.realpath(device)
pvs = __salt__['lvm.pvdisplay'](realdev)
if pvs and pvs.get(realdev, None):
if pvs[realdev]['Volume Group Name'] == name:
ret['comment'] = '{0}\n{1}'.format(
ret['comment'],
'{0} is part of Volume Group'.format(device))
elif pvs[device]['Volume Group Name'] == '#orphans_lvm2':
__salt__['lvm.vgextend'](name, device)
pvs = __salt__['lvm.pvdisplay'](device)
if pvs[device]['Volume Group Name'] == name:
elif pvs[realdev]['Volume Group Name'] == '#orphans_lvm2':
__salt__['lvm.vgextend'](name, realdev)
pvs = __salt__['lvm.pvdisplay'](realdev)
if pvs[realdev]['Volume Group Name'] == name:
ret['changes'].update(
{device: 'added to {0}'.format(name)})
else:
@ -143,7 +147,7 @@ def vg_present(name, devices=None, **kwargs):
ret['comment'] = '{0}\n{1}'.format(
ret['comment'],
'{0} is part of {1}'.format(
device, pvs[device]['Volume Group Name']))
device, pvs[realdev]['Volume Group Name']))
ret['result'] = False
else:
ret['comment'] = '{0}\n{1}'.format(

View File

@ -34,6 +34,28 @@ these states. Here is some example SLS:
- name: logstash
- refresh: True
.. code-block:: yaml
base:
pkgrepo.managed:
- humanname: deb-multimedia
- name: deb http://www.deb-multimedia.org stable main
- file: /etc/apt/sources.list.d/deb-multimedia.list
- key_url: salt://deb-multimedia/files/marillat.pub
.. code-block:: yaml
base:
pkgrepo.managed:
- humanname: Google Chrome
- name: deb http://dl.google.com/linux/chrome/deb/ stable main
- dist: stable
- file: /etc/apt/sources.list.d/chrome-browser.list
- require_in:
- pkg: google-chrome-stable
- gpgcheck: 1
- key_url: https://dl-ssl.google.com/linux/linux_signing_key.pub
.. code-block:: yaml
base:
@ -183,8 +205,11 @@ def managed(name, **kwargs):
key_url
URL to retrieve a GPG key from. Allows the usage of ``http://``,
``https://`` as well as ``salt://``. Use either ``keyid``/``keyserver``
or ``key_url``, but not both.
``https://`` as well as ``salt://``.
.. note::
Use either ``keyid``/``keyserver`` or ``key_url``, but not both.
consolidate
If set to true, this will consolidate all sources definitions to

View File

@ -170,10 +170,9 @@ def _changes(name,
change['homephone'] = homephone
# OpenBSD/FreeBSD login class
if __grains__['kernel'] in ('OpenBSD', 'FreeBSD'):
if not loginclass:
loginclass = '""'
if __salt__['user.get_loginclass'](name)['loginclass'] != loginclass:
change['loginclass'] = loginclass
if loginclass:
if __salt__['user.get_loginclass'](name) != loginclass:
change['loginclass'] = loginclass
return change
@ -434,6 +433,8 @@ def present(name,
ret['comment'] = ('The following user attributes are set to be '
'changed:\n')
for key, val in six.iteritems(changes):
if key == 'password':
val = 'XXX-REDACTED-XXX'
ret['comment'] += '{0}: {1}\n'.format(key, val)
return ret
# The user is present
@ -506,8 +507,8 @@ def present(name,
for key in spost:
if lshad[key] != spost[key]:
ret['changes'][key] = spost[key]
if __grains__['kernel'] in ('OpenBSD', 'FreeBSD') and lcpost['loginclass'] != lcpre['loginclass']:
ret['changes']['loginclass'] = lcpost['loginclass']
if __grains__['kernel'] in ('OpenBSD', 'FreeBSD') and lcpost != lcpre:
ret['changes']['loginclass'] = lcpost
if ret['changes']:
ret['comment'] = 'Updated user {0}'.format(name)
changes = _changes(name,
@ -594,9 +595,9 @@ def present(name,
if spost['passwd'] != password:
ret['comment'] = 'User {0} created but failed to set' \
' password to' \
' {1}'.format(name, password)
' {1}'.format(name, 'XXX-REDACTED-XXX')
ret['result'] = False
ret['changes']['password'] = password
ret['changes']['password'] = 'XXX-REDACTED-XXX'
if date:
__salt__['shadow.set_date'](name, date)
spost = __salt__['shadow.info'](name)
@ -652,7 +653,12 @@ def present(name,
ret['result'] = False
ret['changes']['expire'] = expire
elif salt.utils.is_windows() and password and not empty_password:
ret['changes']['passwd'] = password
if not __salt__['user.setpassword'](name, password):
ret['comment'] = 'User {0} created but failed to set' \
' password to' \
' {1}'.format(name, 'XXX-REDACTED-XXX')
ret['result'] = False
ret['changes']['passwd'] = 'XXX-REDACTED-XXX'
else:
ret['comment'] = 'Failed to create new user {0}'.format(name)
ret['result'] = False

View File

@ -18,6 +18,7 @@ DEVICE={{name}}
{%endif%}{% if ipv6gateway %}IPV6_DEFAULTGW={{ipv6gateway}}
{%endif%}{%endif%}{% if srcaddr %}SRCADDR={{srcaddr}}
{%endif%}{% if peerdns %}PEERDNS={{peerdns}}
{%endif%}{% if peerntp %}PEERNTP={{peerntp}}
{%endif%}{% if bridge %}BRIDGE={{bridge}}
{%endif%}{% if stp %}STP={{stp}}
{%endif%}{% if delay or delay == 0 %}DELAY={{delay}}

View File

@ -22,6 +22,7 @@
{%endif%}{% if ipv6gateway %}IPV6_DEFAULTGW="{{ipv6gateway}}"
{%endif%}{%endif%}{% if srcaddr %}SRCADDR="{{srcaddr}}"
{%endif%}{% if peerdns %}PEERDNS="{{peerdns}}"
{%endif%}{% if peerntp %}PEERNTP="{{peerntp}}"
{%endif%}{% if defroute %}DEFROUTE="{{defroute}}"
{%endif%}{% if bridge %}BRIDGE="{{bridge}}"
{%endif%}{% if stp %}STP="{{stp}}"

View File

@ -27,6 +27,7 @@ DEVICE="{{name}}"
{%endif%}{%endif%}{% if srcaddr %}SRCADDR="{{srcaddr}}"
{%endif%}{% if peerdns %}PEERDNS="{{peerdns}}"
{%endif%}{% if peerroutes %}PEERROUTES="{{peerroutes}}"
{%endif%}{% if peerntp %}PEERNTP="{{peerntp}}"
{%endif%}{% if defroute %}DEFROUTE="{{defroute}}"
{%endif%}{% if bridge %}BRIDGE="{{bridge}}"
{%endif%}{% if stp %}STP="{{stp}}"

View File

@ -3,6 +3,7 @@
from __future__ import absolute_import
# Import Python libs
import errno
import os
import shutil
import subprocess
@ -10,7 +11,7 @@ import subprocess
# Import salt libs
import salt.utils
import salt.modules.selinux
from salt.exceptions import CommandExecutionError
from salt.exceptions import CommandExecutionError, MinionError
def recursive_copy(source, dest):
@ -87,3 +88,19 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
os.remove(tgt)
except Exception:
pass
def rename(src, dst):
'''
On Windows, os.rename() will fail with a WindowsError exception if a file
exists at the destination path. This function removes the destination path
first before attempting the os.rename().
'''
try:
os.remove(dst)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise MinionError(
'Error: Unable to remove {0}: {1}'.format(dst, exc.strerror)
)
os.rename(src, dst)

View File

@ -13,6 +13,7 @@ import time
import pprint
from salt.ext.six.moves import range
import salt.ext.six as six
import salt.utils
try:
import requests
HAS_REQUESTS = True # pylint: disable=W0612
@ -68,6 +69,11 @@ def get_iam_region(version='latest', url='http://169.254.169.254',
'''
Gets instance identity document and returns region
'''
salt.utils.warn_until(
'Carbon',
'''The \'get_iam_region\' function has been deprecated in favor of
\'salt.utils.aws.get_region_from_metadata\'. Please update your code
to reflect this.''')
instance_identity_url = '{0}/{1}/dynamic/instance-identity/document'.format(url, version)
region = None
@ -85,6 +91,10 @@ def get_iam_metadata(version='latest', url='http://169.254.169.254',
'''
Grabs the first IAM role from this instances metadata if it exists.
'''
salt.utils.warn_until(
'Carbon',
'''The \'get_iam_metadata\' function has been deprecated in favor of
\'salt.utils.aws.creds\'. Please update your code to reflect this.''')
iam_url = '{0}/{1}/meta-data/iam/security-credentials/'.format(url, version)
roles = _retry_get_url(iam_url, num_retries, timeout).splitlines()

View File

@ -31,6 +31,7 @@ def __random_name(size=6):
# Create the cloud instance name to be used throughout the tests
INSTANCE_NAME = __random_name()
PROVIDER_NAME = 'joyent'
class JoyentTest(integration.ShellCase):
@ -46,57 +47,57 @@ class JoyentTest(integration.ShellCase):
super(JoyentTest, self).setUp()
# check if appropriate cloud provider and profile files are present
profile_str = 'joyent-config:'
provider = 'joyent'
profile_str = 'joyent-config'
providers = self.run_cloud('--list-providers')
if profile_str not in providers:
if profile_str + ':' not in providers:
self.skipTest(
'Configuration file for {0} was not found. Check {0}.conf files '
'in tests/integration/files/conf/cloud.*.d/ to run these tests.'
.format(provider)
.format(PROVIDER_NAME)
)
# check if user, password, private_key, and keyname are present
path = os.path.join(integration.FILES,
'conf',
'cloud.providers.d',
provider + '.conf')
config = cloud_providers_config(path)
config = cloud_providers_config(
os.path.join(
integration.FILES,
'conf',
'cloud.providers.d',
PROVIDER_NAME + '.conf'
)
)
user = config['joyent-config'][provider]['user']
password = config['joyent-config'][provider]['password']
private_key = config['joyent-config'][provider]['private_key']
keyname = config['joyent-config'][provider]['keyname']
user = config[profile_str][PROVIDER_NAME]['user']
password = config[profile_str][PROVIDER_NAME]['password']
private_key = config[profile_str][PROVIDER_NAME]['private_key']
keyname = config[profile_str][PROVIDER_NAME]['keyname']
if user == '' or password == '' or private_key == '' or keyname == '':
self.skipTest(
'A user name, password, private_key file path, and a key name '
'must be provided to run these tests. Check '
'tests/integration/files/conf/cloud.providers.d/{0}.conf'
.format(provider)
.format(PROVIDER_NAME)
)
def test_instance(self):
'''
Test creating and deleting instance on Joyent
'''
# create the instance
instance = self.run_cloud('-p joyent-test {0}'.format(INSTANCE_NAME))
ret_str = ' {0}'.format(INSTANCE_NAME)
# check if instance with salt installed returned
try:
self.assertIn(ret_str, instance)
self.assertIn(
INSTANCE_NAME,
[i.strip() for i in self.run_cloud('-p joyent-test {0}'.format(INSTANCE_NAME))]
)
except AssertionError:
self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME))
raise
# delete the instance
delete = self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME))
ret_str = ' True'
try:
self.assertIn(ret_str, delete)
self.assertIn(
INSTANCE_NAME + ':',
[i.strip() for i in self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME))]
)
except AssertionError:
raise

View File

@ -41,7 +41,7 @@ class LocalemodTestCase(TestCase):
Test for Get the current system locale
'''
with patch.dict(localemod.__grains__, {'os_family': ['Arch']}):
with patch.object(localemod, '_localectl_get', return_value=True):
with patch.object(localemod, '_locale_get', return_value=True):
self.assertTrue(localemod.get_locale())
with patch.dict(localemod.__grains__, {'os_family': ['Gentoo']}):
@ -59,16 +59,6 @@ class LocalemodTestCase(TestCase):
MagicMock(return_value='A')}):
self.assertEqual(localemod.get_locale(), '')
@patch('salt.utils.which', MagicMock(side_effect=['/usr/bin/localectl', None]))
def test_get_locale_debian(self):
with patch.dict(localemod.__grains__, {'os_family': ['Debian']}):
with patch.object(localemod, '_localectl_get', return_value=True):
self.assertTrue(localemod.get_locale())
with patch.dict(localemod.__salt__, {'cmd.run':
MagicMock(return_value='LC_ALL=C')}):
self.assertEqual(localemod.get_locale(), 'C')
def test_set_locale(self):
'''
Test for Sets the current system locale

View File

@ -22,8 +22,7 @@ ensure_in_syspath('../../')
import salt.utils
from salt.modules import timezone
import os
from salt.exceptions import CommandExecutionError
from salt.exceptions import SaltInvocationError
from salt.exceptions import CommandExecutionError, SaltInvocationError
# Globals
@ -53,22 +52,28 @@ class TimezoneTestCase(TestCase):
with patch('salt.utils.fopen', mock_open(read_data=file_data),
create=True) as mfile:
mfile.return_value.__iter__.return_value = file_data.splitlines()
with patch.dict(timezone.__grains__, {'os_family': 'Debian'}):
with patch.dict(timezone.__grains__, {'os_family': 'Debian',
'os': 'Debian'}):
self.assertEqual(timezone.get_zone(), '#\nA')
with patch.dict(timezone.__grains__, {'os_family': 'Gentoo'}):
with patch.dict(timezone.__grains__, {'os_family': 'Gentoo',
'os': 'Gentoo'}):
self.assertEqual(timezone.get_zone(), '')
with patch.dict(timezone.__grains__, {'os_family': 'FreeBSD'}):
with patch.object(os, 'readlink',
return_value='/usr/share/zoneinfo/'):
self.assertEqual(timezone.get_zone(), '')
with patch.dict(timezone.__grains__, {'os_family': 'FreeBSD',
'os': 'FreeBSD'}):
zone = 'America/Denver'
linkpath = '/usr/share/zoneinfo/' + zone
with patch.object(os, 'readlink', return_value=linkpath):
self.assertEqual(timezone.get_zone(), zone)
with patch.dict(timezone.__grains__, {'os_family': 'Solaris'}):
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value='A=B')}):
self.assertEqual(timezone.get_zone(), 'B')
with patch.dict(timezone.__grains__, {'os_family': 'Solaris',
'os': 'Solaris'}):
fl_data = 'TZ=Foo\n'
with patch('salt.utils.fopen',
mock_open(read_data=fl_data)) as mfile:
mfile.return_value.__iter__.return_value = [fl_data]
self.assertEqual(timezone.get_zone(), 'Foo')
def test_get_zonecode(self):
'''
@ -183,26 +188,24 @@ class TimezoneTestCase(TestCase):
self.assertEqual(timezone.get_hwclock(), 'A')
with patch.dict(timezone.__grains__, {'os_family': 'Debian'}):
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value='A=yes')}):
fl_data = 'UTC=yes\n'
with patch('salt.utils.fopen',
mock_open(read_data=fl_data)) as mfile:
mfile.return_value.__iter__.return_value = [fl_data]
self.assertEqual(timezone.get_hwclock(), 'UTC')
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value='A=no')}):
fl_data = 'UTC=no\n'
with patch('salt.utils.fopen',
mock_open(read_data=fl_data)) as mfile:
mfile.return_value.__iter__.return_value = [fl_data]
self.assertEqual(timezone.get_hwclock(), 'localtime')
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value='A')}):
self.assertEqual(timezone.get_hwclock(), 'A')
with patch.dict(timezone.__grains__, {'os_family': 'Gentoo'}):
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value='A=B')}):
self.assertEqual(timezone.get_hwclock(), 'B')
fl_data = 'clock=UTC\n'
with patch('salt.utils.fopen',
mock_open(read_data=fl_data)) as mfile:
mfile.return_value.__iter__.return_value = [fl_data]
self.assertEqual(timezone.get_hwclock(), 'UTC')
mock = MagicMock(return_value=True)
with patch.object(os.path, 'isfile', mock):
@ -238,31 +241,30 @@ class TimezoneTestCase(TestCase):
'''
Test to sets the hardware clock to be either UTC or localtime
'''
ret = ('UTC is the only choice for SPARC architecture')
ret1 = ('Zone does not exist: /usr/share/zoneinfo/America/Denver')
with patch.object(timezone, 'get_zone',
return_value='America/Denver'):
zone = 'America/Denver'
with patch.object(timezone, 'get_zone', return_value=zone):
with patch.dict(timezone.__grains__, {'os_family': 'Solaris',
'cpuarch': 'sparc'}):
self.assertEqual(timezone.set_hwclock('clock'), ret)
self.assertRaises(
SaltInvocationError,
timezone.set_hwclock,
'clock'
)
self.assertRaises(
SaltInvocationError,
timezone.set_hwclock,
'localtime'
)
with patch.dict(timezone.__salt__,
{'cmd.run':
MagicMock(return_value=None)}):
self.assertTrue(timezone.set_hwclock('localtime'))
self.assertTrue(timezone.set_hwclock('UTC'))
with patch.dict(timezone.__grains__, {'os_family': 'Sola'}):
with patch.dict(timezone.__grains__,
{'os_family': 'DoesNotMatter'}):
with patch.object(os.path, 'exists', return_value=False):
self.assertEqual(timezone.set_hwclock('clock'), ret1)
with patch.object(os.path, 'exists', return_value=True):
with patch.object(os, 'unlink', return_value=None):
with patch.object(os, 'symlink', return_value=None):
self.assertTrue(timezone.set_hwclock('clock'))
self.assertRaises(
CommandExecutionError,
timezone.set_hwclock,
'UTC'
)
if __name__ == '__main__':
from integration import run_tests

View File

@ -347,33 +347,6 @@ class UserAddTestCase(TestCase):
with patch.object(useradd, 'info', mock):
self.assertFalse(useradd.chhomephone('salt', 1))
# 'chloginclass' function tests: 1
def test_chloginclass(self):
'''
Test if the default login class of the user is changed
'''
with patch.dict(useradd.__grains__, {'kernel': 'Linux'}):
self.assertFalse(useradd.chloginclass('salt', 'staff'))
with patch.dict(useradd.__grains__, {'kernel': 'OpenBSD'}):
mock_login = MagicMock(return_value={'loginclass': 'staff'})
with patch.object(useradd, 'get_loginclass', mock_login):
self.assertTrue(useradd.chloginclass('salt', 'staff'))
mock = MagicMock(return_value=None)
with patch.dict(useradd.__salt__, {'cmd.run': mock}):
mock = MagicMock(side_effect=[{'loginclass': '""'},
{'loginclass': 'staff'}])
with patch.object(useradd, 'get_loginclass', mock):
self.assertTrue(useradd.chloginclass('salt', 'staff'))
mock_login = MagicMock(return_value={'loginclass': 'staff1'})
with patch.object(useradd, 'get_loginclass', mock_login):
mock = MagicMock(return_value=None)
with patch.dict(useradd.__salt__, {'cmd.run': mock}):
self.assertFalse(useradd.chloginclass('salt', 'staff'))
# 'info' function tests: 1
def test_info(self):
@ -394,27 +367,6 @@ class UserAddTestCase(TestCase):
with patch.object(useradd, 'list_groups', mock):
self.assertEqual(useradd.info('salt')['name'], '_TEST_GROUP')
# 'get_loginclass' function tests: 1
def test_get_loginclass(self):
'''
Test the login class of the user
'''
with patch.dict(useradd.__grains__, {'kernel': 'Linux'}):
self.assertFalse(useradd.get_loginclass('salt'))
with patch.dict(useradd.__grains__, {'kernel': 'OpenBSD'}):
mock = MagicMock(return_value='class staff')
with patch.dict(useradd.__salt__, {'cmd.run_stdout': mock}):
self.assertDictEqual(useradd.get_loginclass('salt'),
{'loginclass': 'staff'})
with patch.dict(useradd.__grains__, {'kernel': 'OpenBSD'}):
mock = MagicMock(return_value='class ')
with patch.dict(useradd.__salt__, {'cmd.run_stdout': mock}):
self.assertDictEqual(useradd.get_loginclass('salt'),
{'loginclass': '""'})
# 'list_groups' function tests: 1
@patch('salt.utils.get_group_list', MagicMock(return_value='Salt'))

View File

@ -69,7 +69,7 @@ class WinPathTestCase(TestCase):
'''
Test to Returns the system path
'''
mock = MagicMock(return_value='c:\\salt')
mock = MagicMock(return_value={'vdata': 'c:\\salt'})
with patch.dict(win_path.__salt__, {'reg.read_key': mock}):
self.assertListEqual(win_path.get_path(), ['c:\\salt'])
@ -85,12 +85,12 @@ class WinPathTestCase(TestCase):
'''
Test to add the directory to the SYSTEM path
'''
mock = MagicMock(return_value=['c:\\salt'])
with patch.object(win_path, 'get_path', mock):
mock = MagicMock(return_value=True)
with patch.dict(win_path.__salt__, {'reg.set_key': mock}):
mock = MagicMock(side_effect=[True, False])
with patch.object(win_path, 'rehash', mock):
mock_get = MagicMock(return_value=['c:\\salt'])
with patch.object(win_path, 'get_path', mock_get):
mock_set = MagicMock(return_value=True)
with patch.dict(win_path.__salt__, {'reg.set_value': mock_set}):
mock_rehash = MagicMock(side_effect=[True, False])
with patch.object(win_path, 'rehash', mock_rehash):
self.assertTrue(win_path.add("c:\\salt", 1))
self.assertFalse(win_path.add("c:\\salt", 1))
@ -99,14 +99,14 @@ class WinPathTestCase(TestCase):
'''
Test to remove the directory from the SYSTEM path
'''
mock = MagicMock(side_effect=[[1], ['c:\\salt'], ['c:\\salt']])
with patch.object(win_path, 'get_path', mock):
mock_get = MagicMock(side_effect=[[1], ['c:\\salt'], ['c:\\salt']])
with patch.object(win_path, 'get_path', mock_get):
self.assertTrue(win_path.remove("c:\\salt"))
mock = MagicMock(side_effect=[True, False])
with patch.dict(win_path.__salt__, {'reg.set_key': mock}):
mock = MagicMock(return_value="Salt")
with patch.object(win_path, 'rehash', mock):
mock_set = MagicMock(side_effect=[True, False])
with patch.dict(win_path.__salt__, {'reg.set_value': mock_set}):
mock_rehash = MagicMock(return_value="Salt")
with patch.object(win_path, 'rehash', mock_rehash):
self.assertEqual(win_path.remove("c:\\salt"), "Salt")
self.assertFalse(win_path.remove("c:\\salt"))