mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2015.8' into '2016.3'
Conflicts: - salt/cloud/__init__.py - salt/returners/smtp_return.py
This commit is contained in:
commit
fe7ff0ebfa
21 changed files with 524 additions and 259 deletions
BIN
doc/_static/cloud-settings-inheritance.png
vendored
Normal file
BIN
doc/_static/cloud-settings-inheritance.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -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
|
||||
|
|
|
@ -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``:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _salt-cloud-map:
|
||||
|
||||
==============
|
||||
Cloud Map File
|
||||
==============
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _salt-cloud-profiles:
|
||||
|
||||
VM Profiles
|
||||
===========
|
||||
|
||||
|
|
138
doc/topics/cloud/qs.rst
Normal file
138
doc/topics/cloud/qs.rst
Normal 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>`.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
37
tests/unit/modules/zypp/zypper-products-sle11sp3.xml
Normal file
37
tests/unit/modules/zypp/zypper-products-sle11sp3.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version='1.0'?>
|
||||
<stream>
|
||||
<message type="info">Refreshing service 'nu_novell_com'.</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 world’s
|
||||
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 world’s
|
||||
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 world’s
|
||||
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>
|
|
@ -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):
|
||||
'''
|
||||
|
|
Loading…
Add table
Reference in a new issue