Merge branch 'develop' into issue36942

This commit is contained in:
Corvin Mcpherson 2017-08-17 16:18:46 -04:00
commit 78fd3f9b4d
211 changed files with 6955 additions and 2839 deletions

4
.github/stale.yml vendored
View file

@ -1,8 +1,8 @@
# Probot Stale configuration file
# Number of days of inactivity before an issue becomes stale
# 1130 is approximately 3 years and 1 month
daysUntilStale: 1130
# 1115 is approximately 3 years and 1 month
daysUntilStale: 1115
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7

View file

@ -1,4 +1,11 @@
{
"alwaysNotifyForPaths": [
{
"name": "ryan-lane",
"files": ["salt/**/*boto*.py"],
"skipTeamPrs": false
}
],
"skipTitle": "Merge forward",
"userBlacklist": ["cvrebert", "markusgattol", "olliewalsh"]
}

View file

@ -97,3 +97,14 @@
#
#delete_sshkeys: False
# Whether or not to include grains information in the /etc/salt/minion file
# which is generated when the minion is provisioned. For example...
# grains:
# salt-cloud:
# driver: ec2
# provider: my_ec2:ec2
# profile: micro_ec2
#
# Default: 'True'
#
#enable_cloud_grains: 'True'

View file

@ -245,8 +245,8 @@ on_saltstack = 'SALT_ON_SALTSTACK' in os.environ
project = 'Salt'
version = salt.version.__version__
latest_release = '2017.7.0' # latest release
previous_release = '2016.11.6' # latest release from previous branch
latest_release = '2017.7.1' # latest release
previous_release = '2016.11.7' # latest release from previous branch
previous_release_dir = '2016.11' # path on web server for previous branch
next_release = '' # next release
next_release_dir = '' # path on web server for next release branch

View file

@ -19,5 +19,4 @@ auth modules
pki
rest
sharedsecret
stormpath
yubico

View file

@ -1,6 +0,0 @@
===================
salt.auth.stormpath
===================
.. automodule:: salt.auth.stormpath
:members:

View file

@ -33,6 +33,10 @@ Output Options
Write the output to the specified file.
.. option:: --out-file-append, --output-file-append
Append the output to the specified file.
.. option:: --no-color
Disable all colored output
@ -46,3 +50,14 @@ Output Options
``green`` denotes success, ``red`` denotes failure, ``blue`` denotes
changes and success and ``yellow`` denotes a expected future change in configuration.
.. option:: --state-output=STATE_OUTPUT, --state_output=STATE_OUTPUT
Override the configured state_output value for minion
output. One of 'full', 'terse', 'mixed', 'changes' or
'filter'. Default: 'none'.
.. option:: --state-verbose=STATE_VERBOSE, --state_verbose=STATE_VERBOSE
Override the configured state_verbose value for minion
output. Set to True or False. Default: none.

View file

@ -81,7 +81,7 @@ Options
Pass in an external authentication medium to validate against. The
credentials will be prompted for. The options are `auto`,
`keystone`, `ldap`, `pam`, and `stormpath`. Can be used with the -T
`keystone`, `ldap`, and `pam`. Can be used with the -T
option.
.. option:: -T, --make-token

View file

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

View file

@ -97,6 +97,7 @@ execution modules
cytest
daemontools
data
datadog_api
ddns
deb_apache
deb_postgres
@ -399,7 +400,6 @@ execution modules
state
status
statuspage
stormpath
supervisord
suse_apache
svn

View file

@ -0,0 +1,6 @@
========================
salt.modules.datadog_api
========================
.. automodule:: salt.modules.datadog_api
:members:

View file

@ -1,6 +0,0 @@
======================
salt.modules.stormpath
======================
.. automodule:: salt.modules.stormpath
:members:

View file

@ -250,7 +250,6 @@ state modules
stateconf
status
statuspage
stormpath_account
supervisord
svn
sysctl

View file

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

View file

@ -135,19 +135,23 @@ A State Module must return a dict containing the following keys/values:
``test=True``, and changes would have been made if the state was not run in
test mode.
+--------------------+-----------+-----------+
| | live mode | test mode |
+====================+===========+===========+
| no changes | ``True`` | ``True`` |
+--------------------+-----------+-----------+
| successful changes | ``True`` | ``None`` |
+--------------------+-----------+-----------+
| failed changes | ``False`` | ``None`` |
+--------------------+-----------+-----------+
+--------------------+-----------+------------------------+
| | live mode | test mode |
+====================+===========+========================+
| no changes | ``True`` | ``True`` |
+--------------------+-----------+------------------------+
| successful changes | ``True`` | ``None`` |
+--------------------+-----------+------------------------+
| failed changes | ``False`` | ``False`` or ``None`` |
+--------------------+-----------+------------------------+
.. note::
Test mode does not predict if the changes will be successful or not.
Test mode does not predict if the changes will be successful or not,
and hence the result for pending changes is usually ``None``.
However, if a state is going to fail and this can be determined
in test mode without applying the change, ``False`` can be returned.
- **comment:** A string containing a summary of the result.

View file

@ -777,8 +777,6 @@ Stateconf
stderr
stdin
stdout
stormpath
Stormpath
str
strftime
subfolder

View file

@ -56,6 +56,24 @@ settings can be placed in the provider or profile:
sls_list:
- web
When salt cloud creates a new minon, it can automatically add grain information
to the minion configuration file identifying the sources originally used
to define it.
The generated grain information will appear similar to:
.. code-block:: yaml
grains:
salt-cloud:
driver: ec2
provider: my_ec2:ec2
profile: ec2-web
The generation of the salt-cloud grain can be surpressed by the
option ``enable_cloud_grains: 'False'`` in the cloud configuration file.
Cloud Configuration Syntax
==========================

View file

@ -119,6 +119,7 @@ Cloud Provider Specifics
Getting Started With Libvirt <libvirt>
Getting Started With Linode <linode>
Getting Started With LXC <lxc>
Getting Started With OneAndOne <oneandone>
Getting Started With OpenNebula <opennebula>
Getting Started With OpenStack <openstack>
Getting Started With Parallels <parallels>

View file

@ -406,4 +406,22 @@ configuration file. For example:
- whoami
- echo 'hello world!'
These commands will run in sequence **before** the bootstrap script is executed.
These commands will run in sequence **before** the bootstrap script is executed.
Force Minion Config
===================
.. versionadded:: Oxygen
The ``force_minion_config`` option requests the bootstrap process to overwrite
an existing minion configuration file and public/private key files.
Default: False
This might be important for drivers (such as ``saltify``) which are expected to
take over a connection from a former salt master.
.. code-block:: yaml
my_saltify_provider:
driver: saltify
force_minion_config: true

View file

@ -0,0 +1,146 @@
==========================
Getting Started With 1and1
==========================
1&1 is one of the worlds leading Web hosting providers. 1&1 currently offers
a wide range of Web hosting products, including email solutions and high-end
servers in 10 different countries including Germany, Spain, Great Britain
and the United States. From domains to 1&1 MyWebsite to eBusiness solutions
like Cloud Hosting and Web servers for complex tasks, 1&1 is well placed to deliver
a high quality service to its customers. All 1&1 products are hosted in
1&1s high-performance, green data centers in the USA and Europe.
Dependencies
============
* 1and1 >= 1.2.0
Configuration
=============
* Using the new format, set up the cloud configuration at
``/etc/salt/cloud.providers`` or
``/etc/salt/cloud.providers.d/oneandone.conf``:
.. code-block:: yaml
my-oneandone-config:
driver: oneandone
# Set the location of the salt-master
#
minion:
master: saltmaster.example.com
# Configure oneandone authentication credentials
#
api_token: <api_token>
ssh_private_key: /path/to/id_rsa
ssh_public_key: /path/to/id_rsa.pub
Authentication
==============
The ``api_key`` is used for API authorization. This token can be obtained
from the CloudPanel in the Management section below Users.
Profiles
========
Here is an example of a profile:
.. code-block:: yaml
oneandone_fixed_size:
provider: my-oneandone-config
description: Small instance size server
fixed_instance_size: S
appliance_id: 8E3BAA98E3DFD37857810E0288DD8FBA
oneandone_custom_size:
provider: my-oneandone-config
description: Custom size server
vcore: 2
cores_per_processor: 2
ram: 8
appliance_id: 8E3BAA98E3DFD37857810E0288DD8FBA
hdds:
-
is_main: true
size: 20
-
is_main: false
size: 20
The following list explains some of the important properties.
fixed_instance_size_id
When creating a server, either ``fixed_instance_size_id`` or custom hardware params
containing ``vcore``, ``cores_per_processor``, ``ram``, and ``hdds`` must be provided.
Can be one of the IDs listed among the output of the following command:
.. code-block:: bash
salt-cloud --list-sizes oneandone
vcore
Total amount of processors.
cores_per_processor
Number of cores per processor.
ram
RAM memory size in GB.
hdds
Hard disks.
appliance_id
ID of the image that will be installed on server.
Can be one of the IDs listed in the output of the following command:
.. code-block:: bash
salt-cloud --list-images oneandone
datacenter_id
ID of the datacenter where the server will be created.
Can be one of the IDs listed in the output of the following command:
.. code-block:: bash
salt-cloud --list-locations oneandone
description
Description of the server.
password
Password of the server. Password must contain more than 8 characters
using uppercase letters, numbers and other special symbols.
power_on
Power on server after creation. Default is set to true.
firewall_policy_id
Firewall policy ID. If it is not provided, the server will assign
the best firewall policy, creating a new one if necessary. If the parameter
is sent with a 0 value, the server will be created with all ports blocked.
ip_id
IP address ID.
load_balancer_id
Load balancer ID.
monitoring_policy_id
Monitoring policy ID.
deploy
Set to False if Salt should not be installed on the node.
wait_for_timeout
The timeout to wait in seconds for provisioning resources such as servers.
The default wait_for_timeout is 15 minutes.
For more information concerning cloud profiles, see :ref:`here
<salt-cloud-profiles>`.

View file

@ -16,7 +16,7 @@ The Saltify driver has no external dependencies.
Configuration
=============
Because the Saltify driver does not use an actual cloud provider host, it has a
Because the Saltify driver does not use an actual cloud provider host, it can have a
simple provider configuration. The only thing that is required to be set is the
driver name, and any other potentially useful information, like the location of
the salt-master:
@ -31,6 +31,12 @@ the salt-master:
master: 111.222.333.444
provider: saltify
However, if you wish to use the more advanced capabilities of salt-cloud, such as
rebooting, listing, and disconnecting machines, then the salt master must fill
the role usually performed by a vendor's cloud management system. In order to do
that, you must configure your salt master as a salt-api server, and supply credentials
to use it. (See ``salt-api setup`` below.)
Profiles
========
@ -72,6 +78,30 @@ to it can be verified with Salt:
salt my-machine test.ping
Destroy Options
---------------
For obvious reasons, the ``destroy`` action does not actually vaporize hardware.
If the salt master is connected using salt-api, it can tear down parts of
the client machines. It will remove the client's key from the salt master,
and will attempt the following options:
.. code-block:: yaml
- remove_config_on_destroy: true
# default: true
# Deactivate salt-minion on reboot and
# delete the minion config and key files from its ``/etc/salt`` directory,
# NOTE: If deactivation is unsuccessful (older Ubuntu machines) then when
# salt-minion restarts it will automatically create a new, unwanted, set
# of key files. The ``force_minion_config`` option must be used in that case.
- shutdown_on_destroy: false
# default: false
# send a ``shutdown`` command to the client.
.. versionadded:: Oxygen
Using Map Files
---------------
The settings explained in the section above may also be set in a map file. An
@ -135,3 +165,67 @@ Return values:
- ``True``: Credential verification succeeded
- ``False``: Credential verification succeeded
- ``None``: Credential verification was not attempted.
Provisioning salt-api
=====================
In order to query or control minions it created, saltify needs to send commands
to the salt master. It does that using the network interface to salt-api.
The salt-api is not enabled by default. The following example will provide a
simple installation.
.. code-block:: yaml
# file /etc/salt/cloud.profiles.d/my_saltify_profiles.conf
hw_41: # a theoretical example hardware machine
ssh_host: 10.100.9.41 # the hard address of your target
ssh_username: vagrant # a user name which has passwordless sudo
password: vagrant # on your target machine
provider: my_saltify_provider
.. code-block:: yaml
# file /etc/salt/cloud.providers.d/saltify_provider.conf
my_saltify_provider:
driver: saltify
eauth: pam
username: vagrant # supply some sudo-group-member's name
password: vagrant # and password on the salt master
minion:
master: 10.100.9.5 # the hard address of the master
.. code-block:: yaml
# file /etc/salt/master.d/auth.conf
# using salt-api ... members of the 'sudo' group can do anything ...
external_auth:
pam:
sudo%:
- .*
- '@wheel'
- '@runner'
- '@jobs'
.. code-block:: yaml
# file /etc/salt/master.d/api.conf
# see https://docs.saltstack.com/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html
rest_cherrypy:
host: localhost
port: 8000
ssl_crt: /etc/pki/tls/certs/localhost.crt
ssl_key: /etc/pki/tls/certs/localhost.key
thread_pool: 30
socket_queue_size: 10
Start your target machine as a Salt minion named "node41" by:
.. code-block:: bash
$ sudo salt-cloud -p hw_41 node41

View file

@ -18,10 +18,10 @@ on the significance and complexity of the changes required by the user.
Salt feature releases are based on the Periodic Table. Any new features going
into the develop branch will be named after the next element in the Periodic
Table. For example, Beryllium was the feature release name of the develop branch
before the 2015.8 branch was tagged. At that point in time, any new features going
into the develop branch after 2015.8 was branched were part of the Boron feature
release.
Table. For example, Beryllium was the feature release name of the develop
branch before the 2015.8 branch was tagged. At that point in time, any new
features going into the develop branch after 2015.8 was branched were part of
the Boron feature release.
A deprecation warning should be in place for at least two major releases before
the deprecated code and its accompanying deprecation warning are removed. More
@ -29,14 +29,14 @@ time should be given for more complex changes. For example, if the current
release under development is ``Sodium``, the deprecated code and associated
warnings should remain in place and warn for at least ``Aluminum``.
To help in this deprecation task, salt provides :func:`salt.utils.warn_until
<salt.utils.warn_until>`. The idea behind this helper function is to show the
deprecation warning to the user until salt reaches the provided version. Once
that provided version is equaled :func:`salt.utils.warn_until
<salt.utils.warn_until>` will raise a :py:exc:`RuntimeError` making salt stop
its execution. This stoppage is unpleasant and will remind the developer that
the deprecation limit has been reached and that the code can then be safely
removed.
To help in this deprecation task, salt provides
:func:`salt.utils.versions.warn_until <salt.utils.versions.warn_until>`. The
idea behind this helper function is to show the deprecation warning to the user
until salt reaches the provided version. Once that provided version is equaled
:func:`salt.utils.versions.warn_until <salt.utils.versions.warn_until>` will
raise a :py:exc:`RuntimeError` making salt stop its execution. This stoppage is
unpleasant and will remind the developer that the deprecation limit has been
reached and that the code can then be safely removed.
Consider the following example:
@ -44,7 +44,7 @@ Consider the following example:
def some_function(bar=False, foo=None):
if foo is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Aluminum',
'The \'foo\' argument has been deprecated and its '
'functionality removed, as such, its usage is no longer '

View file

@ -319,7 +319,7 @@ function into ``__salt__`` that's actually a MagicMock instance.
def show_patch(self):
with patch.dict(my_module.__salt__,
{'function.to_replace': MagicMock()}:
{'function.to_replace': MagicMock()}):
# From this scope, carry on with testing, with a modified __salt__!

View file

@ -0,0 +1,154 @@
=========================================
Arista EOS Salt minion installation guide
=========================================
The Salt minion for Arista EOS is distributed as a SWIX extension and can be installed directly on the switch. The EOS network operating system is based on old Fedora distributions and the installation of the ``salt-minion`` requires backports. This SWIX extension contains the necessary backports, together with the Salt basecode.
.. note::
This SWIX extension has been tested on Arista DCS-7280SE-68-R, running EOS 4.17.5M and vEOS 4.18.3F.
Important Notes
===============
This package is in beta, make sure to test it carefully before running it in production.
If confirmed working correctly, please report and add a note on this page with the platform model and EOS version.
If you want to uninstall this package, please refer to the uninstalling_ section.
Installation from the Official SaltStack Repository
===================================================
Download the swix package and save it to flash.
.. code-block:: bash
veos#copy https://salt-eos.netops.life/salt-eos-latest.swix flash:
veos#copy https://salt-eos.netops.life/startup.sh flash:
Install the Extension
=====================
Copy the Salt package to extension
.. code-block:: bash
veos#copy flash:salt-eos-latest.swix extension:
Install the SWIX
.. code-block:: bash
veos#extension salt-eos-latest.swix force
Verify the installation
.. code-block:: bash
veos#show extensions | include salt-eos
salt-eos-2017-07-19.swix 1.0.11/1.fc25 A, F 27
Change the Salt master IP address or FQDN, by edit the variable (SALT_MASTER)
.. code-block:: bash
veos#bash vi /mnt/flash/startup.sh
Make sure you enable the eAPI with unix-socket
.. code-block:: bash
veos(config)#management api http-commands
protocol unix-socket
no shutdown
Post-installation tasks
=======================
Generate Keys and host record and start Salt minion
.. code-block:: bash
veos#bash
#sudo /mnt/flash/startup.sh
``salt-minion`` should be running
Copy the installed extensions to boot-extensions
.. code-block:: bash
veos#copy installed-extensions boot-extensions
Apply event-handler to let EOS start salt-minion during boot-up
.. code-block:: bash
veos(config)#event-handler boot-up-script
trigger on-boot
action bash sudo /mnt/flash/startup.sh
For more specific installation details of the ``salt-minion``, please refer to :ref:`Configuring Salt<configuring-salt>`.
.. _uninstalling:
Uninstalling
============
If you decide to uninstall this package, the following steps are recommended for safety:
1. Remove the extension from boot-extensions
.. code-block:: bash
veos#bash rm /mnt/flash/boot-extensions
2. Remove the extension from extensions folder
.. code-block:: bash
veos#bash rm /mnt/flash/.extensions/salt-eos-latest.swix
2. Remove boot-up script
.. code-block:: bash
veos(config)#no event-handler boot-up-script
Additional Information
======================
This SWIX extension contains the following RPM packages:
.. code-block:: text
libsodium-1.0.11-1.fc25.i686.rpm
libstdc++-6.2.1-2.fc25.i686.rpm
openpgm-5.2.122-6.fc24.i686.rpm
python-Jinja2-2.8-0.i686.rpm
python-PyYAML-3.12-0.i686.rpm
python-babel-0.9.6-5.fc18.noarch.rpm
python-backports-1.0-3.fc18.i686.rpm
python-backports-ssl_match_hostname-3.4.0.2-1.fc18.noarch.rpm
python-backports_abc-0.5-0.i686.rpm
python-certifi-2016.9.26-0.i686.rpm
python-chardet-2.0.1-5.fc18.noarch.rpm
python-crypto-1.4.1-1.noarch.rpm
python-crypto-2.6.1-1.fc18.i686.rpm
python-futures-3.1.1-1.noarch.rpm
python-jtextfsm-0.3.1-0.noarch.rpm
python-kitchen-1.1.1-2.fc18.noarch.rpm
python-markupsafe-0.18-1.fc18.i686.rpm
python-msgpack-python-0.4.8-0.i686.rpm
python-napalm-base-0.24.3-1.noarch.rpm
python-napalm-eos-0.6.0-1.noarch.rpm
python-netaddr-0.7.18-0.noarch.rpm
python-pyeapi-0.7.0-0.noarch.rpm
python-salt-2017.7.0_1414_g2fb986f-1.noarch.rpm
python-singledispatch-3.4.0.3-0.i686.rpm
python-six-1.10.0-0.i686.rpm
python-tornado-4.4.2-0.i686.rpm
python-urllib3-1.5-7.fc18.noarch.rpm
python2-zmq-15.3.0-2.fc25.i686.rpm
zeromq-4.1.4-5.fc25.i686.rpm

View file

@ -46,6 +46,7 @@ These guides go into detail how to install Salt on a given platform.
arch
debian
eos
fedora
freebsd
gentoo

View file

