Merge branch '2015.8' into '2016.3'

Conflicts:
  - salt/cloud/__init__.py
  - salt/returners/smtp_return.py
This commit is contained in:
rallytime 2016-03-10 13:13:35 -07:00
commit fe7ff0ebfa
21 changed files with 524 additions and 259 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -2834,7 +2834,7 @@ out for 2015.8.0 and later minions.
.. code-block:: yaml
winrepo_dir: /srv/salt/win/repo-ng
winrepo_dir_ng: /srv/salt/win/repo-ng
.. conf_master:: winrepo_cachefile
.. conf_master:: win_repo_mastercachefile

View file

@ -1,3 +1,5 @@
.. _salt-cloud-config:
==================
Core Configuration
==================
@ -39,11 +41,26 @@ be used here.
In particular, this is the location to specify the location of the salt master
and its listening port, if the port is not set to the default.
Similar to most other settings, Minion configuration settings are inherited
across configuration files. For example, the master setting might be contained
in the main ``cloud`` configuration file as demonstrated above, but additional
settings can be placed in the provider or profile:
.. code-block:: yaml
ec2-web:
size: t1.micro
minion:
environment: test
startup_states: sls
sls_list:
- web
Cloud Configuration Syntax
==========================
The data specific to interacting with public clouds is set up here.
The data specific to interacting with public clouds is set up :ref:`here
<cloud-provider-specifics>`.
Cloud provider configuration settings can live in several places. The first is in
``/etc/salt/cloud``:

View file