@ -38,6 +38,14 @@ In previous releases the bind credentials would only be used to determine
the LDAP user's existence and group membership. The user's LDAP credentials
were used from then on.
Stormpath External Authentication Removed
-----------------------------------------
Per Stormpath's announcement, their API will be shutting down on 8/17/2017 at
noon PST so the Stormpath external authentication module has been removed.
https://stormpath.com/oktaplusstormpath
New GitFS Features
------------------
@ -98,6 +106,497 @@ running on T-Series SPARC hardware. The ``virtual_subtype`` grain is
populated as a list of domain roles.
Beacon configuration changes
----------------------------------------
In order to remain consistent and to align with other Salt components such as states,
support for configuring beacons using dictionary based configuration has been deprecated
in favor of list based configuration. All beacons have a validation function which will
check the configuration for the correct format and only load if the validation passes.
- ``avahi_announce`` beacon
Old behavior:
```
beacons:
avahi_announce:
run_once: True
servicetype: _demo._tcp
port: 1234
txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
```
New behavior:
```
beacons:
avahi_announce:
- run_once: True
- servicetype: _demo._tcp
- port: 1234
- txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
```
- ``bonjour_announce`` beacon
Old behavior:
```
beacons:
bonjour_announce:
run_once: True
servicetype: _demo._tcp
port: 1234
txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
```
New behavior:
```
beacons:
bonjour_announce:
- run_once: True
- servicetype: _demo._tcp
- port: 1234
- txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
```
- ``btmp`` beacon
Old behavior:
```
beacons:
btmp: {}
```
New behavior:
```
beacons:
btmp: []
```
- ``glxinfo`` beacon
Old behavior:
```
beacons:
glxinfo:
user: frank
screen_event: True
```
New behavior:
```
beacons:
glxinfo:
- user: frank
- screen_event: True
```
- ``haproxy`` beacon
Old behavior:
```
beacons:
haproxy:
- www-backend:
threshold: 45
servers:
- web1
- web2
- interval: 120
```
New behavior:
```
beacons:
haproxy:
- backends:
www-backend:
threshold: 45
servers:
- web1
- web2
- interval: 120
```
- ``inotify`` beacon
Old behavior:
```
beacons:
inotify:
/path/to/file/or/dir:
mask:
- open
- create
- close_write
recurse: True
auto_add: True
exclude:
- /path/to/file/or/dir/exclude1
- /path/to/file/or/dir/exclude2
- /path/to/file/or/dir/regex[a-m]*$:
regex: True
coalesce: True
```
New behavior:
```
beacons:
inotify:
- files:
/path/to/file/or/dir:
mask:
- open
- create
- close_write
recurse: True
auto_add: True
exclude:
- /path/to/file/or/dir/exclude1
- /path/to/file/or/dir/exclude2
- /path/to/file/or/dir/regex[a-m]*$:
regex: True
- coalesce: True
```
- ``journald`` beacon
Old behavior:
```
beacons:
journald:
sshd:
SYSLOG_IDENTIFIER: sshd
PRIORITY: 6
```
New behavior:
```
beacons:
journald:
- services:
sshd:
SYSLOG_IDENTIFIER: sshd
PRIORITY: 6
```
- ``load`` beacon
Old behavior:
```
beacons:
load:
1m:
- 0.0
- 2.0
5m:
- 0.0
- 1.5
15m:
- 0.1
- 1.0
emitatstartup: True
onchangeonly: False
```
New behavior:
```
beacons:
load:
- averages:
1m:
- 0.0
- 2.0
5m:
- 0.0
- 1.5
15m:
- 0.1
- 1.0
- emitatstartup: True
- onchangeonly: False
```
- ``log`` beacon
Old behavior:
```
beacons:
log:
file: <path>
<tag>:
regex: <pattern>
```
New behavior:
```
beacons:
log:
- file: <path>
- tags:
<tag>:
regex: <pattern>
```
- ``network_info`` beacon
Old behavior:
```
beacons:
network_info:
- eth0:
type: equal
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
```
New behavior:
```
beacons:
network_info:
- interfaces:
eth0:
type: equal
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
```
- ``network_settings`` beacon
Old behavior:
```
beacons:
network_settings:
eth0:
ipaddr:
promiscuity:
onvalue: 1
eth1:
linkmode:
```
New behavior:
```
beacons:
network_settings:
- interfaces:
- eth0:
ipaddr:
promiscuity:
onvalue: 1
- eth1:
linkmode:
```
- ``proxy_example`` beacon
Old behavior:
```
beacons:
proxy_example:
endpoint: beacon
```
New behavior:
```
beacons:
proxy_example:
- endpoint: beacon
```
- ``ps`` beacon
Old behavior:
```
beacons:
ps:
- salt-master: running
- mysql: stopped
```
New behavior:
```
beacons:
ps:
- processes:
salt-master: running
mysql: stopped
```
- ``salt_proxy`` beacon
Old behavior:
```
beacons:
salt_proxy:
- p8000: {}
- p8001: {}
```
New behavior:
```
beacons:
salt_proxy:
- proxies:
p8000: {}
p8001: {}
```
- ``sensehat`` beacon
Old behavior:
```
beacons:
sensehat:
humidity: 70%
temperature: [20, 40]
temperature_from_pressure: 40
pressure: 1500
```
New behavior:
```
beacons:
sensehat:
- sensors:
humidity: 70%
temperature: [20, 40]
temperature_from_pressure: 40
pressure: 1500
```
- ``service`` beacon
Old behavior:
```
beacons:
service:
salt-master:
mysql:
```
New behavior:
```
beacons:
service:
- services:
nginx:
onchangeonly: True
delay: 30
uncleanshutdown: /run/nginx.pid
```
- ``sh`` beacon
Old behavior:
```
beacons:
sh: {}
```
New behavior:
```
beacons:
sh: []
```
- ``status`` beacon
Old behavior:
```
beacons:
status: {}
```
New behavior:
```
beacons:
status: []
```
- ``telegram_bot_msg`` beacon
Old behavior:
```
beacons:
telegram_bot_msg:
token: "<bot access token>"
accept_from:
- "<valid username>"
interval: 10
```
New behavior:
```
beacons:
telegram_bot_msg:
- token: "<bot access token>"
- accept_from:
- "<valid username>"
- interval: 10
```
- ``twilio_txt_msg`` beacon
Old behavior:
```
beacons:
twilio_txt_msg:
account_sid: "<account sid>"
auth_token: "<auth token>"
twilio_number: "+15555555555"
interval: 10
```
New behavior:
```
beacons:
twilio_txt_msg:
- account_sid: "<account sid>"
- auth_token: "<auth token>"
- twilio_number: "+15555555555"
- interval: 10
```
- ``wtmp`` beacon
Old behavior:
```
beacons:
wtmp: {}
```
New behavior:
```
beacons:
wtmp: []
```
Deprecations
------------
@ -192,6 +691,18 @@ For ``smartos`` some grains have been deprecated. These grains will be removed i
- The ``hypervisor_uuid`` has been replaced with ``mdata:sdc:server_uuid`` grain.
- The ``datacenter`` has been replaced with ``mdata:sdc:datacenter_name`` grain.
Minion Blackout
---------------
During a blackout, minions will not execute any remote execution commands,
except for :mod:`saltutil.refresh_pillar <salt.modules.saltutil.refresh_pillar>`.
Previously, support was added so that blackouts are enabled using a special
pillar key, ``minion_blackout`` set to ``True`` and an optional pillar key
``minion_blackout_whitelist`` to specify additional functions that are permitted
during blackout. This release adds support for using this feature in the grains
as well, by using special grains keys ``minion_blackout`` and
``minion_blackout_whitelist``.
Utils Deprecations
==================

View file

@ -166,13 +166,15 @@ Ubuntu 14.04 LTS and Debian Wheezy (7.x) also have a compatible version packaged
# apt-get install python-git
If your master is running an older version (such as Ubuntu 12.04 LTS or Debian
Squeeze), then you will need to install GitPython using either pip_ or
easy_install (it is recommended to use pip). Version 0.3.2.RC1 is now marked as
the stable release in PyPI, so it should be a simple matter of running ``pip
install GitPython`` (or ``easy_install GitPython``) as root.
GitPython_ requires the ``git`` CLI utility to work. If installed from a system
package, then git should already be installed, but if installed via pip_ then
it may still be necessary to install git separately. For MacOS users,
GitPython_ comes bundled in with the Salt installer, but git must still be
installed for it to work properly. Git can be installed in several ways,
including by installing XCode_.
.. _`pip`: http://www.pip-installer.org/
.. _pip: http://www.pip-installer.org/
.. _XCode: https://developer.apple.com/xcode/
.. warning::

View file

@ -110,7 +110,7 @@ To pass through a file that contains jinja + yaml templating (the default):
method='POST',
data_file='/srv/salt/somefile.jinja',
data_render=True,
template_data={'key1': 'value1', 'key2': 'value2'}
template_dict={'key1': 'value1', 'key2': 'value2'}
)
To pass through a file that contains mako templating:
@ -123,7 +123,7 @@ To pass through a file that contains mako templating:
data_file='/srv/salt/somefile.mako',
data_render=True,
data_renderer='mako',
template_data={'key1': 'value1', 'key2': 'value2'}
template_dict={'key1': 'value1', 'key2': 'value2'}
)
Because this function uses Salt's own rendering system, any Salt renderer can
@ -140,7 +140,7 @@ However, this can be changed to ``master`` if necessary.
method='POST',
data_file='/srv/salt/somefile.jinja',
data_render=True,
template_data={'key1': 'value1', 'key2': 'value2'},
template_dict={'key1': 'value1', 'key2': 'value2'},
opts=__opts__
)
@ -149,7 +149,7 @@ However, this can be changed to ``master`` if necessary.
method='POST',
data_file='/srv/salt/somefile.jinja',
data_render=True,
template_data={'key1': 'value1', 'key2': 'value2'},
template_dict={'key1': 'value1', 'key2': 'value2'},
node='master'
)
@ -170,11 +170,11 @@ a Python dict.
header_file='/srv/salt/headers.jinja',
header_render=True,
header_renderer='jinja',
template_data={'key1': 'value1', 'key2': 'value2'}
template_dict={'key1': 'value1', 'key2': 'value2'}
)
Because much of the data that would be templated between headers and data may be
the same, the ``template_data`` is the same for both. Correcting possible
the same, the ``template_dict`` is the same for both. Correcting possible
variable name collisions is up to the user.
Authentication

View file

@ -28,9 +28,8 @@ Tutorials Index
* :ref:`States tutorial, part 3 - Templating, Includes, Extends <tutorial-states-part-3>`
* :ref:`States tutorial, part 4 <tutorial-states-part-4>`
* :ref:`How to Convert Jinja Logic to an Execution Module <tutorial-jinja_to_execution-module>`
* :ref:`Using Salt with Stormpath <tutorial-stormpath>`
* :ref:`Syslog-ng usage <syslog-ng-sate-usage>`
* :ref:`The macOS (Maverick) Developer Step By Step Guide To Salt Installation <tutorial-macos-walk-through>`
* :ref:`SaltStack Walk-through <tutorial-salt-walk-through>`
* :ref:`Writing Salt Tests <tutorial-salt-testing>`
* :ref:`Multi-cloud orchestration with Apache Libcloud <tutorial-libcloud>`
* :ref:`Multi-cloud orchestration with Apache Libcloud <tutorial-libcloud>`

View file

@ -1,198 +0,0 @@
.. _tutorial-stormpath:
=========================
Using Salt with Stormpath
=========================
`Stormpath <https://stormpath.com/>`_ is a user management and authentication
service. This tutorial covers using SaltStack to manage and take advantage of
Stormpath's features.
External Authentication
-----------------------
Stormpath can be used for Salt's external authentication system. In order to do
this, the master should be configured with an ``apiid``, ``apikey``, and the ID
of the ``application`` that is associated with the users to be authenticated:
.. code-block:: yaml
stormpath:
apiid: 367DFSF4FRJ8767FSF4G34FGH
apikey: FEFREF43t3FEFRe/f323fwer4FWF3445gferWRWEer1
application: 786786FREFrefreg435fr1
.. note::
These values can be found in the `Stormpath dashboard
<https://api.stormpath.com/ui2/index.html#/>`_`.
Users that are to be authenticated should be set up under the ``stormpath``
dict under ``external_auth``:
.. code-block:: yaml
external_auth:
stormpath:
larry:
- .*
- '@runner'
- '@wheel'
Keep in mind that while Stormpath defaults the username associated with the
account to the email address, it is better to use a username without an ``@``
sign in it.
Configuring Stormpath Modules
-----------------------------
Stormpath accounts can be managed via either an execution or state module. In
order to use either, a minion must be configured with an API ID and key.
.. code-block:: yaml
stormpath:
apiid: 367DFSF4FRJ8767FSF4G34FGH
apikey: FEFREF43t3FEFRe/f323fwer4FWF3445gferWRWEer1
directory: efreg435fr1786786FREFr
application: 786786FREFrefreg435fr1
Some functions in the ``stormpath`` modules can make use of other options. The
following options are also available.
directory
`````````
The ID of the directory that is to be used with this minion. Many functions
require an ID to be specified to do their work. However, if the ID of a
``directory`` is specified, then Salt can often look up the resource in
question.
application
```````````
The ID of the application that is to be used with this minion. Many functions
require an ID to be specified to do their work. However, if the ID of a
``application`` is specified, then Salt can often look up the resource in
question.
Managing Stormpath Accounts
---------------------------
With the ``stormpath`` configuration in place, Salt can be used to configure
accounts (which may be thought of as users) on the Stormpath service. The
following functions are available.
stormpath.create_account
````````````````````````
Create an account on the Stormpath service. This requires a ``directory_id`` as
the first argument; it will not be retrieved from the minion configuration. An
``email`` address, ``password``, first name (``givenName``) and last name
(``surname``) are also required. For the full list of other parameters that may
be specified, see:
http://docs.stormpath.com/rest/product-guide/#account-resource
When executed with no errors, this function will return the information about
the account, from Stormpath.
.. code-block:: bash
salt myminion stormpath.create_account <directory_id> shemp@example.com letmein Shemp Howard
stormpath.list_accounts
```````````````````````
Show all accounts on the Stormpath service. This will return all accounts,
regardless of directory, application, or group.
.. code-block:: bash
salt myminion stormpath.list_accounts
'''
stormpath.show_account
``````````````````````
Show the details for a specific Stormpath account. An ``account_id`` is normally
required. However, if am ``email`` is provided instead, along with either a
``directory_id``, ``application_id``, or ``group_id``, then Salt will search the
specified resource to try and locate the ``account_id``.
.. code-block:: bash
salt myminion stormpath.show_account <account_id>
salt myminion stormpath.show_account email=<email> directory_id=<directory_id>
stormpath.update_account
````````````````````````
Update one or more items for this account. Specifying an empty value will clear
it for that account. This function may be used in one of two ways. In order to
update only one key/value pair, specify them in order:
.. code-block:: bash
salt myminion stormpath.update_account <account_id> givenName shemp
salt myminion stormpath.update_account <account_id> middleName ''
In order to specify multiple items, they need to be passed in as a dict. From
the command line, it is best to do this as a JSON string:
.. code-block:: bash
salt myminion stormpath.update_account <account_id> items='{"givenName": "Shemp"}
salt myminion stormpath.update_account <account_id> items='{"middlename": ""}
When executed with no errors, this function will return the information about
the account, from Stormpath.
stormpath.delete_account
````````````````````````
Delete an account from Stormpath.
.. code-block:: bash
salt myminion stormpath.delete_account <account_id>
stormpath.list_directories
``````````````````````````
Show all directories associated with this tenant.
.. code-block:: bash
salt myminion stormpath.list_directories
Using Stormpath States
----------------------
Stormpath resources may be managed using the state system. The following states
are available.
stormpath_account.present
`````````````````````````
Ensure that an account exists on the Stormpath service. All options that are
available with the ``stormpath.create_account`` function are available here.
If an account needs to be created, then this function will require the same
fields that ``stormpath.create_account`` requires, including the ``password``.
However, if a password changes for an existing account, it will NOT be updated
by this state.
.. code-block:: yaml
curly@example.com:
stormpath_account.present:
- directory_id: efreg435fr1786786FREFr
- password: badpass
- firstName: Curly
- surname: Howard
- nickname: curly
It is advisable to always set a ``nickname`` that is not also an email address,
so that it can be used by Salt's external authentication module.
stormpath_account.absent
````````````````````````
Ensure that an account does not exist on Stormpath. As with
``stormpath_account.present``, the ``name`` supplied to this state is the
``email`` address associated with this account. Salt will use this, with or
without the ``directory`` ID that is configured for the minion. However, lookups
will be much faster with a directory ID specified.

View file

@ -7,7 +7,7 @@ CherryPy==11.0.0
click==6.7
enum34==1.1.6
gitdb==0.6.4
GitPython==2.1.5
GitPython==2.1.1
idna==2.5
ipaddress==1.0.18
Jinja2==2.9.6

View file

@ -101,8 +101,8 @@ import logging
import os
# Import salt utils
import salt.utils
import salt.utils.files
import salt.utils.versions
log = logging.getLogger(__name__)
@ -200,7 +200,7 @@ def _htpasswd(username, password, **kwargs):
pwfile = HtpasswdFile(kwargs['filename'])
# passlib below version 1.6 uses 'verify' function instead of 'check_password'
if salt.utils.version_cmp(kwargs['passlib_version'], '1.6') < 0:
if salt.utils.versions.version_cmp(kwargs['passlib_version'], '1.6') < 0:
return pwfile.verify(username, password)
else:
return pwfile.check_password(username, password)
@ -222,7 +222,7 @@ def _htdigest(username, password, **kwargs):
pwfile = HtdigestFile(kwargs['filename'])
# passlib below version 1.6 uses 'verify' function instead of 'check_password'
if salt.utils.version_cmp(kwargs['passlib_version'], '1.6') < 0:
if salt.utils.versions.version_cmp(kwargs['passlib_version'], '1.6') < 0:
return pwfile.verify(username, realm, password)
else:
return pwfile.check_password(username, realm, password)

View file

@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
'''
Provide authentication using Stormpath.
This driver requires some extra configuration beyond that which Stormpath
normally requires.
.. 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
.. versionadded:: 2015.8.0
'''
from __future__ import absolute_import
import json
import base64
import urllib
import salt.utils.http
import logging
log = logging.getLogger(__name__)
def auth(username, password):
'''
Authenticate using a Stormpath directory or application
'''
apiid = __opts__.get('stormpath', {}).get('apiid', None)
apikey = __opts__.get('stormpath', {}).get('apikey', None)
application = __opts__.get('stormpath', {}).get('application', None)
path = 'https://api.stormpath.com/v1'
if application is not None:
path = '{0}/applications/{1}/loginAttempts'.format(path, application)
else:
return False
username = urllib.quote(username)
data = {
'type': 'basic',
'value': base64.b64encode('{0}:{1}'.format(username, password))
}
log.debug('{0}:{1}'.format(username, password))
log.debug(path)
log.debug(data)
log.debug(json.dumps(data))
result = salt.utils.http.query(
path,
method='POST',
username=apiid,
password=apikey,
data=json.dumps(data),
header_dict={'Content-type': 'application/json;charset=UTF-8'},
decode=False,
status=True,
opts=__opts__,
)
log.debug(result)
if result.get('status', 403) == 200:
return True
return False

View file

@ -37,8 +37,9 @@ class Beacon(object):
.. code_block:: yaml
beacons:
inotify:
- /etc/fstab: {}
- /var/cache/foo: {}
- files:
- /etc/fstab: {}
- /var/cache/foo: {}
'''
ret = []
b_config = copy.deepcopy(config)
@ -69,6 +70,7 @@ class Beacon(object):
log.trace('Beacon processing: {0}'.format(mod))
fun_str = '{0}.beacon'.format(mod)
validate_str = '{0}.validate'.format(mod)
if fun_str in self.beacons:
runonce = self._determine_beacon_config(current_beacon_config, 'run_once')
interval = self._determine_beacon_config(current_beacon_config, 'interval')
@ -95,6 +97,17 @@ class Beacon(object):
continue
# Update __grains__ on the beacon
self.beacons[fun_str].__globals__['__grains__'] = grains
# Run the validate function if it's available,
# otherwise there is a warning about it being missing
if validate_str in self.beacons:
valid, vcomment = self.beacons[validate_str](b_config[mod])
if not valid:
log.info('Beacon %s configuration invalid, '
'not running.\n%s', mod, vcomment)
continue
# Fire the beacon!
raw = self.beacons[fun_str](b_config[mod])
for data in raw:
@ -193,6 +206,8 @@ class Beacon(object):
# Fire the complete event back along with the list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
b_conf = self.functions['config.merge']('beacons')
if not isinstance(self.opts['beacons'], dict):
self.opts['beacons'] = {}
self.opts['beacons'].update(b_conf)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacons_list_complete')

View file

@ -10,8 +10,8 @@ from __future__ import absolute_import
import logging
# Salt libs
import salt.utils
import salt.utils.path
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
@ -29,25 +29,34 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for adb beacon should be a dictionary with states array
if not isinstance(config, dict):
log.info('Configuration for adb beacon must be a dict.')
return False, ('Configuration for adb beacon must be a dict.')
elif 'states' not in config.keys():
if not isinstance(config, list):
log.info('Configuration for adb beacon must be a list.')
return False, ('Configuration for adb beacon must be a list.')
_config = {}
list(map(_config.update, config))
if 'states' not in _config:
log.info('Configuration for adb beacon must include a states array.')
return False, ('Configuration for adb beacon must include a states array.')
else:
states = ['offline', 'bootloader', 'device', 'host', 'recovery', 'no permissions',
'sideload', 'unauthorized', 'unknown', 'missing']
if any(s not in states for s in config['states']):
log.info('Need a one of the following adb '
'states: {0}'.format(', '.join(states)))
return False, ('Need a one of the following adb '
'states: {0}'.format(', '.join(states)))
if not isinstance(_config['states'], list):
log.info('Configuration for adb beacon must include a states array.')
return False, ('Configuration for adb beacon must include a states array.')
else:
states = ['offline', 'bootloader', 'device', 'host',
'recovery', 'no permissions',
'sideload', 'unauthorized', 'unknown', 'missing']
if any(s not in states for s in _config['states']):
log.info('Need a one of the following adb '
'states: {0}'.format(', '.join(states)))
return False, ('Need a one of the following adb '
'states: {0}'.format(', '.join(states)))
return True, 'Valid beacon configuration'
@ -75,11 +84,10 @@ def beacon(config):
log.trace('adb beacon starting')
ret = []
_validate = __validate__(config)
if not _validate[0]:
return ret
_config = {}
list(map(_config.update, config))
out = __salt__['cmd.run']('adb devices', runas=config.get('user', None))
out = __salt__['cmd.run']('adb devices', runas=_config.get('user', None))
lines = out.split('\n')[1:]
last_state_devices = list(last_state.keys())
@ -91,21 +99,21 @@ def beacon(config):
found_devices.append(device)
if device not in last_state_devices or \
('state' in last_state[device] and last_state[device]['state'] != state):
if state in config['states']:
if state in _config['states']:
ret.append({'device': device, 'state': state, 'tag': state})
last_state[device] = {'state': state}
if 'battery_low' in config:
if 'battery_low' in _config:
val = last_state.get(device, {})
cmd = 'adb -s {0} shell cat /sys/class/power_supply/*/capacity'.format(device)
battery_levels = __salt__['cmd.run'](cmd, runas=config.get('user', None)).split('\n')
battery_levels = __salt__['cmd.run'](cmd, runas=_config.get('user', None)).split('\n')
for l in battery_levels:
battery_level = int(l)
if 0 < battery_level < 100:
if 'battery' not in val or battery_level != val['battery']:
if ('battery' not in val or val['battery'] > config['battery_low']) and \
battery_level <= config['battery_low']:
if ('battery' not in val or val['battery'] > _config['battery_low']) and \
battery_level <= _config['battery_low']:
ret.append({'device': device, 'battery_level': battery_level, 'tag': 'battery_low'})
if device not in last_state:
@ -119,13 +127,13 @@ def beacon(config):
# Find missing devices and remove them / send an event
for device in last_state_devices:
if device not in found_devices:
if 'missing' in config['states']:
if 'missing' in _config['states']:
ret.append({'device': device, 'state': 'missing', 'tag': 'missing'})
del last_state[device]
# Maybe send an event if we don't have any devices
if 'no_devices_event' in config and config['no_devices_event'] is True:
if 'no_devices_event' in _config and _config['no_devices_event'] is True:
if len(found_devices) == 0 and not last_state_extra['no_devices']:
ret.append({'tag': 'no_devices'})

View file

@ -15,6 +15,7 @@ Dependencies
from __future__ import absolute_import
import logging
import time
from salt.ext.six.moves import map
# Import 3rd Party libs
try:
@ -54,17 +55,23 @@ def __virtual__():
'\'python-avahi\' dependency is missing.'.format(__virtualname__)
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
return False, ('Configuration for avahi_announcement '
'beacon must be a dictionary')
elif not all(x in list(config.keys()) for x in ('servicetype', 'port', 'txt')):
_config = {}
list(map(_config.update, config))
if not isinstance(config, list):
return False, ('Configuration for avahi_announce '
'beacon must be a list.')
elif not all(x in _config for x in ('servicetype',
'port',
'txt')):
return False, ('Configuration for avahi_announce beacon '
'must contain servicetype, port and txt items')
return True, 'Valid beacon configuration'
'must contain servicetype, port and txt items.')
return True, 'Valid beacon configuration.'
def _enforce_txt_record_maxlen(key, value):
@ -138,13 +145,13 @@ def beacon(config):
beacons:
avahi_announce:
run_once: True
servicetype: _demo._tcp
port: 1234
txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
- run_once: True
- servicetype: _demo._tcp
- port: 1234
- txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
'''
ret = []
changes = {}
@ -152,30 +159,27 @@ def beacon(config):
global LAST_GRAINS
_validate = __validate__(config)
if not _validate[0]:
log.warning('Beacon {0} configuration invalid, '
'not adding. {1}'.format(__virtualname__, _validate[1]))
return ret
_config = {}
list(map(_config.update, config))
if 'servicename' in config:
servicename = config['servicename']
if 'servicename' in _config:
servicename = _config['servicename']
else:
servicename = __grains__['host']
# Check for hostname change
if LAST_GRAINS and LAST_GRAINS['host'] != servicename:
changes['servicename'] = servicename
if LAST_GRAINS and config.get('reset_on_change', False):
if LAST_GRAINS and _config.get('reset_on_change', False):
# Check for IP address change in the case when we reset on change
if LAST_GRAINS.get('ipv4', []) != __grains__.get('ipv4', []):
changes['ipv4'] = __grains__.get('ipv4', [])
if LAST_GRAINS.get('ipv6', []) != __grains__.get('ipv6', []):
changes['ipv6'] = __grains__.get('ipv6', [])
for item in config['txt']:
if config['txt'][item].startswith('grains.'):
grain = config['txt'][item][7:]
for item in _config['txt']:
if _config['txt'][item].startswith('grains.'):
grain = _config['txt'][item][7:]
grain_index = None
square_bracket = grain.find('[')
if square_bracket != -1 and grain[-1] == ']':
@ -192,7 +196,7 @@ def beacon(config):
if LAST_GRAINS and (LAST_GRAINS.get(grain, '') != __grains__.get(grain, '')):
changes[str('txt.' + item)] = txt[item]
else:
txt[item] = _enforce_txt_record_maxlen(item, config['txt'][item])
txt[item] = _enforce_txt_record_maxlen(item, _config['txt'][item])
if not LAST_GRAINS:
changes[str('txt.' + item)] = txt[item]
@ -200,33 +204,33 @@ def beacon(config):
if changes:
if not LAST_GRAINS:
changes['servicename'] = servicename
changes['servicetype'] = config['servicetype']
changes['port'] = config['port']
changes['servicetype'] = _config['servicetype']
changes['port'] = _config['port']
changes['ipv4'] = __grains__.get('ipv4', [])
changes['ipv6'] = __grains__.get('ipv6', [])
GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
servicename, config['servicetype'], '', '',
dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt))
servicename, _config['servicetype'], '', '',
dbus.UInt16(_config['port']), avahi.dict_to_txt_array(txt))
GROUP.Commit()
elif config.get('reset_on_change', False) or 'servicename' in changes:
elif _config.get('reset_on_change', False) or 'servicename' in changes:
# A change in 'servicename' requires a reset because we can only
# directly update TXT records
GROUP.Reset()
reset_wait = config.get('reset_wait', 0)
reset_wait = _config.get('reset_wait', 0)
if reset_wait > 0:
time.sleep(reset_wait)
GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
servicename, config['servicetype'], '', '',
dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt))
servicename, _config['servicetype'], '', '',
dbus.UInt16(_config['port']), avahi.dict_to_txt_array(txt))
GROUP.Commit()
else:
GROUP.UpdateServiceTxt(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
servicename, config['servicetype'], '',
servicename, _config['servicetype'], '',
avahi.dict_to_txt_array(txt))
ret.append({'tag': 'result', 'changes': changes})
if config.get('copy_grains', False):
if _config.get('copy_grains', False):
LAST_GRAINS = __grains__.copy()
else:
LAST_GRAINS = __grains__

View file

@ -9,6 +9,7 @@ import atexit
import logging
import select
import time
from salt.ext.six.moves import map
# Import 3rd Party libs
try:
@ -47,17 +48,23 @@ def _register_callback(sdRef, flags, errorCode, name, regtype, domain): # pylin
log.error('Bonjour registration failed with error code {0}'.format(errorCode))
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
return False, ('Configuration for bonjour_announcement '
'beacon must be a dictionary')
elif not all(x in list(config.keys()) for x in ('servicetype', 'port', 'txt')):
_config = {}
list(map(_config.update, config))
if not isinstance(config, list):
return False, ('Configuration for bonjour_announce '
'beacon must be a list.')
elif not all(x in _config for x in ('servicetype',
'port',
'txt')):
return False, ('Configuration for bonjour_announce beacon '
'must contain servicetype, port and txt items')
return True, 'Valid beacon configuration'
'must contain servicetype, port and txt items.')
return True, 'Valid beacon configuration.'
def _enforce_txt_record_maxlen(key, value):
@ -131,13 +138,13 @@ def beacon(config):
beacons:
bonjour_announce:
run_once: True
servicetype: _demo._tcp
port: 1234
txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
- run_once: True
- servicetype: _demo._tcp
- port: 1234
- txt:
ProdName: grains.productname
SerialNo: grains.serialnumber
Comments: 'this is a test'
'''
ret = []
changes = {}
@ -146,30 +153,27 @@ def beacon(config):
global LAST_GRAINS
global SD_REF
_validate = __validate__(config)
if not _validate[0]:
log.warning('Beacon {0} configuration invalid, '
'not adding. {1}'.format(__virtualname__, _validate[1]))
return ret
_config = {}
list(map(_config.update, config))
if 'servicename' in config:
servicename = config['servicename']
if 'servicename' in _config:
servicename = _config['servicename']
else:
servicename = __grains__['host']
# Check for hostname change
if LAST_GRAINS and LAST_GRAINS['host'] != servicename:
changes['servicename'] = servicename
if LAST_GRAINS and config.get('reset_on_change', False):
if LAST_GRAINS and _config.get('reset_on_change', False):
# Check for IP address change in the case when we reset on change
if LAST_GRAINS.get('ipv4', []) != __grains__.get('ipv4', []):
changes['ipv4'] = __grains__.get('ipv4', [])
if LAST_GRAINS.get('ipv6', []) != __grains__.get('ipv6', []):
changes['ipv6'] = __grains__.get('ipv6', [])
for item in config['txt']:
if config['txt'][item].startswith('grains.'):
grain = config['txt'][item][7:]
for item in _config['txt']:
if _config['txt'][item].startswith('grains.'):
grain = _config['txt'][item][7:]
grain_index = None
square_bracket = grain.find('[')
if square_bracket != -1 and grain[-1] == ']':
@ -186,7 +190,7 @@ def beacon(config):
if LAST_GRAINS and (LAST_GRAINS.get(grain, '') != __grains__.get(grain, '')):
changes[str('txt.' + item)] = txt[item]
else:
txt[item] = _enforce_txt_record_maxlen(item, config['txt'][item])
txt[item] = _enforce_txt_record_maxlen(item, _config['txt'][item])
if not LAST_GRAINS:
changes[str('txt.' + item)] = txt[item]
@ -195,32 +199,32 @@ def beacon(config):
txt_record = pybonjour.TXTRecord(items=txt)
if not LAST_GRAINS:
changes['servicename'] = servicename
changes['servicetype'] = config['servicetype']
changes['port'] = config['port']
changes['servicetype'] = _config['servicetype']
changes['port'] = _config['port']
changes['ipv4'] = __grains__.get('ipv4', [])
changes['ipv6'] = __grains__.get('ipv6', [])
SD_REF = pybonjour.DNSServiceRegister(
name=servicename,
regtype=config['servicetype'],
port=config['port'],
regtype=_config['servicetype'],
port=_config['port'],
txtRecord=txt_record,
callBack=_register_callback)
atexit.register(_close_sd_ref)
ready = select.select([SD_REF], [], [])
if SD_REF in ready[0]:
pybonjour.DNSServiceProcessResult(SD_REF)
elif config.get('reset_on_change', False) or 'servicename' in changes:
elif _config.get('reset_on_change', False) or 'servicename' in changes:
# A change in 'servicename' requires a reset because we can only
# directly update TXT records
SD_REF.close()
SD_REF = None
reset_wait = config.get('reset_wait', 0)
reset_wait = _config.get('reset_wait', 0)
if reset_wait > 0:
time.sleep(reset_wait)
SD_REF = pybonjour.DNSServiceRegister(
name=servicename,
regtype=config['servicetype'],
port=config['port'],
regtype=_config['servicetype'],
port=_config['port'],
txtRecord=txt_record,
callBack=_register_callback)
ready = select.select([SD_REF], [], [])
@ -236,7 +240,7 @@ def beacon(config):
ret.append({'tag': 'result', 'changes': changes})
if config.get('copy_grains', False):
if _config.get('copy_grains', False):
LAST_GRAINS = __grains__.copy()
else:
LAST_GRAINS = __grains__

View file

@ -5,7 +5,7 @@ Beacon to fire events at failed login of users
.. code-block:: yaml
beacons:
btmp: {}
btmp: []
'''
# Import python libs
@ -52,14 +52,14 @@ def _get_loc():
return __context__[LOC_KEY]
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for load beacon should be a list of dicts
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for btmp beacon must '
'be a list of dictionaries.')
'be a list.')
return True, 'Valid beacon configuration'
@ -71,7 +71,7 @@ def beacon(config):
.. code-block:: yaml
beacons:
btmp: {}
btmp: []
'''
ret = []
with salt.utils.files.fopen(BTMP, 'rb') as fp_:

View file

@ -31,14 +31,14 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for diskusage beacon should be a list of dicts
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for diskusage beacon '
'must be a dictionary.')
'must be a list.')
return True, 'Valid beacon configuration'
@ -86,25 +86,24 @@ def beacon(config):
parts = psutil.disk_partitions(all=False)
ret = []
for mounts in config:
mount = mounts.keys()[0]
mount = next(iter(mounts))
try:
_current_usage = psutil.disk_usage(mount)
except OSError:
# Ensure a valid mount point
log.warning('{0} is not a valid mount point, try regex.'.format(mount))
for part in parts:
if re.match(mount, part.mountpoint):
row = {}
row[part.mountpoint] = mounts[mount]
config.append(row)
continue
for part in parts:
if re.match(mount, part.mountpoint):
_mount = part.mountpoint
current_usage = _current_usage.percent
monitor_usage = mounts[mount]
if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage)
if current_usage >= monitor_usage:
ret.append({'diskusage': current_usage, 'mount': mount})
try:
_current_usage = psutil.disk_usage(mount)
except OSError:
log.warning('{0} is not a valid mount point.'.format(mount))
continue
current_usage = _current_usage.percent
monitor_usage = mounts[mount]
log.info('current_usage {}'.format(current_usage))
if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage)
if current_usage >= monitor_usage:
ret.append({'diskusage': current_usage, 'mount': _mount})
return ret

View file

@ -11,6 +11,7 @@ import logging
# Salt libs
import salt.utils.path
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
@ -28,14 +29,18 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for glxinfo beacon should be a dictionary
if not isinstance(config, dict):
return False, ('Configuration for glxinfo beacon must be a dict.')
if 'user' not in config:
if not isinstance(config, list):
return False, ('Configuration for glxinfo beacon must be a list.')
_config = {}
list(map(_config.update, config))
if 'user' not in _config:
return False, ('Configuration for glxinfo beacon must '
'include a user as glxinfo is not available to root.')
return True, 'Valid beacon configuration'
@ -45,27 +50,28 @@ def beacon(config):
'''
Emit the status of a connected display to the minion
Mainly this is used to detect when the display fails to connect for whatever reason.
Mainly this is used to detect when the display fails to connect
for whatever reason.
.. code-block:: yaml
beacons:
glxinfo:
user: frank
screen_event: True
- user: frank
- screen_event: True
'''
log.trace('glxinfo beacon starting')
ret = []
_validate = __validate__(config)
if not _validate[0]:
return ret
_config = {}
list(map(_config.update, config))
retcode = __salt__['cmd.retcode']('DISPLAY=:0 glxinfo', runas=config['user'], python_shell=True)
retcode = __salt__['cmd.retcode']('DISPLAY=:0 glxinfo',
runas=_config['user'], python_shell=True)
if 'screen_event' in config and config['screen_event']:
if 'screen_event' in _config and _config['screen_event']:
last_value = last_state.get('screen_available', False)
screen_available = retcode == 0
if last_value != screen_available or 'screen_available' not in last_state:

View file