@ -7,120 +7,57 @@ Salt Cloud
.. raw:: html
:file: index.html
Getting Started
===============
Configuration
=============
Salt Cloud provides a powerful interface to interact with cloud hosts. This
interface is tightly integrated with Salt, and new virtual machines
are automatically connected to your Salt master after creation.
Salt Cloud is built-in to Salt and is configured on and executed from your Salt Master.
Since Salt Cloud is designed to be an automated system, most configuration
is done using the following YAML configuration files:
Define a Provider
-----------------
- ``/etc/salt/cloud``: The main configuration file, contains global settings
that apply to all cloud hosts. See :ref:`Salt Cloud Configuration
<salt-cloud-config>`.
The first step is to add the credentials for your cloud host. Credentials
and other settings provided by the cloud host are stored in provider configuration files.
Provider configurations contain the details needed to connect to a cloud host such as EC2, GCE, Rackspace, etc.,
and any global options that you want set on your cloud minions (such as the location of your Salt Master).
- ``/etc/salt/cloud.providers.d/*.conf``: Contains settings that configure
a specific cloud host, such as credentials, region settings, and so on. Since
configuration varies significantly between each cloud host, a separate file
should be created for each cloud host. In Salt Cloud, a provider is
synonymous with a cloud host (Amazon EC2, Google Compute Engine, Rackspace,
and so on). See :ref:`Provider Specifics <cloud-provider-specifics>`.
On your Salt Master, browse to ``/etc/salt/cloud.providers.d/`` and create a file called ``<provider>.provider.conf``,
replacing ``<provider>`` with ``ec2``, ``softlayer``, and so on. The name helps you identify the contents, and is not
important as long as the file ends in ``.conf``.
- ``/etc/salt/cloud.profiles.d/*.conf``: Contains settings that define
a specific VM type. A profile defines the systems specs and image, and any
other settings that are specific to this VM type. Each specific VM type is
called a profile, and multiple profiles can be defined in a profile file.
Each profile references a parent provider that defines the cloud host in
which the VM is created (the provider settings are in the provider
configuration explained above). Based on your needs, you might define
different profiles for web servers, database servers, and so on. See :ref:`VM
Profiles <cloud-provider-specifics>`.
Next, browse to the :ref:`Provider specifics <cloud-provider-specifics>` and add any required settings for your
cloud host to this file. Here is an example for Amazon EC2:
Configuration Inheritance
=========================
Configuration settings are inherited in order from the cloud config =>
providers => profile.
.. code-block:: yaml
.. image:: /_static/cloud-settings-inheritance.png
:align: center
:width: 40%
my-ec2:
driver: ec2
# Set the EC2 access credentials (see below)
#
id: 'HJGRYCILJLKJYG'
key: 'kdjgfsgm;woormgl/aserigjksjdhasdfgn'
# Make sure this key is owned by root with permissions 0400.
#
private_key: /etc/salt/my_test_key.pem
keyname: my_test_key
securitygroup: default
# Optional: Set up the location of the Salt Master
#
minion:
master: saltmaster.example.com
For example, if you wanted to use the same image for
all virtual machines for a specific provider, the image name could be placed in
the provider file. This value is inherited by all profiles that use that
provider, but is overridden if a image name is defined in the profile.
The required configuration varies between cloud hosts so make sure you read the provider specifics.
Most configuration settings can be defined in any file, the main difference
being how that setting is inherited.
List Cloud Provider Options
---------------------------
You can now query the cloud provider you configured for available locations,
images, and sizes. This information is used when you set up VM profiles.
.. code-block:: bash
salt-cloud --list-locations <provider_name> # my-ec2 in the previous example
salt-cloud --list-images <provider_name>
salt-cloud --list-sizes <provider_name>
Replace ``<provider_name>`` with the name of the provider configuration you defined.
Create VM Profiles
------------------
On your Salt Master, browse to ``/etc/salt/cloud.profiles.d/`` and create a file called ``<provider>.profiles.conf``,
replacing ``<provider>`` with ``ec2``, ``softlayer``, and so on. The file must end in ``.conf``.
You can now add any custom profiles you'd like to define to this file. Here are a few examples:
.. code-block:: yaml
micro_ec2:
provider: my-ec2
image: ami-d514f291
size: t1.micro
medium_ec2:
provider: my-ec2
image: ami-d514f291
size: m3.medium
large_ec2:
provider: my-ec2
image: ami-d514f291
size: m3.large
Notice that the ``provider`` in our profile matches the provider name that we defined? That is how Salt Cloud
knows how to connect to create a VM with these attributes.
Create VMs
----------
VMs are created by calling ``salt-cloud`` with the following options:
.. code-block:: bash
salt-cloud -p <profile> <name1> <name2> ...
For example:
.. code-block:: bash
salt-cloud -p micro_ec2 minion1 minion2
Destroy VMs
-----------
Add a ``-d`` and the minion name you provided to destroy:
.. code-block:: bash
salt-cloud -d minion1 minion2
Query VMs
---------
You can view details about the VMs you've created using ``--query``:
.. code-block:: bash
salt-cloud --query
QuickStart
==========
The :ref:`Salt Cloud Quickstart <salt-cloud-qs>` walks you through defining
a provider, a VM profile, and shows you how to create virtual machines using Salt Cloud.
Using Salt Cloud
================
@ -223,4 +160,5 @@ Tutorials
.. toctree::
:maxdepth: 3
QuickStart <qs>
Using Salt Cloud with the Event Reactor <reactor>

View file

@ -1,3 +1,5 @@
.. _salt-cloud-map:
==============
Cloud Map File
==============

View file

@ -1,3 +1,5 @@
.. _salt-cloud-profiles:
VM Profiles
===========

138
doc/topics/cloud/qs.rst Normal file
View file

@ -0,0 +1,138 @@
.. _salt-cloud-qs:
=====================
Salt Cloud Quickstart
=====================
Salt Cloud is built-in to Salt, and the easiest way to run Salt Cloud is
directly from your Salt Master. On most platforms you can install the
``salt-cloud`` package from the same repo that you used to install Salt.
This quickstart walks you through the basic steps of setting up a cloud host
and defining some virtual machines to create.
.. note:: Salt Cloud has its own process and does not rely on the Salt Master,
so it can be installed on a standalone minion instead of your Salt Master.
Define a Provider
-----------------
The first step is to add the credentials for your cloud host. Credentials and
other settings provided by the cloud host are stored in provider configuration
files. Provider configurations contain the details needed to connect to a cloud
host such as EC2, GCE, Rackspace, etc., and any global options that you want
set on your cloud minions (such as the location of your Salt Master).
On your Salt Master, browse to ``/etc/salt/cloud.providers.d/`` and create
a file called ``<provider>.conf``, replacing ``<provider>`` with
``ec2``, ``softlayer``, and so on. The name helps you identify the contents,
and is not important as long as the file ends in ``.conf``.
Next, browse to the :ref:`Provider specifics <cloud-provider-specifics>` and
add any required settings for your cloud host to this file. Here is an example
for Amazon EC2:
.. code-block:: yaml
my-ec2:
driver: ec2
# Set the EC2 access credentials (see below)
#
id: 'HJGRYCILJLKJYG'
key: 'kdjgfsgm;woormgl/aserigjksjdhasdfgn'
# Make sure this key is owned by root with permissions 0400.
#
private_key: /etc/salt/my_test_key.pem
keyname: my_test_key
securitygroup: default
# Optional: Set up the location of the Salt Master
#
minion:
master: saltmaster.example.com
The required configuration varies between cloud hosts so make sure you read the
provider specifics.
List Cloud Provider Options
---------------------------
You can now query the cloud provider you configured for available locations,
images, and sizes. This information is used when you set up VM profiles.
.. code-block:: bash
salt-cloud --list-locations <provider_name> # my-ec2 in the previous example
salt-cloud --list-images <provider_name>
salt-cloud --list-sizes <provider_name>
Replace ``<provider_name>`` with the name of the provider configuration you defined.
Create VM Profiles
------------------
On your Salt Master, browse to ``/etc/salt/cloud.profiles.d/`` and create
a file called ``<profile>.conf``, replacing ``<profile>`` with
``ec2``, ``softlayer``, and so on. The file must end in ``.conf``.
You can now add any custom profiles you'd like to define to this file. Here are
a few examples:
.. code-block:: yaml
micro_ec2:
provider: my-ec2
image: ami-d514f291
size: t1.micro
medium_ec2:
provider: my-ec2
image: ami-d514f291
size: m3.medium
large_ec2:
provider: my-ec2
image: ami-d514f291
size: m3.large
Notice that the ``provider`` in our profile matches the provider name that we
defined? That is how Salt Cloud knows how to connect to to a cloud host to
create a VM with these attributes.
Create VMs
----------
VMs are created by calling ``salt-cloud`` with the following options:
.. code-block:: bash
salt-cloud -p <profile> <name1> <name2> ...
For example:
.. code-block:: bash
salt-cloud -p micro_ec2 minion1 minion2
Destroy VMs
-----------
Add a ``-d`` and the minion name you provided to destroy:
.. code-block:: bash
salt-cloud -d minion1 minion2
Query VMs
---------
You can view details about the VMs you've created using ``--query``:
.. code-block:: bash
salt-cloud --query
Cloud Map
---------
Now that you know how to create and destoy individual VMs, next you should
learn how to use a cloud map to create a number of VMs at once.
Cloud maps let you define a map of your infrastructure and quickly provision
any number of VMs. On subsequent runs, any VMs that do not exist are created,
and VMs that are already configured are left unmodified.
See :ref:`Cloud Map File <salt-cloud-map>`.

View file

@ -826,7 +826,7 @@ different from the base must be specified of the alternates:
'python': 'dev-python/mysql-python',
},
},
merge=salt['pillar.get']('mysql:lookup'), default='default') %}
merge=salt['pillar.get']('mysql:lookup', default='default') %}
Overriding values in the lookup table

View file

@ -1296,13 +1296,17 @@ class Cloud(object):
)
)
client = salt.client.get_local_client(mopts=mopts_)
client = salt.client.get_local_client(mopts=self.opts)
ret = client.cmd(vm_['name'], 'saltutil.sync_{0}'.format(
self.opts['sync_after_install']
))
log.info('Synchronized the following dynamic modules:')
log.info(' {0}'.format(ret))
ret = client.cmd(
vm_['name'],
'saltutil.sync_{0}'.format(self.opts['sync_after_install']),
timeout=self.opts['timeout']
)
log.info(
six.u('Synchronized the following dynamic modules: '
' {0}').format(ret)
)
except KeyError as exc:
log.exception(
'Failed to create VM {0}. Configuration value {1} needs '
@ -1392,19 +1396,26 @@ class Cloud(object):
if main_cloud_config is None:
main_cloud_config = {}
profile_details = self.opts['profiles'][profile]
alias, driver = profile_details['provider'].split(':')
mapped_providers = self.map_providers_parallel()
alias_data = mapped_providers.setdefault(alias, {})
vms = alias_data.setdefault(driver, {})
profile_details = self.opts['profiles'][profile]
vms = {}
for prov in mapped_providers:
prov_name = mapped_providers[prov].keys()[0]
for node in mapped_providers[prov][prov_name]:
vms[node] = mapped_providers[prov][prov_name][node]
vms[node]['provider'] = prov
vms[node]['driver'] = prov_name
alias, driver = profile_details['provider'].split(':')
provider_details = self.opts['providers'][alias][driver].copy()
del provider_details['profiles']
for name in names:
if name in vms:
msg = '{0} already exists under {1}:{2}'.format(
name, alias, driver
prov = vms[name]['provider']
driv = vms[name]['driver']
msg = six.u('{0} already exists under {1}:{2}').format(
name, prov, driv
)
log.error(msg)
ret[name] = {'Error': msg}

View file

@ -1187,6 +1187,117 @@ def _list_nodes(full=False, for_output=False):
return ret
def reboot(name, call=None):
'''
Reboot a droplet in DigitalOcean.
.. versionadded:: 2015.8.8
name
The name of the droplet to restart.
CLI Example:
.. code-block:: bash
salt-cloud -a reboot droplet_name
'''
if call != 'action':
raise SaltCloudSystemExit(
'The restart action must be called with -a or --action.'
)
data = show_instance(name, call='action')
if data.get('status') == 'off':
return {'success': True,
'action': 'stop',
'status': 'off',
'msg': 'Machine is already off.'}
ret = query(droplet_id=data['id'],
command='actions',
args={'type': 'reboot'},
http_method='post')
return {'success': True,
'action': ret['action']['type'],
'state': ret['action']['status']}
def start(name, call=None):
'''
Start a droplet in DigitalOcean.
.. versionadded:: 2015.8.8
name
The name of the droplet to start.
CLI Example:
.. code-block:: bash
salt-cloud -a start droplet_name
'''
if call != 'action':
raise SaltCloudSystemExit(
'The start action must be called with -a or --action.'
)
data = show_instance(name, call='action')
if data.get('status') == 'active':
return {'success': True,
'action': 'start',
'status': 'active',
'msg': 'Machine is already running.'}
ret = query(droplet_id=data['id'],
command='actions',
args={'type': 'power_on'},
http_method='post')
return {'success': True,
'action': ret['action']['type'],
'state': ret['action']['status']}
def stop(name, call=None):
'''
Stop a droplet in DigitalOcean.
.. versionadded:: 2015.8.8
name
The name of the droplet to stop.
CLI Example:
.. code-block:: bash
salt-cloud -a stop droplet_name
'''
if call != 'action':
raise SaltCloudSystemExit(
'The stop action must be called with -a or --action.'
)
data = show_instance(name, call='action')
if data.get('status') == 'off':
return {'success': True,
'action': 'stop',
'status': 'off',
'msg': 'Machine is already off.'}
ret = query(droplet_id=data['id'],
command='actions',
args={'type': 'shutdown'},
http_method='post')
return {'success': True,
'action': ret['action']['type'],
'state': ret['action']['status']}
def _get_full_output(node, for_output=False):
'''
Helper function for _list_nodes to loop through all node information.

View file

@ -221,8 +221,9 @@ def user_list(database=None, user=None, password=None, host=None, port=None):
client = _client(user=user, password=password, host=host, port=port)
if database:
client.switch_database(database)
return client.get_list_users()
return client.get_list_cluster_admins()
if hasattr(client, 'get_list_cluster_admins') and not database:
return client.get_list_cluster_admins()
return client.get_list_users()
def user_exists(
@ -262,7 +263,17 @@ def user_exists(
users = user_list(database, user, password, host, port)
if not isinstance(users, list):
return False
return name in [u['name'] for u in users]
for user in users:
# the dict key could be different depending on influxdb version
username = user.get('user', user.get('name'))
if username:
if username == name:
return True
else:
log.warning('Could not find username in user: %s', user)
return False
def user_create(name, passwd, database=None, user=None, password=None,

View file

@ -60,14 +60,19 @@ def __virtual__():
if __grains__['os'] in enable:
if __grains__['os'] == 'XenServer':
return __virtualname__
if __grains__['os'] == 'SUSE':
if str(__grains__['osrelease']).startswith('11'):
return __virtualname__
else:
return (False, 'Cannot load rh_service module on SUSE > 11')
try:
osrelease = float(__grains__.get('osrelease', 0))
except ValueError:
return (False, 'Cannot load rh_service module: '
'osrelease grain, {0}, not a float,'.format(osrelease))
if __grains__['os'] == 'SUSE':
if osrelease >= 12:
return (False, 'Cannot load rh_service module on SUSE >= 12')
if __grains__['os'] == 'Fedora':
if osrelease > 15:
return (False, 'Cannot load rh_service module on Fedora >= 15')

View file

@ -367,7 +367,7 @@ def add_ace(path, objectType, user, permission, acetype, propagation):
'''
ret = {'result': None,
'changes': {},
'comment': []}
'comment': ''}
if (path and user and
permission and acetype
@ -406,19 +406,15 @@ def add_ace(path, objectType, user, permission, acetype, propagation):
dc.getPropagationText(objectTypeBit, propagation)))
ret['result'] = True
except Exception as e:
ret['comment'].append((
'An error occurred attempting to add the ace. The error was {0}'
).format(e))
ret['comment'] = 'An error occurred attempting to add the ace. The error was {0}'.format(e)
ret['result'] = False
return ret
if acesAdded:
ret['changes']['Added ACEs'] = acesAdded
else:
ret['comment'].append((
'Unable to obtain the DACL of {0}'
).format(path))
ret['comment'] = 'Unable to obtain the DACL of {0}'.format(path)
else:
ret['comment'].append('An empty value was specified for a required item.')
ret['comment'] = 'An empty value was specified for a required item.'
ret['result'] = False
return ret
@ -444,7 +440,7 @@ def rm_ace(path, objectType, user, permission, acetype, propagation):
'''
ret = {'result': None,
'changes': {},
'comment': []}
'comment': ''}
if (path and user and
permission and acetype
@ -501,13 +497,10 @@ def rm_ace(path, objectType, user, permission, acetype, propagation):
ret['result'] = True
except Exception as e:
ret['result'] = False
ret['comment'].append((
'Error removing ACE. The error was {0}'
).format(e))
ret['comment'] = 'Error removing ACE. The error was {0}.'.format(e)
return ret
else:
ret['comment'].append((
'The specified ACE was not found on the path'))
ret['comment'] = 'The specified ACE was not found on the path.'
return ret
@ -609,9 +602,7 @@ def _set_dacl_inheritance(path, objectType, inheritance=True, copy=True, clear=F
ret['result'] = True
except Exception as e:
ret['result'] = False
ret['comment'] = (
'Error attempting to set the inheritance. The error was {0}'
).format(e)
ret['comment'] = 'Error attempting to set the inheritance. The error was {0}.'.format(e)
return ret
@ -653,7 +644,7 @@ def check_inheritance(path, objectType):
ret = {'result': False,
'Inheritance': False,
'comment': []}
'comment': ''}
dc = daclConstants()
objectType = dc.getObjectTypeBit(objectType)
path = dc.processPath(path, objectType)
@ -663,9 +654,7 @@ def check_inheritance(path, objectType):
dacls = sd.GetSecurityDescriptorDacl()
except Exception as e:
ret['result'] = False
ret['comment'].append((
'Error obtaining the Security Descriptor or DACL of the path: {0}'
).format(e))
ret['comment'] = 'Error obtaining the Security Descriptor or DACL of the path: {0}.'.format(e)
return ret
for counter in range(0, dacls.GetAceCount()):
@ -691,7 +680,7 @@ def check_ace(path, objectType, user=None, permission=None, acetype=None, propag
'''
ret = {'result': False,
'Exists': False,
'comment': []}
'comment': ''}
dc = daclConstants()
objectTypeBit = dc.getObjectTypeBit(objectType)
@ -709,12 +698,11 @@ def check_ace(path, objectType, user=None, permission=None, acetype=None, propag
userSid = win32security.LookupAccountName('', user)[0]
except Exception as e:
ret['result'] = False
ret['comment'].append((
'Unable to obtain the security identifier for {0}. The exception was {1}'
).format(user, e))
ret['comment'] = 'Unable to obtain the security identifier for {0}. The exception was {1}.'.format(user, e)
return ret
dacls = _get_dacl(path, objectTypeBit)
ret['result'] = True
if dacls:
if objectTypeBit == win32security.SE_FILE_OBJECT:
if check_inheritance(path, objectType)['Inheritance']:
@ -730,17 +718,12 @@ def check_ace(path, objectType, user=None, permission=None, acetype=None, propag
if (ace[0][1] & propagationbit) == propagationbit:
if exactPermissionMatch:
if ace[1] == permissionbit:
ret['result'] = True
ret['Exists'] = True
return ret
else:
if (ace[1] & permissionbit) == permissionbit:
ret['result'] = True
ret['Exists'] = True
return ret
else:
ret['result'] = False
ret['comment'].append(
'Error obtaining DACL for object')
ret['result'] = True
ret['comment'] = 'No DACL found for object.'
return ret

View file

@ -1482,7 +1482,7 @@ def list_products(all=False, refresh=False):
for prd in doc.getElementsByTagName('product-list')[0].getElementsByTagName('product'):
p_nfo = dict()
for k_p_nfo, v_p_nfo in prd.attributes.items():
p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo == 'true'
p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo in ['true', '1']
p_nfo['eol'] = prd.getElementsByTagName('endoflife')[0].getAttribute('text')
p_nfo['eol_t'] = int(prd.getElementsByTagName('endoflife')[0].getAttribute('time_t'))
p_nfo['description'] = " ".join(

View file

@ -2,58 +2,47 @@
'''
Return salt data via email
The following fields can be set in the minion conf file::
The following fields can be set in the minion conf file. Fields are optional
unless noted otherwise.
smtp.from (required)
smtp.to (required)
smtp.host (required)
smtp.port (optional, defaults to 25)
smtp.username (optional)
smtp.password (optional)
smtp.tls (optional, defaults to False)
smtp.subject (optional, but helpful)
smtp.gpgowner (optional)
smtp.fields (optional)
smtp.template (optional)
smtp.renderer (optional)
* ``from`` (required) The name/address of the email sender.
* ``to`` (required) The name/address of the email recipient.
* ``host`` (required) The SMTP server hostname or address.
* ``port`` The SMTP server port; defaults to ``25``.
* ``username`` The username used to authenticate to the server. If specified a
password is also required. It is recommended but not required to also use
TLS with this option.
* ``password`` The password used to authenticate to the server.
* ``tls`` Whether to secure the connection using TLS; defaults to ``False``
* ``subject`` The email subject line.
* ``fields`` Which fields from the returned data to include in the subject line
of the email; comma-delimited. For example: ``id,fun``. Please note, *the
subject line is not encrypted*.
* ``gpgowner`` A user's :file:`~/.gpg` directory. This must contain a gpg
public key matching the address the mail is sent to. If left unset, no
encryption will be used. Requires :program:`python-gnupg` to be installed.
* ``template`` The path to a file to be used as a template for the email body.
* ``renderer`` A Salt renderer, or render-pipe, to use to render the email
template. Default ``jinja``.
Below is an example of the above settings in a Salt Minion configuration file:
.. code-block:: yaml
smtp.from: me@example.net
smtp.to: you@example.com
smtp.host: localhost
smtp.port: 1025
Alternative configuration values can be used by prefacing the configuration.
Any values not found in the alternative configuration will be pulled from
the default location::
the default location. For example:
alternative.smtp.from
alternative.smtp.to
alternative.smtp.host
alternative.smtp.port
alternative.smtp.username
alternative.smtp.password
alternative.smtp.tls
alternative.smtp.subject
alternative.smtp.gpgowner
alternative.smtp.fields
alternative.smtp.template
alternative.smtp.renderer
.. code-block:: yaml
There are a few things to keep in mind:
* If a username is used, a password is also required. It is recommended (but
not required) to use the TLS setting when authenticating.
* You should at least declare a subject, but you don't have to.
* The use of encryption, i.e. setting gpgowner in your settings, requires
python-gnupg to be installed.
* The field gpgowner specifies a user's ~/.gpg directory. This must contain a
gpg public key matching the address the mail is sent to. If left unset, no
encryption will be used.
* smtp.fields lets you include the value(s) of various fields in the subject
line of the email. These are comma-delimited. For instance::
smtp.fields: id,fun
...will display the id of the minion and the name of the function in the
subject line. You may also use 'jid' (the job id), but it is generally
recommended not to use 'return', which contains the entire return data
structure (which can be very large). Also note that the subject is always
unencrypted.
alternative.smtp.username: saltdev
alternative.smtp.password: saltdev
alternative.smtp.tls: True
To use the SMTP returner, append '--return smtp' to the salt command.
@ -77,6 +66,13 @@ To override individual configuration items, append --return_kwargs '{"key:": "va
salt '*' test.ping --return smtp --return_kwargs '{"to": "user@domain.com"}'
An easy way to test the SMTP returner is to use the development SMTP server
built into Python. The command below will start a single-threaded SMTP server
that prints any email it receives to the console.
.. code-block:: python
python -m smtpd -n -c DebuggingServer localhost:1025
'''
from __future__ import absolute_import
@ -84,6 +80,7 @@ from __future__ import absolute_import
import os
import logging
import smtplib
import StringIO
from email.utils import formatdate
# Import Salt libs
@ -115,6 +112,7 @@ def _get_options(ret=None):
attrs = {'from': 'from',
'to': 'to',
'host': 'host',
'port': 'port',
'username': 'username',
'password': 'password',
'subject': 'subject',
@ -144,12 +142,12 @@ def returner(ret):
port = _options.get('port')
user = _options.get('username')
passwd = _options.get('password')
subject = _options.get('subject')
subject = _options.get('subject') or 'Email from Salt'
gpgowner = _options.get('gpgowner')
fields = _options.get('fields').split(',') if 'fields' in _options else []
smtp_tls = _options.get('tls')
renderer = _options.get('renderer', __opts__.get('renderer', 'yaml_jinja'))
renderer = _options.get('renderer') or 'jinja'
rend = salt.loader.render(__opts__, {})
if not port:
@ -159,6 +157,9 @@ def returner(ret):
if field in ret:
subject += ' {0}'.format(ret[field])
subject = compile_template(':string:', rend, renderer, input_data=subject, **ret)
if isinstance(subject, StringIO.StringIO):
subject = subject.read()
log.debug("smtp_return: Subject is '{0}'".format(subject))
template = _options.get('template')
@ -184,6 +185,9 @@ def returner(ret):
content = 'Encryption failed, the return data was not sent.\r\n\r\n{0}\r\n{1}'.format(
encrypted_data.status, encrypted_data.stderr)
if isinstance(content, StringIO.StringIO):
content = content.read()
message = ('From: {0}\r\n'
'To: {1}\r\n'
'Date: {2}\r\n'

View file

@ -89,14 +89,13 @@ def present(name, objectType, user, permission, acetype, propagation):
ret = {'name': name,
'result': True,
'changes': {},
'comment': []}
'comment': ''}
tRet = __salt__['win_dacl.check_ace'](name, objectType, user, permission, acetype, propagation, True)
if tRet['result']:
if not tRet['Exists']:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'The ACE is set to be added')
ret['comment'] = 'The ACE is set to be added.'
ret['changes']['Added ACEs'] = ((
'{0} {1} {2} on {3}'
).format(user, acetype, permission, propagation))
@ -107,16 +106,14 @@ def present(name, objectType, user, permission, acetype, propagation):
ret['changes'] = dict(ret['changes'], **addRet['changes'])
else:
ret['result'] = False
ret['comment'] = ret['comment'] + addRet['comment']
ret['comment'] = ' '.join([ret['comment'], addRet['comment']])
else:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'The ACE is present')
ret['comment'] = 'The ACE is present.'
else:
ret['result'] = False
ret['comment'] = tRet['comment']
return ret
return ret
@ -127,14 +124,13 @@ def absent(name, objectType, user, permission, acetype, propagation):
ret = {'name': name,
'result': True,
'changes': {},
'comment': []}
'comment': ''}
tRet = __salt__['win_dacl.check_ace'](name, objectType, user, permission, acetype, propagation, True)
if tRet['result']:
if tRet['Exists']:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'The ACE is set to be removed')
ret['comment'] = 'The ACE is set to be removed.'
ret['changes']['Removed ACEs'] = ((
'{0} {1} {2} on {3}'
).format(user, acetype, permission, propagation))
@ -145,16 +141,14 @@ def absent(name, objectType, user, permission, acetype, propagation):
ret['changes'] = dict(ret['changes'], **addRet['changes'])
else:
ret['result'] = False
ret['comment'] = ret['comment'] + addRet['comment']
ret['comment'] = ' '.join([ret['comment'], addRet['comment']])
else:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'The ACE is not present')
ret['comment'] = 'The ACE is not present.'
else:
ret['result'] = False
ret['comment'] = tRet['comment']
return ret
return ret
@ -165,15 +159,14 @@ def inherit(name, objectType, clear_existing_acl=False):
ret = {'name': name,
'result': True,
'changes': {},
'comment': []}
'comment': ''}
tRet = __salt__['win_dacl.check_inheritance'](name, objectType)
if tRet['result']:
if not tRet['Inheritance']:
if __opts__['test']:
ret['result'] = None
ret['changes']['Inheritance'] = "Enabled"
ret['comment'].append(
'Inheritance is set to be enabled')
ret['comment'] = 'Inheritance is set to be enabled.'
ret['changes']['Existing ACLs'] = (
'Are set to be removed' if clear_existing_acl else 'Are set to be kept')
return ret
@ -183,16 +176,14 @@ def inherit(name, objectType, clear_existing_acl=False):
ret['changes'] = dict(ret['changes'], **eRet['changes'])
else:
ret['result'] = False
ret['comment'] = ret['comment'] + eRet['comment']
ret['comment'] = ' '.join([ret['comment'], eRet['comment']])
else:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'Inheritance is enabled')
ret['comment'] = 'Inheritance is enabled.'
else:
ret['result'] = False
ret['comment'] = tRet['comment']
return ret
return ret
@ -203,32 +194,28 @@ def disinherit(name, objectType, copy_inherited_acl=True):
ret = {'name': name,
'result': True,
'changes': {},
'comment': []}
'comment': ''}
tRet = __salt__['win_dacl.check_inheritance'](name, objectType)
if tRet['result']:
if tRet['Inheritance']:
if __opts__['test']:
ret['result'] = None
ret['changes']['Inheritance'] = "Disabled"
ret['comment'].append(
'Inheritance is set to be disabled')
ret['comment'] = 'Inheritance is set to be disabled.'
ret['changes']['Inherited ACLs'] = (
'Are set to be kept' if copy_inherited_acl else 'Are set to be removed')
return ret
eRet = __salt__['win_dacl.disable_inheritance'](name, objectType, copy_inherited_acl)
ret['result'] = eRet['result']
if eRet['result']:
ret['result'] = True
ret['changes'] = dict(ret['changes'], **eRet['changes'])
else:
ret['result'] = False
ret['comment'] = ret['comment'] + eRet['comment']
ret['comment'] = ' '.join([ret['comment'], eRet['comment']])
else:
if __opts__['test']:
ret['result'] = None
ret['comment'].append(
'Inheritance is disabled')
ret['comment'] = 'Inheritance is disabled.'
else:
ret['result'] = False
ret['comment'] = tRet['comment']
return ret
return ret

View file

@ -86,7 +86,7 @@ import salt.utils
# Import Third Party Libs
try:
from pyVim.connect import SmartConnect, Disconnect
from pyVim.connect import GetSi, SmartConnect, Disconnect
from pyVmomi import vim, vmodl
HAS_PYVMOMI = True
except ImportError:
@ -184,6 +184,13 @@ def get_service_instance(host, username, password, protocol=None, port=None):
if port is None:
port = 443
service_instance = GetSi()
if service_instance:
if service_instance._GetStub().host == ':'.join([host, str(port)]):
service_instance._GetStub().GetConnection()
return service_instance
Disconnect(service_instance)
try:
service_instance = SmartConnect(
host=host,

View file

@ -193,9 +193,9 @@ class PkgModuleTest(integration.ModuleCase,
self.assertIn('rpm', keys)
self.assertIn('yum', keys)
elif os_family == 'Suse':
ret = self.run_function(func, ['bash-completion', 'zypper'])
ret = self.run_function(func, ['less', 'zypper'])
keys = ret.keys()
self.assertIn('bash-completion', keys)
self.assertIn('less', keys)
self.assertIn('zypper', keys)

View file

@ -0,0 +1,37 @@
<?xml version='1.0'?>
<stream>
<message type="info">Refreshing service &apos;nu_novell_com&apos;.</message>
<message type="info">Loading repository data...</message>
<message type="info">Reading installed packages...</message>
<product-list>
<product name="SUSE_SLES" version="11.3" release="1.138" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Linux Enterprise Server 11 SP3" shortname="" flavor="" isbase="0" repo="nu_novell_com:SLES11-SP3-Pool" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Linux Enterprise offers a comprehensive
suite of products built on a single code base.
The platform addresses business needs from
the smallest thin-client devices to the worlds
most powerful high-performance computing
and mainframe servers. SUSE Linux Enterprise
offers common management tools and technology
certifications across the platform, and
each product is enterprise-class.</description></product>
<product name="SUSE_SLES-SP4-migration" version="11.3" release="1.4" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE_SLES Service Pack 4 Migration Product" shortname="" flavor="" isbase="0" repo="nu_novell_com:SLES11-SP3-Updates" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Linux Enterprise offers a comprehensive
suite of products built on a single code base.
The platform addresses business needs from
the smallest thin-client devices to the worlds
most powerful high-performance computing
and mainframe servers. SUSE Linux Enterprise
offers common management tools and technology
certifications across the platform, and
each product is enterprise-class.</description></product>
<product name="SUSE_SLES" version="11.3" release="1.201" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Linux Enterprise Server 11 SP3" shortname="" flavor="" isbase="0" repo="nu_novell_com:SLES11-SP3-Updates" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Linux Enterprise offers a comprehensive
suite of products built on a single code base.
The platform addresses business needs from
the smallest thin-client devices to the worlds
most powerful high-performance computing
and mainframe servers. SUSE Linux Enterprise
offers common management tools and technology
certifications across the platform, and
each product is enterprise-class.</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="0" repo="nu_novell_com:SUSE-Manager-Server-2.1-Pool" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="manager" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="1" repo="@System" installed="1"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
</product-list>
</stream>

View file

@ -150,26 +150,38 @@ class ZypperTestCase(TestCase):
'''
List products test.
'''
ref_out = {
'retcode': 0,
'stdout': get_test_data('zypper-products.xml')
}
with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
products = zypper.list_products()
self.assertEqual(len(products), 5)
self.assertEqual(['SLES', 'SLES', 'SUSE-Manager-Proxy', 'SUSE-Manager-Server', 'sle-manager-tools-beta'],
sorted([prod['name'] for prod in products]))
self.assertIn('SUSE LLC <https://www.suse.com/>', [product['vendor'] for product in products])
self.assertEqual([False, False, False, False, True],
sorted([product['isbase'] for product in products]))
self.assertEqual([False, False, False, False, True],
sorted([product['installed'] for product in products]))
self.assertEqual(['0', '0', '0', '0', '0'],
sorted([product['release'] for product in products]))
self.assertEqual([False, False, False, False, u'sles'],
sorted([product['productline'] for product in products]))
self.assertEqual([1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
sorted([product['eol_t'] for product in products]))
for filename, test_data in {
'zypper-products-sle12sp1.xml': {
'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy',
'SUSE-Manager-Server', 'sle-manager-tools-beta'],
'vendor': 'SUSE LLC <https://www.suse.com/>',
'release': ['0', '0', '0', '0', '0'],
'productline': [False, False, False, False, 'sles'],
'eol_t': [1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
'isbase': [False, False, False, False, True],
'installed': [False, False, False, False, True],
},
'zypper-products-sle11sp3.xml': {
'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server',
'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'],
'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany',
'release': ['1.138', '1.2', '1.2', '1.201', '1.4'],
'productline': [False, False, False, False, 'manager'],
'eol_t': [0, 0, 0, 0, 0],
'isbase': [False, False, False, False, True],
'installed': [False, False, False, False, True],
}}.items():
ref_out = {
'retcode': 0,
'stdout': get_test_data(filename)
}
with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
products = zypper.list_products()
self.assertEqual(len(products), 5)
self.assertIn(test_data['vendor'], [product['vendor'] for product in products])
for kwd in ['name', 'isbase', 'installed', 'release', 'productline', 'eol_t']:
self.assertEqual(test_data[kwd], sorted([prod[kwd] for prod in products]))
def test_refresh_db(self):
'''