@ -9,7 +9,7 @@ Fire an event when over a specified threshold.
# Import Python libs
from __future__ import absolute_import
import logging
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
@ -23,17 +23,39 @@ def __virtual__():
if 'haproxy.get_sessions' in __salt__:
return __virtualname__
else:
log.debug('Not loading haproxy beacon')
return False
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
return False, ('Configuration for haproxy beacon must be a dictionary.')
if 'haproxy' not in config:
return False, ('Configuration for haproxy beacon requires a list of backends and servers')
if not isinstance(config, list):
return False, ('Configuration for haproxy beacon must '
'be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'backends' not in _config:
return False, ('Configuration for haproxy beacon '
'requires backends.')
else:
if not isinstance(_config['backends'], dict):
return False, ('Backends for haproxy beacon '
'must be a dictionary.')
else:
for backend in _config['backends']:
log.debug('_config {}'.format(_config['backends'][backend]))
if 'servers' not in _config['backends'][backend]:
return False, ('Backends for haproxy beacon '
'require servers.')
else:
_servers = _config['backends'][backend]['servers']
if not isinstance(_servers, list):
return False, ('Servers for haproxy beacon '
'must be a list.')
return True, 'Valid beacon configuration'
@ -46,22 +68,23 @@ def beacon(config):
beacons:
haproxy:
- www-backend:
threshold: 45
servers:
- web1
- web2
- backends:
www-backend:
threshold: 45
servers:
- web1
- web2
- interval: 120
'''
log.debug('haproxy beacon starting')
ret = []
_validate = __validate__(config)
if not _validate:
log.debug('haproxy beacon unable to validate')
return ret
for backend in config:
threshold = config[backend]['threshold']
for server in config[backend]['servers']:
_config = {}
list(map(_config.update, config))
for backend in _config.get('backends', ()):
backend_config = _config['backends'][backend]
threshold = backend_config['threshold']
for server in backend_config['servers']:
scur = __salt__['haproxy.get_sessions'](server, backend)
if scur:
if int(scur) > int(threshold):
@ -69,6 +92,10 @@ def beacon(config):
'scur': scur,
'threshold': threshold,
}
log.debug('Emit because {0} > {1} for {2} in {3}'.format(scur, threshold, server, backend))
log.debug('Emit because {0} > {1}'
' for {2} in {3}'.format(scur,
threshold,
server,
backend))
ret.append(_server)
return ret

View file

@ -23,6 +23,7 @@ import re
# Import salt libs
import salt.ext.six
from salt.ext.six.moves import map
# Import third party libs
try:
@ -79,7 +80,7 @@ def _get_notifier(config):
return __context__['inotify.notifier']
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
@ -105,37 +106,45 @@ def __validate__(config):
]
# Configuration for inotify beacon should be a dict of dicts
log.debug('config {0}'.format(config))
if not isinstance(config, dict):
return False, 'Configuration for inotify beacon must be a dictionary.'
if not isinstance(config, list):
return False, 'Configuration for inotify beacon must be a list.'
else:
for config_item in config:
if not isinstance(config[config_item], dict):
return False, ('Configuration for inotify beacon must '
'be a dictionary of dictionaries.')
else:
if not any(j in ['mask', 'recurse', 'auto_add'] for j in config[config_item]):
_config = {}
list(map(_config.update, config))
if 'files' not in _config:
return False, 'Configuration for inotify beacon must include files.'
else:
for path in _config.get('files'):
if not isinstance(_config['files'][path], dict):
return False, ('Configuration for inotify beacon must '
'contain mask, recurse or auto_add items.')
'be a list of dictionaries.')
else:
if not any(j in ['mask',
'recurse',
'auto_add'] for j in _config['files'][path]):
return False, ('Configuration for inotify beacon must '
'contain mask, recurse or auto_add items.')
if 'auto_add' in config[config_item]:
if not isinstance(config[config_item]['auto_add'], bool):
return False, ('Configuration for inotify beacon '
'auto_add must be boolean.')
if 'auto_add' in _config['files'][path]:
if not isinstance(_config['files'][path]['auto_add'], bool):
return False, ('Configuration for inotify beacon '
'auto_add must be boolean.')
if 'recurse' in config[config_item]:
if not isinstance(config[config_item]['recurse'], bool):
return False, ('Configuration for inotify beacon '
'recurse must be boolean.')
if 'recurse' in _config['files'][path]:
if not isinstance(_config['files'][path]['recurse'], bool):
return False, ('Configuration for inotify beacon '
'recurse must be boolean.')
if 'mask' in config[config_item]:
if not isinstance(config[config_item]['mask'], list):
return False, ('Configuration for inotify beacon '
'mask must be list.')
for mask in config[config_item]['mask']:
if mask not in VALID_MASK:
return False, ('Configuration for inotify beacon '
'invalid mask option {0}.'.format(mask))
if 'mask' in _config['files'][path]:
if not isinstance(_config['files'][path]['mask'], list):
return False, ('Configuration for inotify beacon '
'mask must be list.')
for mask in _config['files'][path]['mask']:
if mask not in VALID_MASK:
return False, ('Configuration for inotify beacon '
'invalid mask option {0}.'.format(mask))
return True, 'Valid beacon configuration'
@ -149,19 +158,20 @@ def beacon(config):
beacons:
inotify:
/path/to/file/or/dir:
mask:
- open
- create
- close_write
recurse: True
auto_add: True
exclude:
- /path/to/file/or/dir/exclude1
- /path/to/file/or/dir/exclude2
- /path/to/file/or/dir/regex[a-m]*$:
regex: True
coalesce: True
- files:
/path/to/file/or/dir:
mask:
- open
- create
- close_write
recurse: True
auto_add: True
exclude:
- /path/to/file/or/dir/exclude1
- /path/to/file/or/dir/exclude2
- /path/to/file/or/dir/regex[a-m]*$:
regex: True
- coalesce: True
The mask list can contain the following events (the default mask is create,
delete, and modify):
@ -203,8 +213,11 @@ def beacon(config):
affects all paths that are being watched. This is due to this option
being at the Notifier level in pyinotify.
'''
_config = {}
list(map(_config.update, config))
ret = []
notifier = _get_notifier(config)
notifier = _get_notifier(_config)
wm = notifier._watch_manager
# Read in existing events
@ -219,11 +232,13 @@ def beacon(config):
# Find the matching path in config
path = event.path
while path != '/':
if path in config:
if path in _config.get('files', {}):
break
path = os.path.dirname(path)
excludes = config[path].get('exclude', '')
for path in _config.get('files', {}):
excludes = _config['files'][path].get('exclude', '')
if excludes and isinstance(excludes, list):
for exclude in excludes:
if isinstance(exclude, dict):
@ -257,9 +272,10 @@ def beacon(config):
# Update existing watches and add new ones
# TODO: make the config handle more options
for path in config:
if isinstance(config[path], dict):
mask = config[path].get('mask', DEFAULT_MASK)
for path in _config.get('files', ()):
if isinstance(_config['files'][path], dict):
mask = _config['files'][path].get('mask', DEFAULT_MASK)
if isinstance(mask, list):
r_mask = 0
for sub in mask:
@ -269,8 +285,8 @@ def beacon(config):
else:
r_mask = mask
mask = r_mask
rec = config[path].get('recurse', False)
auto_add = config[path].get('auto_add', False)
rec = _config['files'][path].get('recurse', False)
auto_add = _config['files'][path].get('auto_add', False)
else:
mask = DEFAULT_MASK
rec = False
@ -287,7 +303,7 @@ def beacon(config):
if update:
wm.update_watch(wd, mask=mask, rec=rec, auto_add=auto_add)
elif os.path.exists(path):
excludes = config[path].get('exclude', '')
excludes = _config['files'][path].get('exclude', '')
excl = None
if isinstance(excludes, list):
excl = []

View file

@ -10,6 +10,7 @@ from __future__ import absolute_import
import salt.utils
import salt.utils.locales
import salt.ext.six
from salt.ext.six.moves import map
# Import third party libs
try:
@ -43,18 +44,21 @@ def _get_journal():
return __context__['systemd.journald']
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for journald beacon should be a list of dicts
if not isinstance(config, dict):
return False
if not isinstance(config, list):
return (False, 'Configuration for journald beacon must be a list.')
else:
for item in config:
if not isinstance(config[item], dict):
return False, ('Configuration for journald beacon must '
'be a dictionary of dictionaries.')
_config = {}
list(map(_config.update, config))
for name in _config.get('services', {}):
if not isinstance(_config['services'][name], dict):
return False, ('Services configuration for journald beacon '
'must be a list of dictionaries.')
return True, 'Valid beacon configuration'
@ -69,25 +73,31 @@ def beacon(config):
beacons:
journald:
sshd:
SYSLOG_IDENTIFIER: sshd
PRIORITY: 6
- services:
sshd:
SYSLOG_IDENTIFIER: sshd
PRIORITY: 6
'''
ret = []
journal = _get_journal()
_config = {}
list(map(_config.update, config))
while True:
cur = journal.get_next()
if not cur:
break
for name in config:
for name in _config.get('services', {}):
n_flag = 0
for key in config[name]:
for key in _config['services'][name]:
if isinstance(key, salt.ext.six.string_types):
key = salt.utils.locales.sdecode(key)
if key in cur:
if config[name][key] == cur[key]:
if _config['services'][name][key] == cur[key]:
n_flag += 1
if n_flag == len(config[name]):
if n_flag == len(_config['services'][name]):
# Match!
sub = salt.utils.simple_types_filter(cur)
sub.update({'tag': name})

View file

@ -10,6 +10,7 @@ import os
# Import Salt libs
import salt.utils.platform
from salt.ext.six.moves import map
# Import Py3 compat
from salt.ext.six.moves import zip
@ -28,7 +29,7 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
@ -37,25 +38,26 @@ def __validate__(config):
if not isinstance(config, list):
return False, ('Configuration for load beacon must be a list.')
else:
for config_item in config:
if not isinstance(config_item, dict):
return False, ('Configuration for load beacon must '
'be a list of dictionaries.')
else:
if not all(j in ['1m', '5m', '15m'] for j in config_item.keys()):
return False, ('Configuration for load beacon must '
'contain 1m, 5m or 15m items.')
_config = {}
list(map(_config.update, config))
if 'averages' not in _config:
return False, ('Averages configuration is required'
' for load beacon.')
else:
if not any(j in ['1m', '5m', '15m'] for j
in _config.get('averages', {})):
return False, ('Averages configuration for load beacon '
'must contain 1m, 5m or 15m items.')
for item in ['1m', '5m', '15m']:
if item not in config_item:
continue
if not isinstance(config_item[item], list):
return False, ('Configuration for load beacon: '
if not isinstance(_config['averages'][item], list):
return False, ('Averages configuration for load beacon: '
'1m, 5m and 15m items must be '
'a list of two items.')
else:
if len(config_item[item]) != 2:
if len(_config['averages'][item]) != 2:
return False, ('Configuration for load beacon: '
'1m, 5m and 15m items must be '
'a list of two items.')
@ -82,33 +84,37 @@ def beacon(config):
beacons:
load:
1m:
- 0.0
- 2.0
5m:
- 0.0
- 1.5
15m:
- 0.1
- 1.0
emitatstartup: True
onchangeonly: False
- averages:
1m:
- 0.0
- 2.0
5m:
- 0.0
- 1.5
15m:
- 0.1
- 1.0
- emitatstartup: True
- onchangeonly: False
'''
log.trace('load beacon starting')
_config = {}
list(map(_config.update, config))
# Default config if not present
if 'emitatstartup' not in config:
config['emitatstartup'] = True
if 'onchangeonly' not in config:
config['onchangeonly'] = False
if 'emitatstartup' not in _config:
_config['emitatstartup'] = True
if 'onchangeonly' not in _config:
_config['onchangeonly'] = False
ret = []
avgs = os.getloadavg()
avg_keys = ['1m', '5m', '15m']
avg_dict = dict(zip(avg_keys, avgs))
if config['onchangeonly']:
if _config['onchangeonly']:
if not LAST_STATUS:
for k in ['1m', '5m', '15m']:
LAST_STATUS[k] = avg_dict[k]
@ -120,27 +126,40 @@ def beacon(config):
# Check each entry for threshold
for k in ['1m', '5m', '15m']:
if k in config:
if config['onchangeonly']:
# Emit if current is more that threshold and old value less that threshold
if float(avg_dict[k]) > float(config[k][1]) and float(LAST_STATUS[k]) < float(config[k][1]):
log.debug('Emit because {0} > {1} and last was {2}'.format(float(avg_dict[k]), float(config[k][1]), float(LAST_STATUS[k])))
if k in _config.get('averages', {}):
if _config['onchangeonly']:
# Emit if current is more that threshold and old value less
# that threshold
if float(avg_dict[k]) > float(_config['averages'][k][1]) and \
float(LAST_STATUS[k]) < float(_config['averages'][k][1]):
log.debug('Emit because {0} > {1} and last was '
'{2}'.format(float(avg_dict[k]),
float(_config['averages'][k][1]),
float(LAST_STATUS[k])))
send_beacon = True
break
# Emit if current is less that threshold and old value more that threshold
if float(avg_dict[k]) < float(config[k][0]) and float(LAST_STATUS[k]) > float(config[k][0]):
log.debug('Emit because {0} < {1} and last was {2}'.format(float(avg_dict[k]), float(config[k][0]), float(LAST_STATUS[k])))
# Emit if current is less that threshold and old value more
# that threshold
if float(avg_dict[k]) < float(_config['averages'][k][0]) and \
float(LAST_STATUS[k]) > float(_config['averages'][k][0]):
log.debug('Emit because {0} < {1} and last was'
'{2}'.format(float(avg_dict[k]),
float(_config['averages'][k][0]),
float(LAST_STATUS[k])))
send_beacon = True
break
else:
# Emit no matter LAST_STATUS
if float(avg_dict[k]) < float(config[k][0]) or \
float(avg_dict[k]) > float(config[k][1]):
log.debug('Emit because {0} < {1} or > {2}'.format(float(avg_dict[k]), float(config[k][0]), float(config[k][1])))
if float(avg_dict[k]) < float(_config['averages'][k][0]) or \
float(avg_dict[k]) > float(_config['averages'][k][1]):
log.debug('Emit because {0} < {1} or > '
'{2}'.format(float(avg_dict[k]),
float(_config['averages'][k][0]),
float(_config['averages'][k][1])))
send_beacon = True
break
if config['onchangeonly']:
if _config['onchangeonly']:
for k in ['1m', '5m', '15m']:
LAST_STATUS[k] = avg_dict[k]

View file

@ -13,6 +13,7 @@ import logging
# Import salt libs
import salt.utils.files
import salt.utils.platform
from salt.ext.six.moves import map
try:
@ -48,13 +49,20 @@ def _get_loc():
return __context__[LOC_KEY]
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
_config = {}
list(map(_config.update, config))
# Configuration for log beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for log beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for log beacon must be a list.')
if 'file' not in _config:
return False, ('Configuration for log beacon '
'must contain file option.')
return True, 'Valid beacon configuration'
@ -67,20 +75,24 @@ def beacon(config):
beacons:
log:
file: <path>
<tag>:
regex: <pattern>
- file: <path>
- tags:
<tag>:
regex: <pattern>
'''
_config = {}
list(map(_config.update, config))
ret = []
if 'file' not in config:
if 'file' not in _config:
event = SKEL.copy()
event['tag'] = 'global'
event['error'] = 'file not defined in config'
ret.append(event)
return ret
with salt.utils.files.fopen(config['file'], 'r') as fp_:
with salt.utils.files.fopen(_config['file'], 'r') as fp_:
loc = __context__.get(LOC_KEY, 0)
if loc == 0:
fp_.seek(0, 2)
@ -92,16 +104,17 @@ def beacon(config):
fp_.seek(loc)
txt = fp_.read()
log.info('txt {}'.format(txt))
d = {}
for tag in config:
if 'regex' not in config[tag]:
for tag in _config.get('tags', {}):
if 'regex' not in _config['tags'][tag]:
continue
if len(config[tag]['regex']) < 1:
if len(_config['tags'][tag]['regex']) < 1:
continue
try:
d[tag] = re.compile(r'{0}'.format(config[tag]['regex']))
except Exception:
d[tag] = re.compile(r'{0}'.format(_config['tags'][tag]['regex']))
except Exception as e:
event = SKEL.copy()
event['tag'] = tag
event['error'] = 'bad regex'

View file

@ -11,6 +11,7 @@ Beacon to monitor memory usage.
from __future__ import absolute_import
import logging
import re
from salt.ext.six.moves import map
# Import Third Party Libs
try:
@ -31,14 +32,22 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for memusage beacon should be a list of dicts
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for memusage '
'beacon must be a dictionary.')
'beacon must be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'percent' not in _config:
return False, ('Configuration for memusage beacon '
'requires percent.')
return True, 'Valid beacon configuration'
@ -46,7 +55,8 @@ def beacon(config):
'''
Monitor the memory usage of the minion
Specify thresholds for percent used and only emit a beacon if it is exceeded.
Specify thresholds for percent used and only emit a beacon
if it is exceeded.
.. code-block:: yaml
@ -55,15 +65,17 @@ def beacon(config):
- percent: 63%
'''
ret = []
for memusage in config:
mount = memusage.keys()[0]
_current_usage = psutil.virtual_memory()
current_usage = _current_usage.percent
monitor_usage = memusage[mount]
if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage)
if current_usage >= monitor_usage:
ret.append({'memusage': current_usage})
_config = {}
list(map(_config.update, config))
_current_usage = psutil.virtual_memory()
current_usage = _current_usage.percent
monitor_usage = _config['percent']
if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage)
if current_usage >= monitor_usage:
ret.append({'memusage': current_usage})
return ret

View file

@ -16,6 +16,9 @@ try:
HAS_PSUTIL = True
except ImportError:
HAS_PSUTIL = False
from salt.ext.six.moves import map
# pylint: enable=import-error
log = logging.getLogger(__name__)
@ -45,7 +48,7 @@ def __virtual__():
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
@ -57,15 +60,19 @@ def __validate__(config):
]
# Configuration for load beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for load beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for network_info beacon must be a list.')
else:
for item in config:
if not isinstance(config[item], dict):
return False, ('Configuration for load beacon must '
'be a dictionary of dictionaries.')
_config = {}
list(map(_config.update, config))
for item in _config.get('interfaces', {}):
if not isinstance(_config['interfaces'][item], dict):
return False, ('Configuration for network_info beacon must '
'be a list of dictionaries.')
else:
if not any(j in VALID_ITEMS for j in config[item]):
if not any(j in VALID_ITEMS for j in _config['interfaces'][item]):
return False, ('Invalid configuration item in '
'Beacon configuration.')
return True, 'Valid beacon configuration'
@ -86,16 +93,17 @@ def beacon(config):
beacons:
network_info:
- eth0:
type: equal
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
- interfaces:
eth0:
type: equal
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
Emit beacon when any values are greater
than configured values.
@ -104,46 +112,53 @@ def beacon(config):
beacons:
network_info:
- eth0:
type: greater
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
- interfaces:
eth0:
type: greater
bytes_sent: 100000
bytes_recv: 100000
packets_sent: 100000
packets_recv: 100000
errin: 100
errout: 100
dropin: 100
dropout: 100
'''
ret = []
_config = {}
list(map(_config.update, config))
log.debug('psutil.net_io_counters {}'.format(psutil.net_io_counters))
_stats = psutil.net_io_counters(pernic=True)
for interface_config in config:
interface = interface_config.keys()[0]
log.debug('_stats {}'.format(_stats))
for interface in _config.get('interfaces', {}):
if interface in _stats:
interface_config = _config['interfaces'][interface]
_if_stats = _stats[interface]
_diff = False
for attr in __attrs:
if attr in interface_config[interface]:
if 'type' in interface_config[interface] and \
interface_config[interface]['type'] == 'equal':
if attr in interface_config:
if 'type' in interface_config and \
interface_config['type'] == 'equal':
if getattr(_if_stats, attr, None) == \
int(interface_config[interface][attr]):
int(interface_config[attr]):
_diff = True
elif 'type' in interface_config[interface] and \
interface_config[interface]['type'] == 'greater':
elif 'type' in interface_config and \
interface_config['type'] == 'greater':
if getattr(_if_stats, attr, None) > \
int(interface_config[interface][attr]):
int(interface_config[attr]):
_diff = True
else:
log.debug('attr {}'.format(getattr(_if_stats,
attr, None)))
else:
if getattr(_if_stats, attr, None) == \
int(interface_config[interface][attr]):
int(interface_config[attr]):
_diff = True
if _diff:
ret.append({'interface': interface,

View file

@ -18,6 +18,8 @@ import ast
import re
import salt.loader
import logging
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
__virtual_name__ = 'network_settings'
@ -45,22 +47,23 @@ def __virtual__():
return False
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for network_settings '
'beacon must be a dictionary.')
'beacon must be a list.')
else:
for item in config:
if item == 'coalesce':
continue
if not isinstance(config[item], dict):
return False, ('Configuration for network_settings beacon must be a '
'dictionary of dictionaries.')
_config = {}
list(map(_config.update, config))
for item in _config.get('interfaces', {}):
if not isinstance(_config['interfaces'][item], dict):
return False, ('Configuration for network_settings beacon '
' must be a list of dictionaries.')
else:
if not all(j in ATTRS for j in config[item]):
if not all(j in ATTRS for j in _config['interfaces'][item]):
return False, ('Invalid configuration item in Beacon '
'configuration.')
return True, 'Valid beacon configuration'
@ -75,9 +78,10 @@ def _copy_interfaces_info(interfaces):
for interface in interfaces:
_interface_attrs_cpy = set()
for attr in ATTRS:
attr_dict = Hashabledict()
attr_dict[attr] = repr(interfaces[interface][attr])
_interface_attrs_cpy.add(attr_dict)
if attr in interfaces[interface]:
attr_dict = Hashabledict()
attr_dict[attr] = repr(interfaces[interface][attr])
_interface_attrs_cpy.add(attr_dict)
ret[interface] = _interface_attrs_cpy
return ret
@ -89,8 +93,8 @@ def beacon(config):
By default, the beacon will emit when there is a value change on one of the
settings on watch. The config also support the onvalue parameter for each
setting, which instruct the beacon to only emit if the setting changed to the
value defined.
setting, which instruct the beacon to only emit if the setting changed to
the value defined.
Example Config
@ -98,12 +102,13 @@ def beacon(config):
beacons:
network_settings:
eth0:
ipaddr:
promiscuity:
onvalue: 1
eth1:
linkmode:
- interfaces:
- eth0:
ipaddr:
promiscuity:
onvalue: 1
- eth1:
linkmode:
The config above will check for value changes on eth0 ipaddr and eth1 linkmode. It will also
emit if the promiscuity value changes to 1.
@ -118,12 +123,16 @@ def beacon(config):
beacons:
network_settings:
coalesce: True
eth0:
ipaddr:
promiscuity:
- coalesce: True
- interfaces:
- eth0:
ipaddr:
promiscuity:
'''
_config = {}
list(map(_config.update, config))
ret = []
interfaces = []
expanded_config = {}
@ -137,45 +146,51 @@ def beacon(config):
if not LAST_STATS:
LAST_STATS = _stats
if 'coalesce' in config and config['coalesce']:
if 'coalesce' in _config and _config['coalesce']:
coalesce = True
changes = {}
log.debug('_stats {}'.format(_stats))
# Get list of interfaces included in config that are registered in the
# system, including interfaces defined by wildcards (eth*, wlan*)
for item in config:
if item == 'coalesce':
continue
if item in _stats:
interfaces.append(item)
for interface in _config.get('interfaces', {}):
if interface in _stats:
interfaces.append(interface)
else:
# No direct match, try with * wildcard regexp
interface_regexp = item.replace('*', '[0-9]+')
interface_regexp = interface.replace('*', '[0-9]+')
for interface in _stats:
match = re.search(interface_regexp, interface)
if match:
interfaces.append(match.group())
expanded_config[match.group()] = config[item]
expanded_config[match.group()] = config['interfaces'][interface]
if expanded_config:
config.update(expanded_config)
# config updated so update _config
list(map(_config.update, config))
log.debug('interfaces {}'.format(interfaces))
for interface in interfaces:
_send_event = False
_diff_stats = _stats[interface] - LAST_STATS[interface]
_ret_diff = {}
interface_config = _config['interfaces'][interface]
log.debug('_diff_stats {}'.format(_diff_stats))
if _diff_stats:
_diff_stats_dict = {}
LAST_STATS[interface] = _stats[interface]
for item in _diff_stats:
_diff_stats_dict.update(item)
for attr in config[interface]:
for attr in interface_config:
if attr in _diff_stats_dict:
config_value = None
if config[interface][attr] and 'onvalue' in config[interface][attr]:
config_value = config[interface][attr]['onvalue']
if interface_config[attr] and \
'onvalue' in interface_config[attr]:
config_value = interface_config[attr]['onvalue']
new_value = ast.literal_eval(_diff_stats_dict[attr])
if not config_value or config_value == new_value:
_send_event = True
@ -185,7 +200,9 @@ def beacon(config):
if coalesce:
changes[interface] = _ret_diff
else:
ret.append({'tag': interface, 'interface': interface, 'change': _ret_diff})
ret.append({'tag': interface,
'interface': interface,
'change': _ret_diff})
if coalesce and changes:
grains_info = salt.loader.grains(__opts__, True)

View file

@ -21,15 +21,25 @@ def __virtual__():
return __virtualname__ if 'pkg.upgrade_available' in __salt__ else False
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for pkg beacon should be a list
if not isinstance(config, dict):
return False, ('Configuration for pkg beacon must be a dictionary.')
if 'pkgs' not in config:
return False, ('Configuration for pkg beacon requires list of pkgs.')
if not isinstance(config, list):
return False, ('Configuration for pkg beacon must be a list.')
# Configuration for pkg beacon should contain pkgs
pkgs_found = False
pkgs_not_list = False
for config_item in config:
if 'pkgs' in config_item:
pkgs_found = True
if isinstance(config_item['pkgs'], list):
pkgs_not_list = True
if not pkgs_found or not pkgs_not_list:
return False, 'Configuration for pkg beacon requires list of pkgs.'
return True, 'Valid beacon configuration'
@ -48,14 +58,16 @@ def beacon(config):
- refresh: True
'''
ret = []
_validate = __validate__(config)
if not _validate[0]:
return ret
_refresh = False
if 'refresh' in config and config['refresh']:
_refresh = True
for pkg in config['pkgs']:
pkgs = []
for config_item in config:
if 'pkgs' in config_item:
pkgs += config_item['pkgs']
if 'refresh' in config and config['refresh']:
_refresh = True
for pkg in pkgs:
_installed = __salt__['pkg.version'](pkg)
_latest = __salt__['pkg.latest_version'](pkg, refresh=_refresh)
if _installed and _latest:

View file

@ -15,6 +15,7 @@ import logging
# Import salt libs
import salt.utils.http
from salt.ext.six.moves import map
# Important: If used with salt-proxy
# this is required for the beacon to load!!!
@ -33,12 +34,12 @@ def __virtual__():
return True
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
return False, ('Configuration for rest_example beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for proxy_example beacon must be a list.')
return True, 'Valid beacon configuration'
@ -51,7 +52,7 @@ def beacon(config):
beacons:
proxy_example:
endpoint: beacon
- endpoint: beacon
'''
# Important!!!
# Although this toy example makes an HTTP call
@ -59,8 +60,11 @@ def beacon(config):
# please be advised that doing CPU or IO intensive
# operations in this method will cause the beacon loop
# to block.
_config = {}
list(map(_config.update, config))
beacon_url = '{0}{1}'.format(__opts__['proxy']['url'],
config['endpoint'])
_config['endpoint'])
ret = salt.utils.http.query(beacon_url,
decode_type='json',
decode=True)

View file

@ -14,6 +14,9 @@ try:
HAS_PSUTIL = True
except ImportError:
HAS_PSUTIL = False
from salt.ext.six.moves import map
# pylint: enable=import-error
log = logging.getLogger(__name__) # pylint: disable=invalid-name
@ -23,17 +26,27 @@ __virtualname__ = 'ps'
def __virtual__():
if not HAS_PSUTIL:
return (False, 'cannot load network_info beacon: psutil not available')
return (False, 'cannot load ps beacon: psutil not available')
return __virtualname__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for ps beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for ps beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for ps beacon must be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'processes' not in _config:
return False, ('Configuration for ps beacon requires processes.')
else:
if not isinstance(_config['processes'], dict):
return False, ('Processes for ps beacon must be a dictionary.')
return True, 'Valid beacon configuration'
@ -47,8 +60,9 @@ def beacon(config):
beacons:
ps:
- salt-master: running
- mysql: stopped
- processes:
salt-master: running
mysql: stopped
The config above sets up beacons to check that
processes are running or stopped.
@ -60,19 +74,21 @@ def beacon(config):
if _name not in procs:
procs.append(_name)
for entry in config:
for process in entry:
ret_dict = {}
if entry[process] == 'running':
if process in procs:
ret_dict[process] = 'Running'
ret.append(ret_dict)
elif entry[process] == 'stopped':
if process not in procs:
ret_dict[process] = 'Stopped'
ret.append(ret_dict)
else:
if process not in procs:
ret_dict[process] = False
ret.append(ret_dict)
_config = {}
list(map(_config.update, config))
for process in _config.get('processes', {}):
ret_dict = {}
if _config['processes'][process] == 'running':
if process in procs:
ret_dict[process] = 'Running'
ret.append(ret_dict)
elif _config['processes'][process] == 'stopped':
if process not in procs:
ret_dict[process] = 'Stopped'
ret.append(ret_dict)
else:
if process not in procs:
ret_dict[process] = False
ret.append(ret_dict)
return ret

View file

@ -9,6 +9,7 @@
# Import python libs
from __future__ import absolute_import
import logging
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
@ -20,9 +21,7 @@ def _run_proxy_processes(proxies):
aren't running
'''
ret = []
for prox_ in proxies:
# prox_ is a dict
proxy = prox_.keys()[0]
for proxy in proxies:
result = {}
if not __salt__['salt_proxy.is_running'](proxy)['result']:
__salt__['salt_proxy.configure_proxy'](proxy, start=True)
@ -35,7 +34,29 @@ def _run_proxy_processes(proxies):
return ret
def beacon(proxies):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for adb beacon should be a dictionary with states array
if not isinstance(config, list):
log.info('Configuration for salt_proxy beacon must be a list.')
return False, ('Configuration for salt_proxy beacon must be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'proxies' not in _config:
return False, ('Configuration for salt_proxy'
' beacon requires proxies.')
else:
if not isinstance(_config['proxies'], dict):
return False, ('Proxies for salt_proxy '
'beacon must be a dictionary.')
def beacon(config):
'''
Handle configured proxies
@ -43,9 +64,13 @@ def beacon(proxies):
beacons:
salt_proxy:
- p8000: {}
- p8001: {}
- proxies:
p8000: {}
p8001: {}
'''
log.trace('salt proxy beacon called')
return _run_proxy_processes(proxies)
_config = {}
list(map(_config.update, config))
return _run_proxy_processes(_config['proxies'])

View file

@ -11,6 +11,7 @@ of a Raspberry Pi.
from __future__ import absolute_import
import logging
import re
from salt.ext.six.moves import map
log = logging.getLogger(__name__)
@ -19,14 +20,22 @@ def __virtual__():
return 'sensehat.get_pressure' in __salt__
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for sensehat beacon should be a dict
if not isinstance(config, dict):
# Configuration for sensehat beacon should be a list
if not isinstance(config, list):
return False, ('Configuration for sensehat beacon '
'must be a dictionary.')
'must be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'sensors' not in _config:
return False, ('Configuration for sensehat'
' beacon requires sensors.')
return True, 'Valid beacon configuration'
@ -48,10 +57,11 @@ def beacon(config):
beacons:
sensehat:
humidity: 70%
temperature: [20, 40]
temperature_from_pressure: 40
pressure: 1500
- sensors:
humidity: 70%
temperature: [20, 40]
temperature_from_pressure: 40
pressure: 1500
'''
ret = []
min_default = {
@ -60,13 +70,16 @@ def beacon(config):
'temperature': '-273.15'
}
for sensor in config:
_config = {}
list(map(_config.update, config))
for sensor in _config.get('sensors', {}):
sensor_function = 'sensehat.get_{0}'.format(sensor)
if sensor_function not in __salt__:
log.error('No sensor for meassuring {0}. Skipping.'.format(sensor))
continue
sensor_config = config[sensor]
sensor_config = _config['sensors'][sensor]
if isinstance(sensor_config, list):
sensor_min = str(sensor_config[0])
sensor_max = str(sensor_config[1])

View file

@ -9,19 +9,35 @@ from __future__ import absolute_import
import os
import logging
import time
from salt.ext.six.moves import map
log = logging.getLogger(__name__) # pylint: disable=invalid-name
LAST_STATUS = {}
__virtualname__ = 'service'
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for service beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for service beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for service beacon must be a list.')
else:
_config = {}
list(map(_config.update, config))
if 'services' not in _config:
return False, ('Configuration for service beacon'
' requires services.')
else:
for config_item in _config['services']:
if not isinstance(_config['services'][config_item], dict):
return False, ('Configuration for service beacon must '
'be a list of dictionaries.')
return True, 'Valid beacon configuration'
@ -35,8 +51,9 @@ def beacon(config):
beacons:
service:
salt-master:
mysql:
- services:
salt-master:
mysql:
The config above sets up beacons to check for
the salt-master and mysql services.
@ -79,14 +96,21 @@ def beacon(config):
beacons:
service:
nginx:
onchangeonly: True
delay: 30
uncleanshutdown: /run/nginx.pid
- services:
nginx:
onchangeonly: True
delay: 30
uncleanshutdown: /run/nginx.pid
'''
ret = []
for service in config:
_config = {}
list(map(_config.update, config))
for service in _config.get('services', {}):
ret_dict = {}
service_config = _config['services'][service]
ret_dict[service] = {'running': __salt__['service.status'](service)}
ret_dict['service_name'] = service
ret_dict['tag'] = service
@ -95,40 +119,43 @@ def beacon(config):
# If no options is given to the service, we fall back to the defaults
# assign a False value to oncleanshutdown and onchangeonly. Those
# key:values are then added to the service dictionary.
if 'oncleanshutdown' not in config[service]:
config[service]['oncleanshutdown'] = False
if 'emitatstartup' not in config[service]:
config[service]['emitatstartup'] = True
if 'onchangeonly' not in config[service]:
config[service]['onchangeonly'] = False
if 'delay' not in config[service]:
config[service]['delay'] = 0
if not service_config:
service_config = {}
if 'oncleanshutdown' not in service_config:
service_config['oncleanshutdown'] = False
if 'emitatstartup' not in service_config:
service_config['emitatstartup'] = True
if 'onchangeonly' not in service_config:
service_config['onchangeonly'] = False
if 'delay' not in service_config:
service_config['delay'] = 0
# We only want to report the nature of the shutdown
# if the current running status is False
# as well as if the config for the beacon asks for it
if 'uncleanshutdown' in config[service] and not ret_dict[service]['running']:
filename = config[service]['uncleanshutdown']
if 'uncleanshutdown' in service_config and not ret_dict[service]['running']:
filename = service_config['uncleanshutdown']
ret_dict[service]['uncleanshutdown'] = True if os.path.exists(filename) else False
if 'onchangeonly' in config[service] and config[service]['onchangeonly'] is True:
if 'onchangeonly' in service_config and service_config['onchangeonly'] is True:
if service not in LAST_STATUS:
LAST_STATUS[service] = ret_dict[service]
if config[service]['delay'] > 0:
if service_config['delay'] > 0:
LAST_STATUS[service]['time'] = currtime
elif not config[service]['emitatstartup']:
elif not service_config['emitatstartup']:
continue
else:
ret.append(ret_dict)
if LAST_STATUS[service]['running'] != ret_dict[service]['running']:
LAST_STATUS[service] = ret_dict[service]
if config[service]['delay'] > 0:
if service_config['delay'] > 0:
LAST_STATUS[service]['time'] = currtime
else:
ret.append(ret_dict)
if 'time' in LAST_STATUS[service]:
elapsedtime = int(round(currtime - LAST_STATUS[service]['time']))
if elapsedtime > config[service]['delay']:
if elapsedtime > service_config['delay']:
del LAST_STATUS[service]['time']
ret.append(ret_dict)
else:

View file

@ -41,13 +41,13 @@ def _get_shells():
return __context__['sh.shells']
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for sh beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for sh beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for sh beacon must be a list.')
return True, 'Valid beacon configuration'
@ -58,7 +58,7 @@ def beacon(config):
.. code-block:: yaml
beacons:
sh: {}
sh: []
'''
ret = []
pkey = 'sh.vt'

View file

@ -15,7 +15,7 @@ the minion config:
.. code-block:: yaml
beacons:
status: {}
status: []
By default, all of the information from the following execution module
functions will be returned:
@ -103,12 +103,12 @@ log = logging.getLogger(__name__)
__virtualname__ = 'status'
def __validate__(config):
def validate(config):
'''
Validate the the config is a dict
'''
if not isinstance(config, dict):
return False, ('Configuration for status beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for status beacon must be a list.')
return True, 'Valid beacon configuration'

View file

@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
'''
Beacon to emit Telegram messages
Requires the python-telegram-bot library
'''
# Import Python libs
from __future__ import absolute_import
import logging
from salt.ext.six.moves import map
# Import 3rd Party libs
try:
@ -28,20 +32,23 @@ def __virtual__():
return False
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for telegram_bot_msg '
'beacon must be a dictionary.')
'beacon must be a list.')
if not all(config.get(required_config)
_config = {}
list(map(_config.update, config))
if not all(_config.get(required_config)
for required_config in ['token', 'accept_from']):
return False, ('Not all required configuration for '
'telegram_bot_msg are set.')
if not isinstance(config.get('accept_from'), list):
if not isinstance(_config.get('accept_from'), list):
return False, ('Configuration for telegram_bot_msg, '
'accept_from must be a list of usernames.')
@ -57,18 +64,22 @@ def beacon(config):
beacons:
telegram_bot_msg:
token: "<bot access token>"
accept_from:
- token: "<bot access token>"
- accept_from:
- "<valid username>"
interval: 10
- interval: 10
'''
_config = {}
list(map(_config.update, config))
log.debug('telegram_bot_msg beacon starting')
ret = []
output = {}
output['msgs'] = []
bot = telegram.Bot(config['token'])
bot = telegram.Bot(_config['token'])
updates = bot.get_updates(limit=100, timeout=0, network_delay=10)
log.debug('Num updates: {0}'.format(len(updates)))
@ -83,7 +94,7 @@ def beacon(config):
if update.update_id > latest_update_id:
latest_update_id = update.update_id
if message.chat.username in config['accept_from']:
if message.chat.username in _config['accept_from']:
output['msgs'].append(message.to_dict())
# mark in the server that previous messages are processed

View file

@ -6,10 +6,17 @@ Beacon to emit Twilio text messages
# Import Python libs
from __future__ import absolute_import
import logging
from salt.ext.six.moves import map
# Import 3rd Party libs
try:
from twilio.rest import TwilioRestClient
import twilio
# Grab version, ensure elements are ints
twilio_version = tuple([int(x) for x in twilio.__version_info__])
if twilio_version > (5, ):
from twilio.rest import Client as TwilioRestClient
else:
from twilio.rest import TwilioRestClient
HAS_TWILIO = True
except ImportError:
HAS_TWILIO = False
@ -26,14 +33,24 @@ def __virtual__():
return False
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for twilio_txt_msg beacon should be a list of dicts
if not isinstance(config, dict):
if not isinstance(config, list):
return False, ('Configuration for twilio_txt_msg beacon '
'must be a dictionary.')
'must be a list.')
else:
_config = {}
list(map(_config.update, config))
if not all(x in _config for x in ('account_sid',
'auth_token',
'twilio_number')):
return False, ('Configuration for twilio_txt_msg beacon '
'must contain account_sid, auth_token '
'and twilio_number items.')
return True, 'Valid beacon configuration'
@ -46,20 +63,26 @@ def beacon(config):
beacons:
twilio_txt_msg:
account_sid: "<account sid>"
auth_token: "<auth token>"
twilio_number: "+15555555555"
interval: 10
- account_sid: "<account sid>"
- auth_token: "<auth token>"
- twilio_number: "+15555555555"
- interval: 10
'''
log.trace('twilio_txt_msg beacon starting')
_config = {}
list(map(_config.update, config))
ret = []
if not all([config['account_sid'], config['auth_token'], config['twilio_number']]):
if not all([_config['account_sid'],
_config['auth_token'],
_config['twilio_number']]):
return ret
output = {}
output['texts'] = []
client = TwilioRestClient(config['account_sid'], config['auth_token'])
messages = client.messages.list(to=config['twilio_number'])
client = TwilioRestClient(_config['account_sid'], _config['auth_token'])
messages = client.messages.list(to=_config['twilio_number'])
log.trace('Num messages: {0}'.format(len(messages)))
if len(messages) < 1:
log.trace('Twilio beacon has no texts')

View file

@ -5,7 +5,7 @@ Beacon to fire events at login of users as registered in the wtmp file
.. code-block:: yaml
beacons:
wtmp: {}
wtmp: []
'''
# Import Python libs
@ -55,13 +55,13 @@ def _get_loc():
return __context__[LOC_KEY]
def __validate__(config):
def validate(config):
'''
Validate the beacon configuration
'''
# Configuration for wtmp beacon should be a list of dicts
if not isinstance(config, dict):
return False, ('Configuration for wtmp beacon must be a dictionary.')
if not isinstance(config, list):
return False, ('Configuration for wtmp beacon must be a list.')
return True, 'Valid beacon configuration'
@ -73,7 +73,7 @@ def beacon(config):
.. code-block:: yaml
beacons:
wtmp: {}
wtmp: []
'''
ret = []
with salt.utils.files.fopen(WTMP, 'rb') as fp_:

View file

@ -39,6 +39,7 @@ import salt.utils.files
import salt.utils.minions
import salt.utils.platform
import salt.utils.verify
import salt.utils.versions
import salt.utils.jid
import salt.syspaths as syspaths
from salt.exceptions import (
@ -314,7 +315,7 @@ class LocalClient(object):
{'jid': '20131219215650131543', 'minions': ['jerry']}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -378,7 +379,7 @@ class LocalClient(object):
{'jid': '20131219215650131543', 'minions': ['jerry']}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -435,7 +436,7 @@ class LocalClient(object):
'20131219215921857715'
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -482,7 +483,7 @@ class LocalClient(object):
{'jerry': True}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -545,7 +546,7 @@ class LocalClient(object):
{'stewart': {...}}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -703,7 +704,7 @@ class LocalClient(object):
function name.
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -772,7 +773,7 @@ class LocalClient(object):
:returns: A generator
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -855,7 +856,7 @@ class LocalClient(object):
{'stewart': {'ret': True}}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -931,7 +932,7 @@ class LocalClient(object):
{'stewart': {'ret': True}}
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -988,7 +989,7 @@ class LocalClient(object):
Execute a salt command and return
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -1039,7 +1040,7 @@ class LocalClient(object):
:returns: all of the information for the JID
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -1118,7 +1119,7 @@ class LocalClient(object):
:returns: all of the information for the JID
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -1558,7 +1559,7 @@ class LocalClient(object):
log.trace(u'func get_cli_event_returns()')
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -1750,7 +1751,7 @@ class LocalClient(object):
A set, the targets that the tgt passed should match.
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -1858,7 +1859,7 @@ class LocalClient(object):
A set, the targets that the tgt passed should match.
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '

View file

@ -25,6 +25,7 @@ import salt.utils.job
import salt.utils.lazy
import salt.utils.platform
import salt.utils.process
import salt.utils.versions
import salt.transport
import salt.log.setup
from salt.ext import six
@ -253,7 +254,7 @@ class SyncClientMixin(object):
low[u'kwarg'] = low.pop(u'kwargs')
if msg:
salt.utils.warn_until(u'Oxygen', u' '.join(msg))
salt.utils.versions.warn_until(u'Oxygen', u' '.join(msg))
return self._low(fun, low, print_event=print_event, full_return=full_return)
@ -407,8 +408,6 @@ class SyncClientMixin(object):
)
data[u'success'] = False
namespaced_event.fire_event(data, u'ret')
if self.store_job:
try:
salt.utils.job.store_job(
@ -426,6 +425,9 @@ class SyncClientMixin(object):
log.error(u'Could not store job cache info. '
u'Job details for this run may be unavailable.')
# Outputters _can_ mutate data so write to the job cache first!
namespaced_event.fire_event(data, u'ret')
# 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(u'Runner completed: %s', data[u'jid'])
@ -443,6 +445,7 @@ class SyncClientMixin(object):
_use_fnmatch = True
else:
target_mod = arg + u'.' if not arg.endswith(u'.') else arg
_use_fnmatch = False
if _use_fnmatch:
docs = [(fun, self.functions[fun].__doc__)
for fun in fnmatch.filter(self.functions, target_mod)]

View file

@ -12,8 +12,8 @@ import logging
# Import Salt libs
import salt.config
import salt.client
import salt.utils
import salt.utils.kinds as kinds
import salt.utils.versions
import salt.syspaths as syspaths
try:
@ -50,7 +50,7 @@ class LocalClient(salt.client.LocalClient):
Publish the command!
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '

View file

@ -9,6 +9,7 @@ import random
# Import Salt libs
import salt.config
import salt.utils.versions
import salt.syspaths as syspaths
from salt.exceptions import SaltClientError # Temporary
@ -52,7 +53,7 @@ class SSHClient(object):
Prepare the arguments
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -88,7 +89,7 @@ class SSHClient(object):
.. versionadded:: 2015.5.0
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -122,7 +123,7 @@ class SSHClient(object):
.. versionadded:: 2015.5.0
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -226,7 +227,7 @@ class SSHClient(object):
.. versionadded:: 2017.7.0
'''
if u'expr_form' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'The target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '

View file

@ -17,6 +17,8 @@ import logging
# Import salt libs
import salt.client.ssh
import salt.runner
import salt.utils.args
import salt.utils.versions
log = logging.getLogger(__name__)
@ -173,7 +175,7 @@ def publish(tgt,
# remember to remove the expr_form argument from this function when
# performing the cleanup on this deprecation.
if expr_form is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'the target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '
@ -223,7 +225,7 @@ def full_data(tgt,
# remember to remove the expr_form argument from this function when
# performing the cleanup on this deprecation.
if expr_form is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Fluorine',
u'the target type should be passed using the \'tgt_type\' '
u'argument instead of \'expr_form\'. Support for using '

View file

@ -729,18 +729,9 @@ class Cloud(object):
continue
for vm_name, details in six.iteritems(vms):
# If VM was created with use_fqdn with either of the softlayer drivers,
# we need to strip the VM name and only search for the short hostname.
if driver == 'softlayer' or driver == 'softlayer_hw':
ret = []
for name in names:
name = name.split('.')[0]
ret.append(name)
if vm_name not in ret:
continue
# XXX: The logic below can be removed once the aws driver
# is removed
elif vm_name not in names:
if vm_name not in names:
continue
elif driver == 'ec2' and 'aws' in handled_drivers and \

View file

@ -0,0 +1,849 @@
# -*- coding: utf-8 -*-
'''
1&1 Cloud Server Module
=======================
=======
The 1&1 SaltStack cloud module allows a 1&1 server to
be automatically deployed and bootstrapped with Salt.
:depends: 1and1 >= 1.2.0
The module requires the 1&1 api_token to be provided.
The server should also be assigned a public LAN, a private LAN,
or both along with SSH key pairs.
...
Set up the cloud configuration at ``/etc/salt/cloud.providers`` or
``/etc/salt/cloud.providers.d/oneandone.conf``:
.. code-block:: yaml
my-oneandone-config:
driver: oneandone
# The 1&1 api token
api_token: <your-token>
# SSH private key filename
ssh_private_key: /path/to/private_key
# SSH public key filename
ssh_public_key: /path/to/public_key
.. code-block:: yaml
my-oneandone-profile:
provider: my-oneandone-config
# Either provide fixed_instance_size_id or vcore, cores_per_processor, ram, and hdds.
# Size of the ID desired for the server
fixed_instance_size: S
# Total amount of processors
vcore: 2
# Number of cores per processor
cores_per_processor: 2
# RAM memory size in GB
ram: 4
# Hard disks
hdds:
-
is_main: true
size: 20
-
is_main: false
size: 20
# ID of the appliance image that will be installed on server
appliance_id: <ID>
# ID of the datacenter where the server will be created
datacenter_id: <ID>
# Description of the server
description: My server description
# Password of the server. Password must contain more than 8 characters
# using uppercase letters, numbers and other special symbols.
password: P4$$w0rD
# Power on server after creation - default True
power_on: true
# Firewall policy ID. If it is not provided, the server will assign
# the best firewall policy, creating a new one if necessary.
# If the parameter is sent with a 0 value, the server will be created with all ports blocked.
firewall_policy_id: <ID>
# IP address ID
ip_id: <ID>
# Load balancer ID
load_balancer_id: <ID>
# Monitoring policy ID
monitoring_policy_id: <ID>
Set ``deploy`` to False if Salt should not be installed on the node.
.. code-block:: yaml
my-oneandone-profile:
deploy: False
'''
# Import python libs
from __future__ import absolute_import
import logging
import os
import pprint
import time
# Import salt libs
import salt.utils
import salt.config as config
from salt.exceptions import (
SaltCloudConfigError,
SaltCloudNotFound,
SaltCloudExecutionFailure,
SaltCloudExecutionTimeout,
SaltCloudSystemExit
)
# Import salt.cloud libs
import salt.utils.cloud
from salt.ext import six
try:
from oneandone.client import (
OneAndOneService, Server, Hdd
)
HAS_ONEANDONE = True
except ImportError:
HAS_ONEANDONE = False
# Get logging started
log = logging.getLogger(__name__)
__virtualname__ = 'oneandone'
# Only load in this module if the 1&1 configurations are in place
def __virtual__():
'''
Check for 1&1 configurations.
'''
if get_configured_provider() is False:
return False
if get_dependencies() is False:
return False
return __virtualname__
def get_configured_provider():
'''
Return the first configured instance.
'''
return config.is_provider_configured(
__opts__,
__active_provider_name__ or __virtualname__,
('api_token',)
)
def get_dependencies():
'''
Warn if dependencies are not met.
'''
return config.check_driver_dependencies(
__virtualname__,
{'oneandone': HAS_ONEANDONE}
)
def get_conn():
'''
Return a conn object for the passed VM data
'''
return OneAndOneService(
api_token=config.get_cloud_config_value(
'api_token',
get_configured_provider(),
__opts__,
search_global=False
)
)
def get_size(vm_):
'''
Return the VM's size object
'''
vm_size = config.get_cloud_config_value(
'fixed_instance_size', vm_, __opts__, default=None,
search_global=False
)
sizes = avail_sizes()
if not vm_size:
size = next((item for item in sizes if item['name'] == 'S'), None)
return size
size = next((item for item in sizes if item['name'] == vm_size or item['id'] == vm_size), None)
if size:
return size
raise SaltCloudNotFound(
'The specified size, \'{0}\', could not be found.'.format(vm_size)
)
def get_image(vm_):
'''
Return the image object to use
'''
vm_image = config.get_cloud_config_value('image', vm_, __opts__).encode(
'ascii', 'salt-cloud-force-ascii'
)
images = avail_images()
for key, value in six.iteritems(images):
if vm_image and vm_image in (images[key]['id'], images[key]['name']):
return images[key]
raise SaltCloudNotFound(
'The specified image, \'{0}\', could not be found.'.format(vm_image)
)
def avail_locations(conn=None, call=None):
'''
List available locations/datacenters for 1&1
'''
if call == 'action':
raise SaltCloudSystemExit(
'The avail_locations function must be called with '
'-f or --function, or with the --list-locations option'
)
datacenters = []
if not conn:
conn = get_conn()
for datacenter in conn.list_datacenters():
datacenters.append({datacenter['country_code']: datacenter})
return {'Locations': datacenters}
def avail_images(conn=None, call=None):
'''
Return a list of the server appliances that are on the provider
'''
if call == 'action':
raise SaltCloudSystemExit(
'The avail_images function must be called with '
'-f or --function, or with the --list-images option'
)
if not conn:
conn = get_conn()
ret = {}
for appliance in conn.list_appliances():
ret[appliance['name']] = appliance
return ret
def avail_sizes(call=None):
'''
Return a dict of all available VM sizes on the cloud provider with
relevant data.
'''
if call == 'action':
raise SaltCloudSystemExit(
'The avail_sizes function must be called with '
'-f or --function, or with the --list-sizes option'
)
conn = get_conn()
sizes = conn.fixed_server_flavors()
return sizes
def script(vm_):
'''
Return the script deployment object
'''
return salt.utils.cloud.os_script(
config.get_cloud_config_value('script', vm_, __opts__),
vm_,
__opts__,
salt.utils.cloud.salt_config_to_yaml(
salt.utils.cloud.minion_config(__opts__, vm_)
)
)
def list_nodes(conn=None, call=None):
'''
Return a list of VMs that are on the provider
'''
if call == 'action':
raise SaltCloudSystemExit(
'The list_nodes function must be called with -f or --function.'
)
if not conn:
conn = get_conn()
ret = {}
nodes = conn.list_servers()
for node in nodes:
public_ips = []
private_ips = []
ret = {}
size = node.get('hardware').get('fixed_instance_size_id', 'Custom size')
if node.get('private_networks') and len(node['private_networks']) > 0:
for private_ip in node['private_networks']:
private_ips.append(private_ip)
if node.get('ips') and len(node['ips']) > 0:
for public_ip in node['ips']:
public_ips.append(public_ip['ip'])
server = {
'id': node['id'],
'image': node['image']['id'],
'size': size,
'state': node['status']['state'],
'private_ips': private_ips,
'public_ips': public_ips
}
ret[node['name']] = server
return ret
def list_nodes_full(conn=None, call=None):
'''
Return a list of the VMs that are on the provider, with all fields
'''
if call == 'action':
raise SaltCloudSystemExit(
'The list_nodes_full function must be called with -f or '
'--function.'
)
if not conn:
conn = get_conn()
ret = {}
nodes = conn.list_servers()
for node in nodes:
ret[node['name']] = node
return ret
def list_nodes_select(conn=None, call=None):
'''
Return a list of the VMs that are on the provider, with select fields
'''
if not conn:
conn = get_conn()
return salt.utils.cloud.list_nodes_select(
list_nodes_full(conn, 'function'),
__opts__['query.selection'],
call,
)
def show_instance(name, call=None):
'''
Show the details from the provider concerning an instance
'''
if call != 'action':
raise SaltCloudSystemExit(
'The show_instance action must be called with -a or --action.'
)
nodes = list_nodes_full()
__utils__['cloud.cache_node'](
nodes[name],
__active_provider_name__,
__opts__
)
return nodes[name]
def _get_server(vm_):
'''
Construct server instance from cloud profile config
'''
description = config.get_cloud_config_value(
'description', vm_, __opts__, default=None,
search_global=False
)
ssh_key = load_public_key(vm_)
vcore = None
cores_per_processor = None
ram = None
fixed_instance_size_id = None
if 'fixed_instance_size' in vm_:
fixed_instance_size = get_size(vm_)
fixed_instance_size_id = fixed_instance_size['id']
elif (vm_['vcore'] and vm_['cores_per_processor'] and
vm_['ram'] and vm_['hdds']):
vcore = config.get_cloud_config_value(
'vcore', vm_, __opts__, default=None,
search_global=False
)
cores_per_processor = config.get_cloud_config_value(
'cores_per_processor', vm_, __opts__, default=None,
search_global=False
)
ram = config.get_cloud_config_value(
'ram', vm_, __opts__, default=None,
search_global=False
)
else:
raise SaltCloudConfigError("'fixed_instance_size' or 'vcore',"
"'cores_per_processor', 'ram', and 'hdds'"
"must be provided.")
appliance_id = config.get_cloud_config_value(
'appliance_id', vm_, __opts__, default=None,
search_global=False
)
password = config.get_cloud_config_value(
'password', vm_, __opts__, default=None,
search_global=False
)
firewall_policy_id = config.get_cloud_config_value(
'firewall_policy_id', vm_, __opts__, default=None,
search_global=False
)
ip_id = config.get_cloud_config_value(
'ip_id', vm_, __opts__, default=None,
search_global=False
)
load_balancer_id = config.get_cloud_config_value(
'load_balancer_id', vm_, __opts__, default=None,
search_global=False
)
monitoring_policy_id = config.get_cloud_config_value(
'monitoring_policy_id', vm_, __opts__, default=None,
search_global=False
)
datacenter_id = config.get_cloud_config_value(
'datacenter_id', vm_, __opts__, default=None,
search_global=False
)
private_network_id = config.get_cloud_config_value(
'private_network_id', vm_, __opts__, default=None,
search_global=False
)
power_on = config.get_cloud_config_value(
'power_on', vm_, __opts__, default=True,
search_global=False
)
# Contruct server object
return Server(
name=vm_['name'],
description=description,
fixed_instance_size_id=fixed_instance_size_id,
vcore=vcore,
cores_per_processor=cores_per_processor,
ram=ram,
appliance_id=appliance_id,
password=password,
power_on=power_on,
firewall_policy_id=firewall_policy_id,
ip_id=ip_id,
load_balancer_id=load_balancer_id,
monitoring_policy_id=monitoring_policy_id,
datacenter_id=datacenter_id,
rsa_key=ssh_key,
private_network_id=private_network_id
)
def _get_hdds(vm_):
'''
Construct VM hdds from cloud profile config
'''
_hdds = config.get_cloud_config_value(
'hdds', vm_, __opts__, default=None,
search_global=False
)
hdds = []
for hdd in _hdds:
hdds.append(
Hdd(
size=hdd['size'],
is_main=hdd['is_main']
)
)
return hdds
def create(vm_):
'''
Create a single VM from a data dict
'''
try:
# Check for required profile parameters before sending any API calls.
if (vm_['profile'] and
config.is_profile_configured(__opts__,
(__active_provider_name__ or
'oneandone'),
vm_['profile']) is False):
return False
except AttributeError:
pass
data = None
conn = get_conn()
hdds = []
# Assemble the composite server object.
server = _get_server(vm_)
if not bool(server.specs['hardware']['fixed_instance_size_id']):
# Assemble the hdds object.
hdds = _get_hdds(vm_)
__utils__['cloud.fire_event'](
'event',
'requesting instance',
'salt/cloud/{0}/requesting'.format(vm_['name']),
args={'name': vm_['name']},
sock_dir=__opts__['sock_dir'],
transport=__opts__['transport']
)
try:
data = conn.create_server(server=server, hdds=hdds)
_wait_for_completion(conn,
get_wait_timeout(vm_),
data['id'])
except Exception as exc: # pylint: disable=W0703
log.error(
'Error creating {0} on 1and1\n\n'
'The following exception was thrown by the 1and1 library '
'when trying to run the initial deployment: \n{1}'.format(
vm_['name'], exc
),
exc_info_on_loglevel=logging.DEBUG
)
return False
vm_['server_id'] = data['id']
password = data['first_password']
def __query_node_data(vm_, data):
'''
Query node data until node becomes available.
'''
running = False
try:
data = show_instance(vm_['name'], 'action')
if not data:
return False
log.debug(
'Loaded node data for {0}:\nname: {1}\nstate: {2}'.format(
vm_['name'],
pprint.pformat(data['name']),
data['status']['state']
)
)
except Exception as err:
log.error(
'Failed to get nodes list: {0}'.format(
err
),
# Show the trackback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG
)
# Trigger a failure in the wait for IP function
return False
running = data['status']['state'].lower() == 'powered_on'
if not running:
# Still not running, trigger another iteration
return
vm_['ssh_host'] = data['ips'][0]['ip']
return data
try:
data = salt.utils.cloud.wait_for_ip(
__query_node_data,
update_args=(vm_, data),
timeout=config.get_cloud_config_value(
'wait_for_ip_timeout', vm_, __opts__, default=10 * 60),
interval=config.get_cloud_config_value(
'wait_for_ip_interval', vm_, __opts__, default=10),
)
except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
try:
# It might be already up, let's destroy it!
destroy(vm_['name'])
except SaltCloudSystemExit:
pass
finally:
raise SaltCloudSystemExit(str(exc.message))
log.debug('VM is now running')
log.info('Created Cloud VM {0}'.format(vm_))
log.debug(
'{0} VM creation details:\n{1}'.format(
vm_, pprint.pformat(data)
)
)
__utils__['cloud.fire_event'](
'event',
'created instance',
'salt/cloud/{0}/created'.format(vm_['name']),
args={
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
sock_dir=__opts__['sock_dir'],
transport=__opts__['transport']
)
if 'ssh_host' in vm_:
vm_['password'] = password
vm_['key_filename'] = get_key_filename(vm_)
ret = __utils__['cloud.bootstrap'](vm_, __opts__)
ret.update(data)
return ret
else:
raise SaltCloudSystemExit('A valid IP address was not found.')
def destroy(name, call=None):
'''
destroy a server by name
:param name: name given to the server
:param call: call value in this case is 'action'
:return: array of booleans , true if successfully stopped and true if
successfully removed
CLI Example:
.. code-block:: bash
salt-cloud -d vm_name
'''
if call == 'function':
raise SaltCloudSystemExit(
'The destroy action must be called with -d, --destroy, '
'-a or --action.'
)
__utils__['cloud.fire_event'](
'event',
'destroying instance',
'salt/cloud/{0}/destroying'.format(name),
args={'name': name},
sock_dir=__opts__['sock_dir'],
transport=__opts__['transport']
)
conn = get_conn()
node = get_node(conn, name)
conn.delete_server(server_id=node['id'])
__utils__['cloud.fire_event'](
'event',
'destroyed instance',
'salt/cloud/{0}/destroyed'.format(name),
args={'name': name},
sock_dir=__opts__['sock_dir'],
transport=__opts__['transport']
)
if __opts__.get('update_cachedir', False) is True:
__utils__['cloud.delete_minion_cachedir'](
name,
__active_provider_name__.split(':')[0],
__opts__
)
return True
def reboot(name, call=None):
'''
reboot a server by name
:param name: name given to the machine
:param call: call value in this case is 'action'
:return: true if successful
CLI Example:
.. code-block:: bash
salt-cloud -a reboot vm_name
'''
conn = get_conn()
node = get_node(conn, name)
conn.modify_server_status(server_id=node['id'], action='REBOOT')
return True
def stop(name, call=None):
'''
stop a server by name
:param name: name given to the machine
:param call: call value in this case is 'action'
:return: true if successful
CLI Example:
.. code-block:: bash
salt-cloud -a stop vm_name
'''
conn = get_conn()
node = get_node(conn, name)
conn.stop_server(server_id=node['id'])
return True
def start(name, call=None):
'''
start a server by name
:param name: name given to the machine
:param call: call value in this case is 'action'
:return: true if successful
CLI Example:
.. code-block:: bash
salt-cloud -a start vm_name
'''
conn = get_conn()
node = get_node(conn, name)
conn.start_server(server_id=node['id'])
return True
def get_node(conn, name):
'''
Return a node for the named VM
'''
for node in conn.list_servers(per_page=1000):
if node['name'] == name:
return node
def get_key_filename(vm_):
'''
Check SSH private key file and return absolute path if exists.
'''
key_filename = config.get_cloud_config_value(
'ssh_private_key', vm_, __opts__, search_global=False, default=None
)
if key_filename is not None:
key_filename = os.path.expanduser(key_filename)
if not os.path.isfile(key_filename):
raise SaltCloudConfigError(
'The defined ssh_private_key \'{0}\' does not exist'.format(
key_filename
)
)
return key_filename
def load_public_key(vm_):
'''
Load the public key file if exists.
'''
public_key_filename = config.get_cloud_config_value(
'ssh_public_key', vm_, __opts__, search_global=False, default=None
)
if public_key_filename is not None:
public_key_filename = os.path.expanduser(public_key_filename)
if not os.path.isfile(public_key_filename):
raise SaltCloudConfigError(
'The defined ssh_public_key \'{0}\' does not exist'.format(
public_key_filename
)
)
with salt.utils.fopen(public_key_filename, 'r') as public_key:
key = public_key.read().replace('\n', '')
return key
def get_wait_timeout(vm_):
'''
Return the wait_for_timeout for resource provisioning.
'''
return config.get_cloud_config_value(
'wait_for_timeout', vm_, __opts__, default=15 * 60,
search_global=False
)
def _wait_for_completion(conn, wait_timeout, server_id):
'''
Poll request status until resource is provisioned.
'''
wait_timeout = time.time() + wait_timeout
while wait_timeout > time.time():
time.sleep(5)
server = conn.get_server(server_id)
server_state = server['status']['state'].lower()
if server_state == "powered_on":
return
elif server_state == 'failed':
raise Exception('Server creation failed for {0}'.format(server_id))
elif server_state in ('active',
'enabled',
'deploying',
'configuring'):
continue
else:
raise Exception(
'Unknown server state {0}'.format(server_state))
raise Exception(
'Timed out waiting for server create completion for {0}'.format(server_id)
)

View file

@ -151,7 +151,9 @@ import os
import logging
import socket
import pprint
from salt.utils.versions import LooseVersion as _LooseVersion
# This import needs to be here so the version check can be done below
import salt.utils.versions
# Import libcloud
try:
@ -170,7 +172,8 @@ try:
# However, older versions of libcloud must still be supported with this work-around.
# This work-around can be removed when the required minimum version of libcloud is
# 2.0.0 (See PR #40837 - which is implemented in Salt Oxygen).
if _LooseVersion(libcloud.__version__) < _LooseVersion('1.4.0'):
if salt.utils.versions.LooseVersion(libcloud.__version__) < \
salt.utils.versions.LooseVersion('1.4.0'):
# See https://github.com/saltstack/salt/issues/32743
import libcloud.security
libcloud.security.CA_CERTS_PATH.append('/etc/ssl/certs/YaST-CA.pem')
@ -182,7 +185,7 @@ except Exception:
from salt.cloud.libcloudfuncs import * # pylint: disable=W0614,W0401
# Import salt libs
import salt.utils # Can be removed once namespaced_function and warn_until have been moved
import salt.utils # Can be removed once namespaced_function has been moved
import salt.utils.cloud
import salt.utils.files
import salt.utils.pycrypto
@ -237,7 +240,7 @@ def __virtual__():
if get_dependencies() is False:
return False
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'This driver has been deprecated and will be removed in the '
'{version} release of Salt. Please use the nova driver instead.'

View file

@ -17,8 +17,16 @@ from __future__ import absolute_import
import logging
# Import salt libs
import salt.utils
import salt.config as config
from salt.exceptions import SaltCloudException
import salt.netapi
import salt.ext.six as six
if six.PY3:
import ipaddress
else:
import salt.ext.ipaddress as ipaddress
from salt.exceptions import SaltCloudException, SaltCloudSystemExit
# Get logging started
log = logging.getLogger(__name__)
@ -47,28 +55,188 @@ def __virtual__():
return True
def list_nodes():
def _get_connection_info():
'''
Because this module is not specific to any cloud providers, there will be
no nodes to list.
Return connection information for the passed VM data
'''
vm_ = get_configured_provider()
try:
ret = {'username': vm_['username'],
'password': vm_['password'],
'eauth': vm_['eauth'],
'vm': vm_,
}
except KeyError:
raise SaltCloudException(
'Configuration must define salt-api "username", "password" and "eauth"')
return ret
def avail_locations(call=None):
'''
This function returns a list of locations available.
.. code-block:: bash
salt-cloud --list-locations my-cloud-provider
[ saltify will always returns an empty dictionary ]
'''
return {}
def avail_images(call=None):
'''
This function returns a list of images available for this cloud provider.
.. code-block:: bash
salt-cloud --list-images saltify
returns a list of available profiles.
..versionadded:: Oxygen
'''
vm_ = get_configured_provider()
return {'Profiles': [profile for profile in vm_['profiles']]}
def avail_sizes(call=None):
'''
This function returns a list of sizes available for this cloud provider.
.. code-block:: bash
salt-cloud --list-sizes saltify
[ saltify always returns an empty dictionary ]
'''
return {}
def list_nodes_full():
def list_nodes(call=None):
'''
Because this module is not specific to any cloud providers, there will be
no nodes to list.
List the nodes which have salt-cloud:driver:saltify grains.
.. code-block:: bash
salt-cloud -Q
returns a list of dictionaries of defined standard fields.
salt-api setup required for operation.
..versionadded:: Oxygen
'''
return {}
nodes = _list_nodes_full(call)
return _build_required_items(nodes)
def list_nodes_select():
def _build_required_items(nodes):
ret = {}
for name, grains in nodes.items():
if grains:
private_ips = []
public_ips = []
ips = grains['ipv4'] + grains['ipv6']
for adrs in ips:
ip_ = ipaddress.ip_address(adrs)
if not ip_.is_loopback:
if ip_.is_private:
private_ips.append(adrs)
else:
public_ips.append(adrs)
ret[name] = {
'id': grains['id'],
'image': grains['salt-cloud']['profile'],
'private_ips': private_ips,
'public_ips': public_ips,
'size': '',
'state': 'running'
}
return ret
def list_nodes_full(call=None):
'''
Because this module is not specific to any cloud providers, there will be
no nodes to list.
Lists complete information for all nodes.
.. code-block:: bash
salt-cloud -F
returns a list of dictionaries.
for 'saltify' minions, returns dict of grains (enhanced).
salt-api setup required for operation.
..versionadded:: Oxygen
'''
return {}
ret = _list_nodes_full(call)
for key, grains in ret.items(): # clean up some hyperverbose grains -- everything is too much
try:
del grains['cpu_flags'], grains['disks'], grains['pythonpath'], grains['dns'], grains['gpus']
except KeyError:
pass # ignore absence of things we are eliminating
except TypeError:
del ret[key] # eliminate all reference to unexpected (None) values.
reqs = _build_required_items(ret)
for name in ret:
ret[name].update(reqs[name])
return ret
def _list_nodes_full(call=None):
'''
List the nodes, ask all 'saltify' minions, return dict of grains.
'''
local = salt.netapi.NetapiClient(__opts__)
cmd = {'client': 'local',
'tgt': 'salt-cloud:driver:saltify',
'fun': 'grains.items',
'arg': '',
'tgt_type': 'grain',
}
cmd.update(_get_connection_info())
return local.run(cmd)
def list_nodes_select(call=None):
'''
Return a list of the minions that have salt-cloud grains, with
select fields.
'''
return salt.utils.cloud.list_nodes_select(
list_nodes_full('function'), __opts__['query.selection'], call,
)
def show_instance(name, call=None):
'''
List the a single node, return dict of grains.
'''
local = salt.netapi.NetapiClient(__opts__)
cmd = {'client': 'local',
'tgt': 'name',
'fun': 'grains.items',
'arg': '',
'tgt_type': 'glob',
}
cmd.update(_get_connection_info())
ret = local.run(cmd)
ret.update(_build_required_items(ret))
return ret
def create(vm_):
@ -190,3 +358,130 @@ def _verify(vm_):
except SaltCloudException as exc:
log.error('Exception: %s', exc)
return False
def destroy(name, call=None):
''' Destroy a node.
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt-cloud --destroy mymachine
salt-api setup required for operation.
'''
if call == 'function':
raise SaltCloudSystemExit(
'The destroy action must be called with -d, --destroy, '
'-a, or --action.'
)
opts = __opts__
__utils__['cloud.fire_event'](
'event',
'destroying instance',
'salt/cloud/{0}/destroying'.format(name),
args={'name': name},
sock_dir=opts['sock_dir'],
transport=opts['transport']
)
local = salt.netapi.NetapiClient(opts)
cmd = {'client': 'local',
'tgt': name,
'fun': 'grains.get',
'arg': ['salt-cloud'],
}
cmd.update(_get_connection_info())
vm_ = cmd['vm']
my_info = local.run(cmd)
try:
vm_.update(my_info[name]) # get profile name to get config value
except (IndexError, TypeError):
pass
if config.get_cloud_config_value(
'remove_config_on_destroy', vm_, opts, default=True
):
cmd.update({'fun': 'service.disable', 'arg': ['salt-minion']})
ret = local.run(cmd) # prevent generating new keys on restart
if ret and ret[name]:
log.info('disabled salt-minion service on %s', name)
cmd.update({'fun': 'config.get', 'arg': ['conf_file']})
ret = local.run(cmd)
if ret and ret[name]:
confile = ret[name]
cmd.update({'fun': 'file.remove', 'arg': [confile]})
ret = local.run(cmd)
if ret and ret[name]:
log.info('removed minion %s configuration file %s',
name, confile)
cmd.update({'fun': 'config.get', 'arg': ['pki_dir']})
ret = local.run(cmd)
if ret and ret[name]:
pki_dir = ret[name]
cmd.update({'fun': 'file.remove', 'arg': [pki_dir]})
ret = local.run(cmd)
if ret and ret[name]:
log.info(
'removed minion %s key files in %s',
name,
pki_dir)
if config.get_cloud_config_value(
'shutdown_on_destroy', vm_, opts, default=False
):
cmd.update({'fun': 'system.shutdown', 'arg': ''})
ret = local.run(cmd)
if ret and ret[name]:
log.info('system.shutdown for minion %s successful', name)
__utils__['cloud.fire_event'](
'event',
'destroyed instance',
'salt/cloud/{0}/destroyed'.format(name),
args={'name': name},
sock_dir=opts['sock_dir'],
transport=opts['transport']
)
return {'Destroyed': '{0} was destroyed.'.format(name)}
def reboot(name, call=None):
'''
Reboot a saltify minion.
salt-api setup required for operation.
..versionadded:: Oxygen
name
The name of the VM to reboot.
CLI Example:
.. code-block:: bash
salt-cloud -a reboot vm_name
'''
if call != 'action':
raise SaltCloudException(
'The reboot action must be called with -a or --action.'
)
local = salt.netapi.NetapiClient(__opts__)
cmd = {'client': 'local',
'tgt': name,
'fun': 'system.reboot',
'arg': '',
}
cmd.update(_get_connection_info())
ret = local.run(cmd)
return ret

View file

@ -511,7 +511,7 @@ def list_nodes_full(mask='mask[id]', call=None):
conn = get_conn(service='SoftLayer_Account')
response = conn.getVirtualGuests()
for node_id in response:
hostname = node_id['hostname'].split('.')[0]
hostname = node_id['hostname']
ret[hostname] = node_id
__utils__['cloud.cache_node_list'](ret, __active_provider_name__.split(':')[0], __opts__)
return ret
@ -597,9 +597,6 @@ def destroy(name, call=None):
transport=__opts__['transport']
)
# If the VM was created with use_fqdn, the short hostname will be used instead.
name = name.split('.')[0]
node = show_instance(name, call='action')
conn = get_conn()
response = conn.deleteObject(id=node['id'])

View file

@ -526,9 +526,6 @@ def destroy(name, call=None):
transport=__opts__['transport']
)
# If the VM was created with use_fqdn, the short hostname will be used instead.
name = name.split('.')[0]
node = show_instance(name, call='action')
conn = get_conn(service='SoftLayer_Ticket')
response = conn.createCancelServerTicket(

View file

@ -2344,7 +2344,7 @@ def syndic_config(master_config_path,
'pki_dir', 'cachedir', 'pidfile', 'sock_dir', 'extension_modules',
'autosign_file', 'autoreject_file', 'token_dir'
]
for config_key in ('syndic_log_file', 'log_file', 'key_logfile'):
for config_key in ('log_file', 'key_logfile', 'syndic_log_file'):
# If this is not a URI and instead a local path
if urlparse(opts.get(config_key, '')).scheme == '':
prepend_root_dirs.append(config_key)
@ -3235,12 +3235,12 @@ def is_profile_configured(opts, provider, profile_name, vm_=None):
alias, driver = provider.split(':')
# Most drivers need an image to be specified, but some do not.
non_image_drivers = ['nova', 'virtualbox', 'libvirt', 'softlayer']
non_image_drivers = ['nova', 'virtualbox', 'libvirt', 'softlayer', 'oneandone']
# Most drivers need a size, but some do not.
non_size_drivers = ['opennebula', 'parallels', 'proxmox', 'scaleway',
'softlayer', 'softlayer_hw', 'vmware', 'vsphere',
'virtualbox', 'profitbricks', 'libvirt']
'virtualbox', 'profitbricks', 'libvirt', 'oneandone']
provider_key = opts['providers'][alias][driver]
profile_key = opts['providers'][alias][driver]['profiles'][profile_name]

View file

@ -33,6 +33,7 @@ import salt.utils.platform
import salt.utils.stringutils
import salt.utils.templates
import salt.utils.url
import salt.utils.versions
from salt.utils.locales import sdecode
from salt.utils.openstack.swift import SaltSwift
@ -736,7 +737,7 @@ class Client(object):
Cache a file then process it as a template
'''
if u'env' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Oxygen',
u'Parameter \'env\' has been detected in the argument list. This '
u'parameter is no longer used and has been replaced by \'saltenv\' '
@ -1286,7 +1287,7 @@ class RemoteClient(Client):
u'specified file %s is not present to generate hash: %s',
path, err
)
return {}
return {}, None
else:
ret = {}
hash_type = self.opts.get(u'hash_type', u'md5')

View file

@ -15,10 +15,10 @@ import time
# Import salt libs
import salt.loader
import salt.utils
import salt.utils.files
import salt.utils.locales
import salt.utils.url
import salt.utils.versions
from salt.utils.args import get_function_argspec as _argspec
# Import 3rd-party libs
@ -553,7 +553,7 @@ class Fileserver(object):
kwargs[args[0]] = args[1]
if 'env' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -583,7 +583,7 @@ class Fileserver(object):
'dest': ''}
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -609,7 +609,7 @@ class Fileserver(object):
Common code for hashing and stating files
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. '
'This parameter is no longer used and has been replaced by '
@ -656,7 +656,7 @@ class Fileserver(object):
Deletes the file_lists cache files
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -738,7 +738,7 @@ class Fileserver(object):
Return a list of files from the dominant environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -769,7 +769,7 @@ class Fileserver(object):
List all emptydirs in the given environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -800,7 +800,7 @@ class Fileserver(object):
List all directories in the given environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -831,7 +831,7 @@ class Fileserver(object):
Return a list of symlinked files and dirs
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -63,6 +63,7 @@ import salt.utils
import salt.utils.files
import salt.utils.gzip_util
import salt.utils.url
import salt.utils.versions
import salt.fileserver
from salt.utils.event import tagify
@ -569,7 +570,7 @@ def _env_is_exposed(env):
blacklist.
'''
if __opts__['hgfs_env_whitelist']:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The hgfs_env_whitelist config option has been renamed to '
'hgfs_saltenv_whitelist. Please update your configuration.'
@ -579,7 +580,7 @@ def _env_is_exposed(env):
whitelist = __opts__['hgfs_saltenv_whitelist']
if __opts__['hgfs_env_blacklist']:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The hgfs_env_blacklist config option has been renamed to '
'hgfs_saltenv_blacklist. Please update your configuration.'
@ -735,7 +736,7 @@ def serve_file(load, fnd):
Return a chunk from a file based on the data received
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -769,7 +770,7 @@ def file_hash(load, fnd):
Return a file hash, the hash type is set in the master config file
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -803,7 +804,7 @@ def _file_lists(load, form):
Return a dict containing the file lists for files and dirs
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -851,7 +852,7 @@ def _get_file_list(load):
Get a list of all files on the file server in a specified environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -896,7 +897,7 @@ def _get_dir_list(load):
Get a list of all directories on the master
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -35,6 +35,7 @@ import salt.utils
import salt.utils.files
import salt.utils.gzip_util
import salt.utils.url
import salt.utils.versions
# Import third party libs
from salt.ext import six
@ -164,7 +165,7 @@ def file_hash(load, fnd):
ret = {}
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -234,7 +235,7 @@ def file_list(load):
Return a list of all files on the file server in a specified environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -318,7 +319,7 @@ def dir_list(load):
- source-minion/absolute/path
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -24,11 +24,12 @@ import logging
# Import salt libs
import salt.fileserver
import salt.utils
import salt.utils # Can be removed once is_bin_file and get_hash are moved
import salt.utils.event
import salt.utils.files
import salt.utils.gzip_util
import salt.utils.path
import salt.utils.versions
from salt.ext import six
log = logging.getLogger(__name__)
@ -39,7 +40,7 @@ def find_file(path, saltenv='base', **kwargs):
Search the environment for the relative path.
'''
if 'env' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -116,7 +117,7 @@ def serve_file(load, fnd):
Return a chunk from a file based on the data received
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -217,7 +218,7 @@ def file_hash(load, fnd):
Return a file hash, the hash type is set in the master config file
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -297,7 +298,7 @@ def _file_lists(load, form):
Return a dict containing the file lists for files, dirs, emtydirs and symlinks
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -443,7 +444,7 @@ def symlink_list(load):
Return a dict of all symlinks based on a given path on the Master
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -71,6 +71,7 @@ import salt.modules
import salt.utils
import salt.utils.files
import salt.utils.gzip_util
import salt.utils.versions
# Import 3rd-party libs
# pylint: disable=import-error,no-name-in-module,redefined-builtin
@ -125,7 +126,7 @@ def find_file(path, saltenv='base', **kwargs):
is missing, or if the MD5 does not match.
'''
if 'env' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -167,7 +168,7 @@ def file_hash(load, fnd):
Return an MD5 file hash
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -200,7 +201,7 @@ def serve_file(load, fnd):
Return a chunk from a file based on the data received
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -244,7 +245,7 @@ def file_list(load):
Return a list of all files on the file server in a specified environment
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -285,7 +286,7 @@ def dir_list(load):
Return a list of all directories on the master
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -58,6 +58,7 @@ import salt.utils
import salt.utils.files
import salt.utils.gzip_util
import salt.utils.url
import salt.utils.versions
import salt.fileserver
from salt.utils.event import tagify
@ -484,7 +485,7 @@ def _env_is_exposed(env):
blacklist.
'''
if __opts__['svnfs_env_whitelist']:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The svnfs_env_whitelist config option has been renamed to '
'svnfs_saltenv_whitelist. Please update your configuration.'
@ -494,7 +495,7 @@ def _env_is_exposed(env):
whitelist = __opts__['svnfs_saltenv_whitelist']
if __opts__['svnfs_env_blacklist']:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The svnfs_env_blacklist config option has been renamed to '
'svnfs_saltenv_blacklist. Please update your configuration.'
@ -630,7 +631,7 @@ def serve_file(load, fnd):
Return a chunk from a file based on the data received
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -664,7 +665,7 @@ def file_hash(load, fnd):
Return a file hash, the hash type is set in the master config file
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -722,7 +723,7 @@ def _file_lists(load, form):
Return a dict containing the file lists for files, dirs, emptydirs and symlinks
'''
if 'env' in load:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'env\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -21,7 +21,6 @@ from zipimport import zipimporter
# Import salt libs
import salt.config
import salt.syspaths
import salt.utils # Can be removed after warn_until is moved
import salt.utils.context
import salt.utils.dictupdate
import salt.utils.event
@ -29,6 +28,7 @@ import salt.utils.files
import salt.utils.lazy
import salt.utils.odict
import salt.utils.platform
import salt.utils.versions
from salt.exceptions import LoaderError
from salt.template import check_render_pipe_str
from salt.utils.decorators import Depends
@ -82,14 +82,11 @@ else:
# which simplifies code readability, it adds some unsupported functions into
# the driver's module scope.
# We list un-supported functions here. These will be removed from the loaded.
# TODO: remove the need for this cross-module code. Maybe use NotImplemented
LIBCLOUD_FUNCS_NOT_SUPPORTED = (
u'parallels.avail_sizes',
u'parallels.avail_locations',
u'proxmox.avail_sizes',
u'saltify.destroy',
u'saltify.avail_sizes',
u'saltify.avail_images',
u'saltify.avail_locations',
u'rackspace.reboot',
u'openstack.list_locations',
u'rackspace.list_locations'
@ -513,7 +510,7 @@ def beacons(opts, functions, context=None, proxy=None):
opts,
tag=u'beacons',
pack={u'__context__': context, u'__salt__': functions, u'__proxy__': proxy or {}},
virtual_funcs=[u'__validate__'],
virtual_funcs=[],
)
@ -1484,7 +1481,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
virtual_funcs_to_process = [u'__virtual__'] + self.virtual_funcs
for virtual_func in virtual_funcs_to_process:
virtual_ret, module_name, virtual_err, virtual_aliases = \
self.process_virtual(mod, module_name)
self.process_virtual(mod, module_name, virtual_func)
if virtual_err is not None:
log.trace(
u'Error loading %s.%s: %s',
@ -1717,7 +1714,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
log.trace(u'Loaded %s as virtual %s', module_name, virtual)
if not hasattr(mod, u'__virtualname__'):
salt.utils.warn_until(
salt.utils.versions.warn_until(
u'Hydrogen',
u'The \'{0}\' module is renaming itself in its '
u'__virtual__() function ({1} => {2}). Please '

View file

@ -1453,13 +1453,21 @@ class Minion(MinionBase):
function_name = data[u'fun']
if function_name in minion_instance.functions:
try:
minion_blackout_violation = False
if minion_instance.connected and minion_instance.opts[u'pillar'].get(u'minion_blackout', False):
# this minion is blacked out. Only allow saltutil.refresh_pillar
if function_name != u'saltutil.refresh_pillar' and \
function_name not in minion_instance.opts[u'pillar'].get(u'minion_blackout_whitelist', []):
raise SaltInvocationError(u'Minion in blackout mode. Set \'minion_blackout\' '
u'to False in pillar to resume operations. Only '
u'saltutil.refresh_pillar allowed in blackout mode.')
whitelist = minion_instance.opts[u'pillar'].get(u'minion_blackout_whitelist', [])
# this minion is blacked out. Only allow saltutil.refresh_pillar and the whitelist
if function_name != u'saltutil.refresh_pillar' and function_name not in whitelist:
minion_blackout_violation = True
elif minion_instance.opts[u'grains'].get(u'minion_blackout', False):
whitelist = minion_instance.opts[u'grains'].get(u'minion_blackout_whitelist', [])
if function_name != u'saltutil.refresh_pillar' and function_name not in whitelist:
minion_blackout_violation = True
if minion_blackout_violation:
raise SaltInvocationError(u'Minion in blackout mode. Set \'minion_blackout\' '
u'to False in pillar or grains to resume operations. Only '
u'saltutil.refresh_pillar allowed in blackout mode.')
func = minion_instance.functions[function_name]
args, kwargs = load_args_and_kwargs(
func,
@ -1622,14 +1630,23 @@ class Minion(MinionBase):
for ind in range(0, len(data[u'fun'])):
ret[u'success'][data[u'fun'][ind]] = False
try:
minion_blackout_violation = False
if minion_instance.connected and minion_instance.opts[u'pillar'].get(u'minion_blackout', False):
# this minion is blacked out. Only allow saltutil.refresh_pillar
if data[u'fun'][ind] != u'saltutil.refresh_pillar' and \
data[u'fun'][ind] not in minion_instance.opts[u'pillar'].get(u'minion_blackout_whitelist', []):
raise SaltInvocationError(u'Minion in blackout mode. Set \'minion_blackout\' '
u'to False in pillar to resume operations. Only '
u'saltutil.refresh_pillar allowed in blackout mode.')
whitelist = minion_instance.opts[u'pillar'].get(u'minion_blackout_whitelist', [])
# this minion is blacked out. Only allow saltutil.refresh_pillar and the whitelist
if data[u'fun'][ind] != u'saltutil.refresh_pillar' and data[u'fun'][ind] not in whitelist:
minion_blackout_violation = True
elif minion_instance.opts[u'grains'].get(u'minion_blackout', False):
whitelist = minion_instance.opts[u'grains'].get(u'minion_blackout_whitelist', [])
if data[u'fun'][ind] != u'saltutil.refresh_pillar' and data[u'fun'][ind] not in whitelist:
minion_blackout_violation = True
if minion_blackout_violation:
raise SaltInvocationError(u'Minion in blackout mode. Set \'minion_blackout\' '
u'to False in pillar or grains to resume operations. Only '
u'saltutil.refresh_pillar allowed in blackout mode.')
func = minion_instance.functions[data[u'fun'][ind]]
args, kwargs = load_args_and_kwargs(
func,
data[u'arg'][ind],

View file

@ -46,6 +46,7 @@ import salt.utils.path
import salt.utils.pkg
import salt.utils.pkg.deb
import salt.utils.systemd
import salt.utils.versions
from salt.exceptions import (
CommandExecutionError, MinionError, SaltInvocationError
)
@ -252,7 +253,7 @@ def latest_version(*names, **kwargs):
if 'repo' in kwargs:
# Remember to kill _get_repo() too when removing this warning.
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Hydrogen',
'The \'repo\' argument to apt.latest_version is deprecated, and '
'will be removed in Salt {version}. Please use \'fromrepo\' '
@ -319,7 +320,7 @@ def latest_version(*names, **kwargs):
# to the install candidate, then the candidate is an upgrade, so
# add it to the return dict
if not any(
(salt.utils.compare_versions(ver1=x,
(salt.utils.versions.compare(ver1=x,
oper='>=',
ver2=candidate,
cmp_func=version_cmp)
@ -764,12 +765,12 @@ def install(name=None,
cver = old.get(pkgname, '')
if reinstall and cver \
and salt.utils.compare_versions(ver1=version_num,
and salt.utils.versions.compare(ver1=version_num,
oper='==',
ver2=cver,
cmp_func=version_cmp):
to_reinstall[pkgname] = pkgstr
elif not cver or salt.utils.compare_versions(ver1=version_num,
elif not cver or salt.utils.versions.compare(ver1=version_num,
oper='>=',
ver2=cver,
cmp_func=version_cmp):

View file

@ -81,7 +81,7 @@ def add(name, beacon_data, **kwargs):
.. code-block:: bash
salt '*' beacons.add ps "{'salt-master': 'stopped', 'apache2': 'stopped'}"
salt '*' beacons.add ps "[{'salt-master': 'stopped', 'apache2': 'stopped'}]"
'''
ret = {'comment': 'Failed to add beacon {0}.'.format(name),
@ -125,6 +125,7 @@ def add(name, beacon_data, **kwargs):
res = __salt__['event.fire']({'name': name, 'beacon_data': beacon_data, 'func': 'add'}, 'manage_beacons')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_beacon_add_complete', wait=30)
log.debug('=== event_ret {} ==='.format(event_ret))
if event_ret and event_ret['complete']:
beacons = event_ret['beacons']
if name in beacons and beacons[name] == beacon_data:
@ -149,7 +150,7 @@ def modify(name, beacon_data, **kwargs):
.. code-block:: bash
salt '*' beacons.modify ps "{'salt-master': 'stopped', 'apache2': 'stopped'}"
salt '*' beacons.modify ps "[{'salt-master': 'stopped', 'apache2': 'stopped'}]"
'''
ret = {'comment': '',
@ -252,6 +253,7 @@ def delete(name, **kwargs):
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_beacon_delete_complete', wait=30)
if event_ret and event_ret['complete']:
log.debug('== event_ret {} =='.format(event_ret))
beacons = event_ret['beacons']
if name not in beacons:
ret['result'] = True

View file

@ -1955,7 +1955,7 @@ def list_policy_versions(policy_name,
return ret.get('list_policy_versions_response', {}).get('list_policy_versions_result', {}).get('versions')
except boto.exception.BotoServerError as e:
log.debug(e)
msg = 'Failed to list {0} policy vesions.'
msg = 'Failed to list {0} policy versions.'
log.error(msg.format(policy_name))
return []

View file

@ -53,6 +53,7 @@ import time
# Import salt libs
import salt.utils.compat
import salt.utils.versions
import salt.utils.odict as odict
from salt.exceptions import SaltInvocationError
from salt.utils.versions import LooseVersion as _LooseVersion
@ -255,7 +256,7 @@ def zone_exists(zone, region=None, key=None, keyid=None, profile=None,
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
if retry_on_rate_limit or rate_limit_retries is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The \'retry_on_rate_limit\' and \'rate_limit_retries\' arguments '
'have been deprecated in favor of \'retry_on_errors\' and '
@ -387,7 +388,7 @@ def get_record(name, zone, record_type, fetch_all=False, region=None, key=None,
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
if retry_on_rate_limit or rate_limit_retries is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The \'retry_on_rate_limit\' and \'rate_limit_retries\' arguments '
'have been deprecated in favor of \'retry_on_errors\' and '
@ -468,7 +469,7 @@ def add_record(name, value, zone, record_type, identifier=None, ttl=None,
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
if retry_on_rate_limit or rate_limit_retries is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The \'retry_on_rate_limit\' and \'rate_limit_retries\' arguments '
'have been deprecated in favor of \'retry_on_errors\' and '
@ -555,7 +556,7 @@ def update_record(name, value, zone, record_type, identifier=None, ttl=None,
_type = record_type.upper()
if retry_on_rate_limit or rate_limit_retries is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The \'retry_on_rate_limit\' and \'rate_limit_retries\' arguments '
'have been deprecated in favor of \'retry_on_errors\' and '
@ -617,7 +618,7 @@ def delete_record(name, zone, record_type, identifier=None, all_records=False,
_type = record_type.upper()
if retry_on_rate_limit or rate_limit_retries is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Neon',
'The \'retry_on_rate_limit\' and \'rate_limit_retries\' arguments '
'have been deprecated in favor of \'retry_on_errors\' and '

View file

@ -137,6 +137,7 @@ import random
import salt.utils.boto
import salt.utils.boto3
import salt.utils.compat
import salt.utils.versions
from salt.exceptions import SaltInvocationError, CommandExecutionError
from salt.utils.versions import LooseVersion as _LooseVersion
@ -2456,9 +2457,10 @@ def describe_route_table(route_table_id=None, route_table_name=None,
'''
salt.utils.warn_until('Oxygen',
'The \'describe_route_table\' method has been deprecated and '
'replaced by \'describe_route_tables\'.'
salt.utils.versions.warn_until(
'Oxygen',
'The \'describe_route_table\' method has been deprecated and '
'replaced by \'describe_route_tables\'.'
)
if not any((route_table_id, route_table_name, tags)):
raise SaltInvocationError('At least one of the following must be specified: '

View file

@ -33,6 +33,7 @@ import salt.utils.powershell
import salt.utils.stringutils
import salt.utils.templates
import salt.utils.timed_subprocess
import salt.utils.versions
import salt.utils.vt
import salt.grains.extra
from salt.ext import six
@ -2119,7 +2120,7 @@ def script(source,
)
if '__env__' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'__env__\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '
@ -2335,7 +2336,7 @@ def script_retcode(source,
salt '*' cmd.script_retcode salt://scripts/runme.sh stdin='one\\ntwo\\nthree\\nfour\\nfive\\n'
'''
if '__env__' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'__env__\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -2,10 +2,7 @@
'''
An execution module that interacts with the Datadog API
Common parameters:
scope
The scope of the request
The following parameters are required for all functions.
api_key
The datadog API key
@ -45,6 +42,10 @@ def _initialize_connection(api_key, app_key):
'''
Initialize Datadog connection
'''
if api_key is None:
raise SaltInvocationError('api_key must be specified')
if app_key is None:
raise SaltInvocationError('app_key must be specified')
options = {
'api_key': api_key,
'app_key': app_key
@ -52,31 +53,36 @@ def _initialize_connection(api_key, app_key):
datadog.initialize(**options)
def schedule_downtime(scope, api_key=None, app_key=None, monitor_id=None,
start=None, end=None, message=None, recurrence=None,
timezone=None, test=False):
def schedule_downtime(scope,
api_key=None,
app_key=None,
monitor_id=None,
start=None,
end=None,
message=None,
recurrence=None,
timezone=None,
test=False):
'''
Schedule downtime for a scope of monitors.
monitor_id
The ID of the monitor
start
Start time in seconds since the epoch
end
End time in seconds since the epoch
message
A message to send in a notification for this downtime
recurrence
Repeat this downtime periodically
timezone
Specify the timezone
CLI Example:
.. code-block:: bash
salt-call datadog.schedule_downtime 'host:app2' stop=$(date --date='30
minutes' +%s) app_key=<app_key> api_key=<api_key>
salt-call datadog.schedule_downtime 'host:app2' \\
stop=$(date --date='30 minutes' +%s) \\
app_key='0123456789' \\
api_key='9876543210'
Optional arguments
:param monitor_id: The ID of the monitor
:param start: Start time in seconds since the epoch
:param end: End time in seconds since the epoch
:param message: A message to send in a notification for this downtime
:param recurrence: Repeat this downtime periodically
:param timezone: Specify the timezone
'''
ret = {'result': False,
'response': None,
@ -114,21 +120,25 @@ def schedule_downtime(scope, api_key=None, app_key=None, monitor_id=None,
return ret
def cancel_downtime(api_key=None, app_key=None, scope=None, id=None):
def cancel_downtime(api_key=None,
app_key=None,
scope=None,
id=None):
'''
Cancel a downtime by id or by scope.
Either scope or id is required.
id
The ID of the downtime
CLI Example:
.. code-block:: bash
salt-call datadog.cancel_downtime scope='host:app01' api_key=<api_key>
app_key=<app_key>`
salt-call datadog.cancel_downtime scope='host:app01' \\
api_key='0123456789' \\
app_key='9876543210'`
Arguments - Either scope or id is required.
:param id: The downtime ID
:param scope: The downtime scope
'''
if api_key is None:
raise SaltInvocationError('api_key must be specified')
@ -153,9 +163,9 @@ def cancel_downtime(api_key=None, app_key=None, scope=None, id=None):
'scope': scope
}
response = requests.post(
'https://app.datadoghq.com/api/v1/downtime/cancel/by_scope',
params=params
)
'https://app.datadoghq.com/api/v1/downtime/cancel/by_scope',
params=params
)
if response.status_code == 200:
ret['result'] = True
ret['response'] = response.json()
@ -168,3 +178,87 @@ def cancel_downtime(api_key=None, app_key=None, scope=None, id=None):
raise SaltInvocationError('One of id or scope must be specified')
return ret
def post_event(api_key=None,
app_key=None,
title=None,
text=None,
date_happened=None,
priority=None,
host=None,
tags=None,
alert_type=None,
aggregation_key=None,
source_type_name=None):
'''
Post an event to the Datadog stream.
CLI Example
.. code-block:: bash
salt-call datadog.post_event api_key='0123456789' \\
app_key='9876543210' \\
title='Salt Highstate' \\
text="Salt highstate was run on $(salt-call grains.get id)" \\
tags='["service:salt", "event:highstate"]'
Required arguments
:param title: The event title. Limited to 100 characters.
:param text: The body of the event. Limited to 4000 characters. The text
supports markdown.
Optional arguments
:param date_happened: POSIX timestamp of the event.
:param priority: The priority of the event ('normal' or 'low').
:param host: Host name to associate with the event.
:param tags: A list of tags to apply to the event.
:param alert_type: "error", "warning", "info" or "success".
:param aggregation_key: An arbitrary string to use for aggregation,
max length of 100 characters.
:param source_type_name: The type of event being posted.
'''
_initialize_connection(api_key, app_key)
if title is None:
raise SaltInvocationError('title must be specified')
if text is None:
raise SaltInvocationError('text must be specified')
if alert_type not in [None, 'error', 'warning', 'info', 'success']:
# Datadog only supports these alert types but the API doesn't return an
# error for an incorrect alert_type, so we can do it here for now.
# https://github.com/DataDog/datadogpy/issues/215
message = ('alert_type must be one of "error", "warning", "info", or '
'"success"')
raise SaltInvocationError(message)
ret = {'result': False,
'response': None,
'comment': ''}
try:
response = datadog.api.Event.create(title=title,
text=text,
date_happened=date_happened,
priority=priority,
host=host,
tags=tags,
alert_type=alert_type,
aggregation_key=aggregation_key,
source_type_name=source_type_name
)
except ValueError:
comment = ('Unexpected exception in Datadog Post Event API '
'call. Are your keys correct?')
ret['comment'] = comment
return ret
ret['response'] = response
if 'status' in response.keys():
ret['result'] = True
ret['comment'] = 'Successfully sent event'
else:
ret['comment'] = 'Error in posting event.'
return ret

View file

@ -13,6 +13,7 @@ import re
import salt.utils
import salt.utils.path
import salt.utils.files
import salt.utils.versions
log = logging.getLogger(__name__)
@ -185,7 +186,7 @@ def set_file(path, saltenv='base', **kwargs):
salt '*' debconf.set_file salt://pathto/pkg.selections
'''
if '__env__' in kwargs:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Oxygen',
'Parameter \'__env__\' has been detected in the argument list. This '
'parameter is no longer used and has been replaced by \'saltenv\' '

View file

@ -26,6 +26,7 @@ import salt.utils.args
import salt.utils.path
import salt.utils.pkg
import salt.utils.systemd
import salt.utils.versions
from salt.exceptions import CommandExecutionError, MinionError
from salt.ext import six
@ -236,7 +237,7 @@ def latest_version(*names, **kwargs):
ret[name] = ''
installed = _cpv_to_version(_vartree().dep_bestmatch(name))
avail = _cpv_to_version(_porttree().dep_bestmatch(name))
if avail and (not installed or salt.utils.compare_versions(ver1=installed, oper='<', ver2=avail, cmp_func=version_cmp)):
if avail and (not installed or salt.utils.versions.compare(ver1=installed, oper='<', ver2=avail, cmp_func=version_cmp)):
ret[name] = avail
# Return a string if only one package name passed

View file

@ -35,8 +35,6 @@ def useradd(pwfile, user, password, opts='', runas=None):
Add a user to htpasswd file using the htpasswd command. If the htpasswd
file does not exist, it will be created.
.. deprecated:: 2016.3.0
pwfile
Path to htpasswd file

View file

@ -56,6 +56,14 @@ try:
except ImportError:
HAS_LIBS = False
try:
# There is an API change in Kubernetes >= 2.0.0.
from kubernetes.client import V1beta1Deployment as AppsV1beta1Deployment
from kubernetes.client import V1beta1DeploymentSpec as AppsV1beta1DeploymentSpec
except ImportError:
from kubernetes.client import AppsV1beta1Deployment
from kubernetes.client import AppsV1beta1DeploymentSpec
log = logging.getLogger(__name__)
@ -876,7 +884,7 @@ def create_deployment(
'''
body = __create_object_body(
kind='Deployment',
obj_class=kubernetes.client.V1beta1Deployment,
obj_class=AppsV1beta1Deployment,
spec_creator=__dict_to_deployment_spec,
name=name,
namespace=namespace,
@ -1139,7 +1147,7 @@ def replace_deployment(name,
'''
body = __create_object_body(
kind='Deployment',
obj_class=kubernetes.client.V1beta1Deployment,
obj_class=AppsV1beta1Deployment,
spec_creator=__dict_to_deployment_spec,
name=name,
namespace=namespace,
@ -1410,9 +1418,9 @@ def __dict_to_object_meta(name, namespace, metadata):
def __dict_to_deployment_spec(spec):
'''
Converts a dictionary into kubernetes V1beta1DeploymentSpec instance.
Converts a dictionary into kubernetes AppsV1beta1DeploymentSpec instance.
'''
spec_obj = kubernetes.client.V1beta1DeploymentSpec()
spec_obj = AppsV1beta1DeploymentSpec()
for key, value in iteritems(spec):
if hasattr(spec_obj, key):
setattr(spec_obj, key, value)

View file

@ -430,7 +430,7 @@ def lvcreate(lvname,
cmd.extend(extra_arguments)
if force:
cmd.append('-yes')
cmd.append('--yes')
out = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
lvdev = '/dev/{0}/{1}'.format(vgname, lvname)

View file

@ -20,6 +20,7 @@ import logging
import salt.utils
import salt.utils.path
import salt.utils.pkg
import salt.utils.versions
from salt.exceptions import CommandExecutionError, MinionError
from salt.ext import six
from salt.ext.six.moves import zip
@ -128,7 +129,7 @@ def list_pkgs(versions_as_list=False, **kwargs):
name_and_versions = line.split(' ')
name = name_and_versions[0]
installed_versions = name_and_versions[1:]
key_func = functools.cmp_to_key(salt.utils.version_cmp)
key_func = functools.cmp_to_key(salt.utils.versions.version_cmp)
newest_version = sorted(installed_versions, key=key_func).pop()
except ValueError:
continue

View file

@ -42,6 +42,7 @@ import salt.utils.path
import salt.utils.pkg
import salt.utils.platform
import salt.utils.mac_utils
import salt.utils.versions
from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
@ -170,7 +171,7 @@ def latest_version(*names, **kwargs):
ret = {}
for key, val in six.iteritems(available):
if key not in installed or salt.utils.compare_versions(ver1=installed[key], oper='<', ver2=val):
if key not in installed or salt.utils.versions.compare(ver1=installed[key], oper='<', ver2=val):
ret[key] = val
else:
ret[key] = '{0} (installed)'.format(version(key))

View file

@ -11,7 +11,7 @@ import sys
# Import salt libs
import salt.minion
import salt.utils
import salt.utils.versions
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.ext.six import string_types
@ -338,7 +338,7 @@ def filter_by(lookup,
# remember to remove the expr_form argument from this function when
# performing the cleanup on this deprecation.
if expr_form is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Fluorine',
'the target type should be passed using the \'tgt_type\' '
'argument instead of \'expr_form\'. Support for using '

View file

@ -15,8 +15,9 @@ import salt.crypt
import salt.payload
import salt.utils
import salt.utils.args
import salt.utils.network
import salt.utils.event
import salt.utils.network
import salt.utils.versions
from salt.exceptions import SaltClientError
# Import 3rd-party libs
@ -292,7 +293,7 @@ def get(tgt,
# remember to remove the expr_form argument from this function when
# performing the cleanup on this deprecation.
if expr_form is not None:
salt.utils.warn_until(
salt.utils.versions.warn_until(
'Fluorine',
'the target type should be passed using the \'tgt_type\' '
'argument instead of \'expr_form\'. Support for using '

View file

@ -152,7 +152,8 @@ Optional small program to encrypt data without needing salt modules.
from __future__ import absolute_import
import base64
import os
import salt.utils
import salt.utils.files
import salt.utils.platform
import salt.utils.win_functions
import salt.utils.win_dacl
import salt.syspaths
@ -203,7 +204,7 @@ def _get_sk(**kwargs):
key = config['sk']
sk_file = config['sk_file']
if not key and sk_file:
with salt.utils.fopen(sk_file, 'rb') as keyf:
with salt.utils.files.fopen(sk_file, 'rb') as keyf:
key = str(keyf.read()).rstrip('\n')
if key is None:
raise Exception('no key or sk_file found')
@ -218,7 +219,7 @@ def _get_pk(**kwargs):
pubkey = config['pk']
pk_file = config['pk_file']
if not pubkey and pk_file:
with salt.utils.fopen(pk_file, 'rb') as keyf:
with salt.utils.files.fopen(pk_file, 'rb') as keyf:
pubkey = str(keyf.read()).rstrip('\n')
if pubkey is None:
raise Exception('no pubkey or pk_file found')
@ -256,9 +257,9 @@ def keygen(sk_file=None, pk_file=None):
if sk_file and pk_file is None:
if not os.path.isfile(sk_file):
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
with salt.utils.files.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
if salt.utils.platform.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
@ -277,25 +278,25 @@ def keygen(sk_file=None, pk_file=None):
if os.path.isfile(sk_file) and not os.path.isfile(pk_file):
# generate pk using the sk
with salt.utils.fopen(sk_file, 'rb') as keyf:
with salt.utils.files.fopen(sk_file, 'rb') as keyf:
sk = str(keyf.read()).rstrip('\n')
sk = base64.b64decode(sk)
kp = libnacl.public.SecretKey(sk)
with salt.utils.fopen(pk_file, 'w') as keyf:
with salt.utils.files.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved pk_file: {0}'.format(pk_file)
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
with salt.utils.files.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
if salt.utils.platform.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
else:
# chmod 0600 file
os.chmod(sk_file, 1536)
with salt.utils.fopen(pk_file, 'w') as keyf:
with salt.utils.files.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file)
@ -335,13 +336,13 @@ def enc_file(name, out=None, **kwargs):
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
with salt.utils.files.fopen(name, 'rb') as f:
data = f.read()
d = enc(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
with salt.utils.files.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d
@ -382,13 +383,13 @@ def dec_file(name, out=None, **kwargs):
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
with salt.utils.files.fopen(name, 'rb') as f:
data = f.read()
d = dec(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
with salt.utils.files.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d

View file

@ -30,6 +30,7 @@ import logging
# Import Salt libs
import salt.utils
import salt.utils.versions
from salt.exceptions import CommandExecutionError, MinionError
log = logging.getLogger(__name__)
@ -123,7 +124,7 @@ def latest_version(*names, **kwargs):
continue
pkgname += '--{0}'.format(flavor) if flavor else ''
cur = pkgs.get(pkgname, '')
if not cur or salt.utils.compare_versions(ver1=cur,
if not cur or salt.utils.versions.compare(ver1=cur,
oper='<',
ver2=pkgver):
ret[pkgname] = pkgver

View file

@ -24,13 +24,13 @@ import re
import logging
# Import salt libs
import salt.utils # Can be removed when is_true, compare_versions, compare_dicts are moved
import salt.utils # Can be removed when is_true, compare_dicts are moved
import salt.utils.args
import salt.utils.files
import salt.utils.itertools
import salt.utils.path
import salt.utils.pkg
from salt.utils.versions import LooseVersion as _LooseVersion
import salt.utils.versions
from salt.exceptions import (
CommandExecutionError, MinionError, SaltInvocationError
)
@ -327,13 +327,13 @@ def install(name=None,
else:
pkgstr = '{0}={1}'.format(pkgname, version_num)
cver = old.get(pkgname, '')
if reinstall and cver and salt.utils.compare_versions(
if reinstall and cver and salt.utils.versions.compare(
ver1=version_num,
oper='==',
ver2=cver,
cmp_func=version_cmp):
to_reinstall.append(pkgstr)
elif not cver or salt.utils.compare_versions(
elif not cver or salt.utils.versions.compare(
ver1=version_num,
oper='>=',
ver2=cver,
@ -1014,7 +1014,8 @@ def version_cmp(pkg1, pkg2, ignore_epoch=False):
output_loglevel='trace',
python_shell=False)
opkg_version = output.split(' ')[2].strip()
if _LooseVersion(opkg_version) >= _LooseVersion('0.3.4'):
if salt.utils.versions.LooseVersion(opkg_version) >= \
salt.utils.versions.LooseVersion('0.3.4'):
cmd_compare = ['opkg', 'compare-versions']
elif salt.utils.path.which('opkg-compare-versions'):
cmd_compare = ['opkg-compare-versions']

View file

@ -58,11 +58,11 @@ def _osquery(sql, format='json'):
cmd = 'osqueryi --json "{0}"'.format(sql)
res = __salt__['cmd.run_all'](cmd)
if res['retcode'] == 0:
ret['data'] = json.loads(res['stdout'])
else:
if res['stderr']:
ret['result'] = False
ret['error'] = res['stderr']
else:
ret['data'] = json.loads(res['stdout'])
return ret

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