Merge remote-tracking branch 'upstream/2015.8' into merge-forward-develop

Conflicts:
    salt/cloud/clouds/ec2.py
    salt/config/__init__.py
    salt/modules/git.py
    salt/modules/hosts.py
    salt/modules/htpasswd.py
    salt/states/boto_secgroup.py
    salt/states/htpasswd.py
    tests/unit/modules/schedule_test.py
This commit is contained in:
Colton Myers 2015-10-20 18:13:11 -06:00
commit a471832fa7
65 changed files with 3452 additions and 679 deletions

View file

@ -402,7 +402,8 @@
# Set the file client. The client defaults to looking on the master server for
# files, but can be directed to look at the local file directory setting
# defined below by setting it to local.
# defined below by setting it to "local". Setting a local file_client runs the
# minion in masterless mode.
#file_client: remote
# The file directory works on environments passed to the minion, each environment

View file

@ -164,8 +164,8 @@ project = 'Salt'
copyright = '2015 SaltStack, Inc.'
version = salt.version.__version__
latest_release = '2015.8.0' # latest release
previous_release = '2015.5.5' # latest release from previous branch
latest_release = '2015.8.1' # latest release
previous_release = '2015.5.6' # latest release from previous branch
previous_release_dir = '2015.5' # path on web server for previous branch
build_type = 'develop' # latest, previous, develop
@ -230,11 +230,11 @@ rst_prolog = """\
.. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers
.. |windownload| raw:: html
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-x86-Setup.exe"><strong>Salt-Minion-{release}-3-x86-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-x86-Setup.exe.md5"><strong>md5</strong></a></p>
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe"><strong>Salt-Minion-{release}-x86-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe.md5"><strong>md5</strong></a></p>
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-AMD64-Setup.exe"><strong>Salt-Minion-{release}-3-AMD64-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-3-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe"><strong>Salt-Minion-{release}-AMD64-Setup.exe</strong></a>
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
""".format(release=release)

View file

@ -307,7 +307,7 @@ https://github.com/saltstack-formulas/salt-formula
.. _faq-grain-security:
Is Targeting using Grain Data Secure?
=====================================
-------------------------------------
Because grains can be set by users that have access to the minion configuration
files on the local system, grains are considered less secure than other

View file

@ -53,6 +53,7 @@ Full list of builtin execution modules
cabal
cassandra
cassandra_cql
chassis
chef
chocolatey
cloud
@ -87,6 +88,7 @@ Full list of builtin execution modules
dockerng
dpkg
drac
dracr
drbd
ebuild
eix

View file

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

View file

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

View file

@ -10,5 +10,6 @@ Full list of builtin proxy modules
:toctree:
:template: autosummary.rst.tmpl
fx2
junos
rest_sample

View file

@ -0,0 +1,6 @@
==============
salt.proxy.fx2
==============
.. automodule:: salt.proxy.fx2
:members:

View file

@ -50,6 +50,7 @@ Full list of builtin state modules
cyg
ddns
debconfmod
dellchassis
disk
dockerio
dockerng

View file

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

View file

@ -969,7 +969,7 @@ the network interfaces of your virtual machines, for example:-
# Uncomment this to associate an existing Elastic IP Address with
# this network interface:
#
# associate_eip: eni-XXXXXXXX
# associate_eip: eipalloc-XXXXXXXX
# You can allocate more than one IP address to an interface. Use the
# 'ip addr list' command to see them.

View file

@ -67,6 +67,9 @@ Set up an initial profile at ``/etc/salt/cloud.profiles`` or
provider: my-proxmox-config
image: local:vztmpl/ubuntu-12.04-standard_12.04-1_amd64.tar.gz
technology: openvz
# host needs to be set to the configured name of the proxmox host
# and not the ip address or FQDN of the server
host: myvmhost
ip_address: 192.168.100.155
password: topsecret

View file

@ -12,19 +12,24 @@ Installation from the SaltStack Repository
2015.8.0 and later packages for Debian 8 (Jessie) are available in the
SaltStack repository.
.. important::
The repository folder structure changed between 2015.8.0 and 2015.8.1. If you
previously configured this repository, verify that your paths contain
``latest``.
To install using the SaltStack repository:
#. Run the following command to import the SaltStack repository key:
.. code-block:: bash
wget -O - https://repo.saltstack.com/apt/debian/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
wget -O - https://repo.saltstack.com/apt/debian/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
#. Add the following line to ``/etc/apt/sources.list``:
.. code-block:: bash
deb http://repo.saltstack.com/apt/debian jessie contrib
deb http://repo.saltstack.com/apt/debian/latest jessie main
#. Run ``sudo apt-get update``.

View file

@ -10,6 +10,11 @@ Installation from the SaltStack Repository
2015.8.0 and later packages for Ubuntu 14 (Trusty) and Ubuntu 12 (Precise) are
available in the SaltStack repository.
.. important::
The repository folder structure changed between 2015.8.0 and 2015.8.1. If you
previously configured this repository, verify that your paths contain
``latest``.
To install using the SaltStack repository:
#. Run the following command to import the SaltStack repository key:
@ -18,13 +23,13 @@ To install using the SaltStack repository:
.. code-block:: bash
wget -O - https://repo.saltstack.com/apt/ubuntu/ubuntu14/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
wget -O - https://repo.saltstack.com/apt/ubuntu/ubuntu14/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
Ubuntu 12:
.. code-block:: bash
wget -O - https://repo.saltstack.com/apt/ubuntu/ubuntu12/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
wget -O - https://repo.saltstack.com/apt/ubuntu/ubuntu12/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
#. Add the following line to ``/etc/apt/sources.list``:
@ -32,13 +37,13 @@ To install using the SaltStack repository:
.. code-block:: bash
deb http://repo.saltstack.com/apt/ubuntu/ubuntu14 trusty main
deb http://repo.saltstack.com/apt/ubuntu/ubuntu14/latest trusty main
Ubuntu 12:
.. code-block:: bash
deb http://repo.saltstack.com/apt/ubuntu/ubuntu12 precise main
deb http://repo.saltstack.com/apt/ubuntu/ubuntu12/latest precise main
#. Run ``sudo apt-get update``.

View file

@ -85,6 +85,10 @@ by their ``os`` grain:
company: Foo Industries
.. important::
See :ref:`Is Targeting using Grain Data Secure? <faq-grain-security>` for
important security information.
The above pillar sets two key/value pairs. If a minion is running RedHat, then
the ``apache`` key is set to ``httpd`` and the ``git`` key is set to the value
of ``git``. If the minion is running Debian, those values are changed to

View file

@ -24,6 +24,9 @@ and discovery, control, status, remote execution, and state management.
See the :doc:`Proxy Minion Walkthrough </topics/proxyminion/demo>` for an end-to-end
demonstration of a working proxy minion.
See the :doc:`Proxy Minion SSH Walkthrough </topics/proxyminion/ssh>` for an end-to-end
demonstration of a working SSH proxy minion.
New in 2015.8.2
---------------
@ -540,3 +543,178 @@ And then in salt.proxy.rest_sample.py we find
:glob:
demo
SSH Proxymodules
----------------
See above for a general introduction to writing proxy modules.
All of the guidelines that apply to REST are the same for SSH.
This sections specifically talks about the SSH proxy module and
explains the working of the example proxy module ``ssh_sample``.
Here is a simple example proxymodule used to interface to a device over SSH.
Code for the SSH shell is in the `salt-contrib GitHub repository <https://github.com/saltstack/salt-contrib/proxyminion_ssh_example>`_
This proxymodule enables "package" installation.
.. code-block:: python
# -*- coding: utf-8 -*-
'''
This is a simple proxy-minion designed to connect to and communicate with
a server that exposes functionality via SSH.
This can be used as an option when the device does not provide
an api over HTTP and doesn't have the python stack to run a minion.
'''
from __future__ import absolute_import
# Import python libs
import json
import logging
# Import Salt's libs
from salt.utils.vt_helper import SSHConnection
from salt.utils.vt import TerminalException
# This must be present or the Salt loader won't load this module
__proxyenabled__ = ['ssh_sample']
DETAILS = {}
# Want logging!
log = logging.getLogger(__file__)
# This does nothing, it's here just as an example and to provide a log
# entry when the module is loaded.
def __virtual__():
'''
Only return if all the modules are available
'''
log.info('ssh_sample proxy __virtual__() called...')
return True
def init(opts):
'''
Required.
Can be used to initialize the server connection.
'''
try:
DETAILS['server'] = SSHConnection(host=__opts__['proxy']['host'],
username=__opts__['proxy']['username'],
password=__opts__['proxy']['password'])
# connected to the SSH server
out, err = DETAILS['server'].sendline('help')
except TerminalException as e:
log.error(e)
return False
def shutdown(opts):
'''
Disconnect
'''
DETAILS['server'].close_connection()
def parse(out):
'''
Extract json from out.
Parameter
out: Type string. The data returned by the
ssh command.
'''
jsonret = []
in_json = False
for ln_ in out.split('\n'):
if '{' in ln_:
in_json = True
if in_json:
jsonret.append(ln_)
if '}' in ln_:
in_json = False
return json.loads('\n'.join(jsonret))
def package_list():
'''
List "packages" by executing a command via ssh
This function is called in response to the salt command
..code-block::bash
salt target_minion pkg.list_pkgs
'''
# Send the command to execute
out, err = DETAILS['server'].sendline('pkg_list')
# "scrape" the output and return the right fields as a dict
return parse(out)
def package_install(name, **kwargs):
'''
Install a "package" on the REST server
'''
cmd = 'pkg_install ' + name
if 'version' in kwargs:
cmd += '/'+kwargs['version']
else:
cmd += '/1.0'
# Send the command to execute
out, err = DETAILS['server'].sendline(cmd)
# "scrape" the output and return the right fields as a dict
return parse(out)
def package_remove(name):
'''
Remove a "package" on the REST server
'''
cmd = 'pkg_remove ' + name
# Send the command to execute
out, err = DETAILS['server'].sendline(cmd)
# "scrape" the output and return the right fields as a dict
return parse(out)
Connection Setup
################
The ``init()`` method is responsible for connection setup. It uses the ``host``, ``username`` and ``password`` config variables defined in the pillar data. The ``prompt`` kwarg can be passed to ``SSHConnection`` if your SSH server's prompt differs from the example's prompt ``(Cmd)``. Instantiating the ``SSHConnection`` class establishes an SSH connection to the ssh server (using Salt VT).
Command execution
#################
The ``package_*`` methods use the SSH connection (established in ``init()``) to send commands out to the SSH server. The ``sendline()`` method of ``SSHConnection`` class can be used to send commands out to the server. In the above example we send commands like ``pkg_list`` or ``pkg_install``. You can send any SSH command via this utility.
Output parsing
##############
Output returned by ``sendline()`` is a tuple of strings representing the stdout and the stderr respectively. In the toy example shown we simply scrape the output and convert it to a python dictionary, as shown in the ``parse`` method. You can tailor this method to match your parsing logic.
Connection teardown
###################
The ``shutdown`` method is responsible for calling the ``close_connection()`` method of ``SSHConnection`` class. This ends the SSH connection to the server.
For more information please refer to class `SSHConnection`_.
.. toctree::
:maxdepth: 2
:glob:
ssh
.. _SSHConnection: https://github.com/saltstack/salt/blob/b8271c7512da7e048019ee26422be9e7d6b795ab/salt/utils/vt_helper.py#L28

View file

@ -0,0 +1,82 @@
========================================
Salt Proxy Minion SSH End-to-End Example
========================================
The following is walkthrough that documents how to run a sample SSH service
and configure one or more proxy minions to talk to and control it.
1. This walkthrough uses a custom SSH shell to provide an end to end example.
Any other shells can be used too.
2. Setup the proxy command shell as shown https://github.com/saltstack/salt-contrib/tree/master/proxyminion_ssh_example
Now, configure your salt-proxy.
1. Edit ``/etc/salt/proxy`` and add an entry for your master's location
.. code-block:: yaml
master: localhost
add_proxymodule_to_opts: False
multiprocessing: False
2. On your salt-master, ensure that pillar is configured properly. Select an ID
for your proxy (in this example we will name the proxy with the letter 'p'
followed by the port the proxy is answering on). In your pillar topfile,
place an entry for your proxy:
.. code-block:: yaml
base:
'p8000':
- p8000
This says that Salt's pillar should load some values for the proxy ``p8000``
from the file /srv/pillar/p8000.sls (if you have not changed your default pillar_roots)
3. In the pillar root for your base environment, create this file:
.. code-block:: yaml
p8000.sls
---------
proxy:
proxytype: ssh_sample
host: saltyVM
username: salt
password: badpass
4. Make sure your salt-master is running.
5. Start the salt-proxy in debug mode
.. code-block:: bash
salt-proxy --proxyid=p8000 -l debug
6. Accept your proxy's key on your salt-master
.. code-block:: bash
salt-key -y -a p8000
The following keys are going to be accepted:
Unaccepted Keys:
p8000
Key for minion p8000 accepted.
7. Now you should be able to run commands on your proxy.
.. code-block:: bash
salt p8000 pkg.list_pkgs
8. The SSH shell implements a degenerately simple pkg.
To "install" a package, use a standard
``pkg.install``. If you pass '==' and a verrsion number after the package
name then the service will parse that and accept that as the package's
version.

View file

@ -5,6 +5,18 @@ Salt 2015.5.6 Release Notes
Version 2015.5.6 is a bugfix release for :doc:`2015.5.0
</topics/releases/2015.5.0>`.
Security Fixes
--------------
CVE-2015-6941 - ``win_useradd`` module and ``salt-cloud`` display passwords in debug log
Updated the ``win_useradd`` module return data to no longer include the password of the newly created user. The password is now replaced with the string ``XXX-REDACTED-XXX``.
Updated the Salt Cloud debug output to no longer display ``win_password`` and ``sudo_password`` authentication credentials.
CVE-2015-6918 - Git modules leaking HTTPS auth credentials to debug log
Updated the Git state and execution modules to no longer display HTTPS basic authentication credentials in loglevel debug output on the Salt master. These credentials are now replaced with ``REDACTED`` in the debug output. Thanks to Andreas Stieger <asteiger@suse.com> for bringing this to our attention.
Changes for v2015.5.5..v2015.5.6
--------------------------------

View file

@ -248,6 +248,13 @@ Deprecations
- The use of ``delim`` was removed from the following functions in the ``match``
execution module: ``pillar_pcre``, ``pillar``, ``grain_pcre``,
Security Fixes
==============
CVE-2015-6918 - Git modules leaking HTTPS auth credentials to debug log
Updated the Git state and execution modules to no longer display HTTPS basic authentication credentials in loglevel debug output on the Salt master. These credentials are now replaced with ``REDACTED`` in the debug output. Thanks to Andreas Stieger <asteiger@suse.com> for bringing this to our attention.
Major Bug Fixes
===============

View file

@ -5,7 +5,20 @@ Salt 2015.8.1 Release Notes
Version 2015.8.1 is a bugfix release for :doc:`2015.8.0
</topics/releases/2015.8.0>`.
Changes:
Security Fixes
--------------
CVE-2015-6941 - ``win_useradd`` module and ``salt-cloud`` display passwords in debug log
Updated the ``win_useradd`` module return data to no longer include the password of the newly created user. The password is now replaced with the string ``XXX-REDACTED-XXX``.
Updated the Salt Cloud debug output to no longer display ``win_password`` and ``sudo_password`` authentication credentials. Also updated the Linode driver to no longer display authentication credentials in debug logs. These credentials are now replaced with ``REDACTED`` in the debug output.
CVE-2015-6918 - Git modules leaking HTTPS auth credentials to debug log
Updated the Git state and execution modules to no longer display HTTPS basic authentication credentials in loglevel debug output on the Salt master. These credentials are now replaced with ``REDACTED`` in the debug output. Thanks to Andreas Stieger <asteiger@suse.com> for bringing this to our attention.
Major Bug Fixes
---------------
- Add support for ``spm.d/*.conf`` configuration of SPM (:issue:`27010`)
- Fix ``proxy`` grains breakage for non-proxy minions (:issue:`27039`)

View file

@ -22,6 +22,10 @@ to a custom grain, grain data is refreshed.
Grains resolve to lowercase letters. For example, ``FOO``, and ``foo``
target the same grain.
.. important::
See :ref:`Is Targeting using Grain Data Secure? <faq-grain-security>` for
important security information.
Match all CentOS minions:
.. code-block:: bash
@ -197,6 +201,67 @@ change, consider using :doc:`Pillar <../pillar/index>` instead.
<minion-start-reactor>` to ensure that the custom grains are synced when
the minion starts.
Loading Custom Grains
---------------------
If you have multiple functions specifying grains that are called from a ``main``
function, be sure to prepend grain function names with an underscore. This prevents
Salt from including the loaded grains from the grain functions in the final
grain data structure. For example, consider this custom grain file:
.. code-block:: python
#!/usr/bin/env python
def _my_custom_grain():
my_grain = {'foo': 'bar', 'hello': 'world'}
return my_grain
def main():
# initialize a grains dictionary
grains = {}
grains['my_grains'] = _my_custom_grain()
return grains
The output of this example renders like so:
.. code-block:: bash
# salt-call --local grains.items
local:
----------
<Snipped for brevity>
my_grains:
----------
foo:
bar
hello:
world
However, if you don't prepend the ``my_custom_grain`` function with an underscore,
the function will be rendered twice by Salt in the items output: once for the
``my_custom_grain`` call itself, and again when it is called in the ``main``
function:
.. code-block:: bash
# salt-call --local grains.items
local:
----------
<Snipped for brevity>
foo:
bar
<Snipped for brevity>
hello:
world
<Snipped for brevity>
my_grains:
----------
foo:
bar
hello:
world
Precedence
==========

View file

@ -73,14 +73,14 @@ def beacon(config):
.. code-block:: yaml
beacons:
- load:
- 1m:
load:
1m:
- 0.0
- 2.0
- 5m:
5m:
- 0.0
- 1.5
- 15m:
15m:
- 0.1
- 1.0
@ -94,11 +94,11 @@ def beacon(config):
avg_keys = ['1m', '5m', '15m']
avg_dict = dict(zip(avg_keys, avgs))
# Check each entry for threshold
if float(avgs[0]) < float(config[0]['1m'][0]) or \
float(avgs[0]) > float(config[0]['1m'][1]) or \
float(avgs[1]) < float(config[1]['5m'][0]) or \
float(avgs[1]) > float(config[1]['5m'][1]) or \
float(avgs[2]) < float(config[2]['15m'][0]) or \
float(avgs[2]) > float(config[2]['15m'][1]):
if float(avgs[0]) < float(config['1m'][0]) or \
float(avgs[0]) > float(config['1m'][1]) or \
float(avgs[1]) < float(config['5m'][0]) or \
float(avgs[1]) > float(config['5m'][1]) or \
float(avgs[2]) < float(config['15m'][0]) or \
float(avgs[2]) > float(config['15m'][1]):
ret.append(avg_dict)
return ret

View file

@ -106,7 +106,9 @@ class Master(parsers.MasterOptionParser):
'udp://',
'file://')):
# Logfile is not using Syslog, verify
current_umask = os.umask(0o027)
verify_files([logfile], self.config['user'])
os.umask(current_umask)
# Clear out syndics from cachedir
for syndic_file in os.listdir(self.config['syndic_dir']):
os.remove(os.path.join(self.config['syndic_dir'], syndic_file))
@ -505,7 +507,9 @@ class Syndic(parsers.SyndicOptionParser):
'udp://',
'file://')):
# Logfile is not using Syslog, verify
current_umask = os.umask(0o027)
verify_files([logfile], self.config['user'])
os.umask(current_umask)
except OSError as err:
logger.exception('Failed to prepare salt environment')
self.shutdown(err.errno)

View file

@ -1270,7 +1270,7 @@ class Cloud(object):
time.sleep(3)
mopts_ = salt.config.DEFAULT_MINION_OPTS
conf_path = '/'.join(__opts__['conf_file'].split('/')[:-1])
conf_path = '/'.join(self.opts['conf_file'].split('/')[:-1])
mopts_.update(
salt.config.minion_config(
os.path.join(conf_path,

View file

@ -93,6 +93,8 @@ class SaltCloud(parsers.SaltCloudParser):
log.info('salt-cloud starting')
try:
mapper = salt.cloud.Map(self.config)
except SaltCloudSystemExit as exc:
self.handle_exception(exc.args, exc)
except SaltCloudException as exc:
msg = 'There was an error generating the mapper.'
self.handle_exception(msg, exc)
@ -417,7 +419,7 @@ class SaltCloud(parsers.SaltCloudParser):
def handle_exception(self, msg, exc):
if isinstance(exc, SaltCloudException):
# It's a know exception an we know own to handle it
# It's a known exception and we know how to handle it
if isinstance(exc, SaltCloudSystemExit):
# This is a salt cloud system exit
if exc.exit_code > 0:

View file

@ -243,6 +243,11 @@ def create(vm_):
except AttributeError:
pass
# Since using "provider: <provider-engine>" is deprecated, alias provider
# to use driver: "driver: <provider-engine>"
if 'provider' in vm_:
vm_['driver'] = vm_.pop('provider')
salt.utils.cloud.fire_event(
'event',
'starting create',
@ -250,7 +255,7 @@ def create(vm_):
{
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['provider'],
'provider': vm_['driver'],
},
transport=__opts__['transport']
)
@ -383,7 +388,7 @@ def create(vm_):
{
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['provider'],
'provider': vm_['driver'],
},
transport=__opts__['transport']
)

View file

@ -595,12 +595,10 @@ def create_disk_from_distro(vm_, linode_id, swap_size=None):
'The Linode driver requires a password.'
)
distribution_id = get_distribution_id(vm_)
kwargs.update({'LinodeID': linode_id,
'DistributionID': distribution_id,
'DistributionID': get_distribution_id(vm_),
'Label': vm_['name'],
'Size': get_disk_size(vm_, swap_size)})
'Size': get_disk_size(vm_, swap_size, linode_id)})
result = _query('linode', 'disk.createfromdistribution', args=kwargs)
@ -750,15 +748,14 @@ def get_datacenter_id(location):
return avail_locations()[location]['DATACENTERID']
def get_disk_size(vm_, swap):
def get_disk_size(vm_, swap, linode_id):
r'''
Returns the size of of the root disk in MB.
vm_
The VM to get the disk size for.
'''
vm_size = get_vm_size(vm_)
disk_size = vm_size
disk_size = get_linode(kwargs={'linode_id': linode_id})['TOTALHD']
return config.get_cloud_config_value(
'disk_size', vm_, __opts__, default=disk_size - swap
)
@ -979,7 +976,7 @@ def get_swap_size(vm_):
The VM profile to obtain the swap size from.
'''
return config.get_cloud_config_value(
'wap', vm_, __opts__, default=128
'swap', vm_, __opts__, default=128
)

View file

@ -467,6 +467,18 @@ def create(vm_, call=None):
__opts__['internal_lxc_profile'] = __opts__['profile']
del __opts__['profile']
salt.utils.cloud.fire_event(
'event',
'created instance',
'salt/cloud/{0}/created'.format(vm_['name']),
{
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
transport=__opts__['transport']
)
return ret
@ -532,13 +544,10 @@ def get_configured_provider(vm_=None):
{}).get(dalias, {}).get(driver, {})
# in all cases, verify that the linked saltmaster is alive.
if data:
try:
ret = _salt('test.ping', salt_target=data['target'])
if not ret:
raise Exception('error')
return data
except Exception:
ret = _salt('test.ping', salt_target=data['target'])
if not ret:
raise SaltCloudSystemExit(
'Configured provider {0} minion: {1} is unreachable'.format(
__active_provider_name__, data['target']))
return data
return False

View file

@ -123,10 +123,9 @@ def _authenticate():
'password', get_configured_provider(), __opts__, search_global=False
)
verify_ssl = config.get_cloud_config_value(
'verify_ssl', get_configured_provider(), __opts__, search_global=False
'verify_ssl', get_configured_provider(), __opts__,
default=True, search_global=False
)
if verify_ssl is None:
verify_ssl = True
connect_data = {'username': username, 'password': passwd}
full_url = 'https://{0}:8006/api2/json/access/ticket'.format(url)

View file

@ -87,7 +87,7 @@ VALID_OPTS = {
# Selects a random master when starting a minion up in multi-master mode
'master_shuffle': bool,
# When in mulit-master mode, temporarily remove a master from the list if a conenction
# When in multi-master mode, temporarily remove a master from the list if a conenction
# is interrupted and try another master in the list.
'master_alive_interval': int,
@ -240,7 +240,7 @@ VALID_OPTS = {
# The ipc strategy. (i.e., sockets versus tcp, etc)
'ipc_mode': str,
# Enable ipv6 support for deamons
# Enable ipv6 support for daemons
'ipv6': bool,
# The chunk size to use when streaming files with the file server
@ -395,7 +395,7 @@ VALID_OPTS = {
'range_server': str,
# The tcp keepalive interval to set on TCP ports. This setting can be used to tune salt connectivity
# issues in messy network environments with misbeahving firewalls
# issues in messy network environments with misbehaving firewalls
'tcp_keepalive': bool,
# Sets zeromq TCP keepalive idle. May be used to tune issues with minion disconnects
@ -1203,7 +1203,13 @@ DEFAULT_MASTER_OPTS = {
DEFAULT_PROXY_MINION_OPTS = {
'conf_file': os.path.join(salt.syspaths.CONFIG_DIR, 'proxy'),
'log_file': os.path.join(salt.syspaths.LOGS_DIR, 'proxy'),
'add_proxymodule_to_opts': True
'add_proxymodule_to_opts': True,
# Default multiprocessing to False since anything that needs
# salt.vt will have trouble with our forking model.
# Proxies with non-persistent (mostly REST API) connections
# can change this back to True
'multiprocessing': False
}
# ----- Salt Cloud Configuration Defaults ----------------------------------->
@ -1505,7 +1511,14 @@ def include_config(include, orig_path, verbose):
for fn_ in sorted(glob.glob(path)):
log.debug('Including configuration from \'{0}\''.format(fn_))
salt.utils.dictupdate.update(configuration, _read_conf_file(fn_))
opts = _read_conf_file(fn_)
include = opts.get('include', [])
if include:
opts.update(include_config(include, fn_, verbose))
salt.utils.dictupdate.update(configuration, opts)
return configuration

View file

@ -10,6 +10,7 @@ import logging
import hashlib
import os
import shutil
import ftplib
# Import salt libs
from salt.exceptions import (
@ -228,6 +229,7 @@ class Client(object):
# go through the list of all files finding ones that are in
# the target directory and caching them
for fn_ in self.file_list(saltenv):
fn_ = salt.utils.locales.sdecode(fn_)
if fn_.strip() and fn_.startswith(path):
if salt.utils.check_include_exclude(
fn_, include_pat, exclude_pat):
@ -571,6 +573,15 @@ class Client(object):
return dest
except Exception:
raise MinionError('Could not fetch from {0}'.format(url))
if url_data.scheme == 'ftp':
try:
ftp = ftplib.FTP(url_data.hostname)
ftp.login()
with salt.utils.fopen(dest, 'wb') as fp_:
ftp.retrbinary('RETR {0}'.format(url_data.path), fp_.write)
return dest
except Exception as exc:
raise MinionError('Could not retrieve {0} from FTP server. Exception: {1}'.format(url, exc))
if url_data.scheme == 'swift':
try:

View file

@ -551,7 +551,8 @@ class Fileserver(object):
if 'path' not in load or 'saltenv' not in load:
return ''
fnd = self.find_file(load['path'], load['saltenv'])
fnd = self.find_file(salt.utils.locales.sdecode(load['path']),
load['saltenv'])
if not fnd.get('back'):
return ''
fstr = '{0}.file_hash'.format(fnd['back'])

View file

@ -75,6 +75,9 @@ LIBCLOUD_FUNCS_NOT_SUPPORTED = (
'rackspace.list_locations'
)
# Will be set to pyximport module at runtime if cython is enabled in config.
pyximport = None
def static_loader(
opts,
@ -989,8 +992,9 @@ class LazyLoader(salt.utils.lazy.LazyDict):
if self.opts.get('cython_enable', True) is True:
try:
self.pyximport = __import__('pyximport') # pylint: disable=import-error
self.pyximport.install()
global pyximport
pyximport = __import__('pyximport') # pylint: disable=import-error
pyximport.install()
# add to suffix_map so file_mapping will pick it up
self.suffix_map['.pyx'] = tuple()
except ImportError:
@ -1129,7 +1133,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
try:
sys.path.append(os.path.dirname(fpath))
if suffix == '.pyx':
mod = self.pyximport.load_module(name, fpath, tempfile.gettempdir())
mod = pyximport.load_module(name, fpath, tempfile.gettempdir())
elif suffix == '.o':
top_mod = __import__(fpath, globals(), locals(), [])
comps = fpath.split('.')

43
salt/modules/chassis.py Normal file
View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
'''
Glue execution module to link to the :doc:`fx2 proxymodule </ref/proxy/all/salt.proxy.fx2>`.
Depends: :doc:`iDRAC Remote execution module (salt.modules.dracr) </ref/modules/all/salt.modules.dracr>`
For documentation on commands that you can direct to a Dell chassis via proxy,
look in the documentation for :doc:`salt.modules.dracr </ref/modules/all/salt.modules.dracr>`.
This execution module calls through to a function in the fx2 proxy module
called ``chconfig``. That function looks up the function passed in the ``cmd``
parameter in :doc:`salt.modules.dracr </ref/modules/all/salt.modules.dracr>` and calls it.
.. versionadded:: 2015.8.2
'''
from __future__ import absolute_import
# Import python libs
import logging
import salt.utils
log = logging.getLogger(__name__)
__proxyenabled__ = ['fx2']
__virtualname__ = 'chassis'
def __virtual__():
'''
Only work on proxy
'''
if salt.utils.is_proxy():
return __virtualname__
return False
def cmd(cmd, *args, **kwargs):
proxycmd = __opts__['proxy']['proxytype'] + '.chconfig'
kwargs['admin_username'] = __pillar__['proxy']['admin_username']
kwargs['admin_password'] = __pillar__['proxy']['admin_password']
kwargs['host'] = __pillar__['proxy']['host']
return __proxy__[proxycmd](cmd, *args, **kwargs)

View file

@ -289,7 +289,7 @@ __func_alias__ = {
}
# Minimum supported versions
MIN_DOCKER = (1, 0, 0)
MIN_DOCKER = (1, 4, 0)
MIN_DOCKER_PY = (1, 4, 0)
VERSION_RE = r'([\d.]+)'
@ -420,9 +420,6 @@ VALID_CREATE_OPTS = {
'labels': {
'path': 'Config:Labels',
},
}
VALID_RUNTIME_OPTS = {
'binds': {
'path': 'HostConfig:Binds',
},
@ -525,18 +522,7 @@ def _get_docker_py_versioninfo():
'''
Returns a version_info tuple for docker-py
'''
contextkey = 'docker.docker_py_version'
if contextkey in __context__:
return __context__[contextkey]
match = re.match(VERSION_RE, str(docker.__version__))
if match:
__context__[contextkey] = tuple(
[int(x) for x in match.group(1).split('.')]
)
else:
log.warning('Unable to determine docker-py version')
__context__[contextkey] = None
return __context__[contextkey]
return docker.version_info
# Decorators
@ -1052,8 +1038,7 @@ def _error_detail(data, item):
data.append(msg)
def _validate_input(action,
kwargs,
def _validate_input(kwargs,
validate_ip_addrs=True):
'''
Perform validation on kwargs. Checks each key in kwargs against the
@ -1683,15 +1668,6 @@ def _validate_input(action,
kwargs['labels'] = salt.utils.repack_dictlist(kwargs['labels'])
# And now, the actual logic to perform the validation
if action == 'create':
valid_opts = VALID_CREATE_OPTS
elif action == 'runtime':
valid_opts = VALID_RUNTIME_OPTS
else:
raise SaltInvocationError(
'Invalid validation action \'{0}\''.format(action)
)
if 'docker.docker_version' not in __context__:
# Have to call this func using the __salt__ dunder (instead of just
# version()) because this _validate_input() will be imported into the
@ -1709,40 +1685,40 @@ def _validate_input(action,
_locals = locals()
for kwarg in kwargs:
if kwarg not in valid_opts:
if kwarg not in VALID_CREATE_OPTS:
raise SaltInvocationError('Invalid argument \'{0}\''.format(kwarg))
# Check for Docker/docker-py compatibility
compat_errors = []
if 'min_docker' in valid_opts[kwarg]:
min_docker = valid_opts[kwarg]['min_docker']
if 'min_docker' in VALID_CREATE_OPTS[kwarg]:
min_docker = VALID_CREATE_OPTS[kwarg]['min_docker']
if __context__['docker.docker_version'] is not None:
if __context__['docker.docker_version'] < min_docker:
compat_errors.append(
'The \'{0}\' parameter requires at least Docker {1} '
'(detected version {2})'.format(
kwarg,
'.'.join(min_docker),
'.'.join(map(str, min_docker)),
'.'.join(__context__['docker.docker_version'])
)
)
if 'min_docker_py' in valid_opts[kwarg]:
if 'min_docker_py' in VALID_CREATE_OPTS[kwarg]:
cur_docker_py = _get_docker_py_versioninfo()
if cur_docker_py is not None:
min_docker_py = valid_opts[kwarg]['min_docker_py']
min_docker_py = VALID_CREATE_OPTS[kwarg]['min_docker_py']
if cur_docker_py < min_docker_py:
compat_errors.append(
'The \'{0}\' parameter requires at least docker-py '
'{1} (detected version {2})'.format(
kwarg,
'.'.join(min_docker_py),
'.'.join(cur_docker_py)
'.'.join(map(str, min_docker_py)),
'.'.join(map(str, cur_docker_py))
)
)
if compat_errors:
raise SaltInvocationError('; '.join(compat_errors))
default_val = valid_opts[kwarg].get('default')
default_val = VALID_CREATE_OPTS[kwarg].get('default')
if kwargs[kwarg] is None:
if default_val is None:
# Passed as None and None is the default. Skip validation. This
@ -1753,7 +1729,7 @@ def _validate_input(action,
# None, don't let them do this.
raise SaltInvocationError(kwarg + ' cannot be None')
validator = valid_opts[kwarg].get('validator')
validator = VALID_CREATE_OPTS[kwarg].get('validator')
if validator is None:
# Look for custom validation function
validator = kwarg
@ -2136,6 +2112,11 @@ def inspect_image(name):
Retrieves image information. Equivalent to running the ``docker inspect``
Docker CLI command, but will only look for image information.
.. note::
To inspect an image, it must have been pulled from a registry or built
locally. Images on a Docker registry which have not been pulled cannot
be inspected.
name
Image name or ID
@ -2554,6 +2535,7 @@ def version():
@_refresh_mine_cache
def create(image,
name=None,
validate_ip_addrs=True,
client_timeout=CLIENT_TIMEOUT,
**kwargs):
'''
@ -2694,6 +2676,166 @@ def create(image,
Example: ``labels=LABEL1,LABEL2``,
``labels="{'LABEL1': 'value1', 'LABEL2': 'value2'}"``
validate_ip_addrs : True
For parameters which accept IP addresses as input, IP address
validation will be performed. To disable, set this to ``False``
binds
Files/directories to bind mount. Each bind mount should be passed in
the format ``<host_path>:<container_path>:<read_only>``, where
``<read_only>`` is one of ``rw`` (for read-write access) or ``ro`` (for
read-only access). Optionally, the read-only information can be left
off the end and the bind mount will be assumed to be read-write.
Examples 2 and 3 below are equivalent.
Example 1: ``binds=/srv/www:/var/www:ro``
Example 2: ``binds=/srv/www:/var/www:rw``
Example 3: ``binds=/srv/www:/var/www``
port_bindings
Bind exposed ports which were exposed using the ``ports`` argument to
:py:func:`dockerng.create <salt.modules.dockerng.create>`. These
should be passed in the same way as the ``--publish`` argument to the
``docker run`` CLI command:
- ``ip:hostPort:containerPort`` - Bind a specific IP and port on the
host to a specific port within the container.
- ``ip::containerPort`` - Bind a specific IP and an ephemeral port to a
specific port within the container.
- ``hostPort:containerPort`` - Bind a specific port on all of the
host's interfaces to a specific port within the container.
- ``containerPort`` - Bind an ephemeral port on all of the host's
interfaces to a specific port within the container.
Multiple bindings can be separated by commas, or passed as a Python
list. The below two examples are equivalent:
Example 1: ``port_bindings="5000:5000,2123:2123/udp,8080"``
Example 2: ``port_bindings="['5000:5000', '2123:2123/udp', '8080']"``
.. note::
When configuring bindings for UDP ports, the protocol must be
passed in the ``containerPort`` value, as seen in the examples
above.
lxc_conf
Additional LXC configuration parameters to set before starting the
container.
Example: ``lxc_conf="{lxc.utsname: docker}"``
.. note::
These LXC configuration parameters will only have the desired
effect if the container is using the LXC execution driver, which
has not been the default for some time.
publish_all_ports : False
Allocates a random host port for each port exposed using the ``ports``
argument to :py:func:`dockerng.create <salt.modules.dockerng.create>`.
Example: ``publish_all_ports=True``
links
Link this container to another. Links should be specified in the format
``<container_name_or_id>:<link_alias>``. Multiple links can be passed,
ether as a comma separated list or a Python list.
Example 1: ``links=mycontainer:myalias``,
``links=web1:link1,web2:link2``
Example 2: ``links="['mycontainer:myalias']"``
``links="['web1:link1', 'web2:link2']"``
dns
List of DNS nameservers. Can be passed as a comma-separated list or a
Python list.
Example: ``dns=8.8.8.8,8.8.4.4`` or ``dns="[8.8.8.8, 8.8.4.4]"``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
dns_search
List of DNS search domains. Can be passed as a comma-separated list
or a Python list.
Example: ``dns_search=foo1.domain.tld,foo2.domain.tld`` or
``dns_search="[foo1.domain.tld, foo2.domain.tld]"``
volumes_from
Container names or IDs from which the container will get volumes. Can
be passed as a comma-separated list or a Python list.
Example: ``volumes_from=foo``, ``volumes_from=foo,bar``,
``volumes_from="[foo, bar]"``
network_mode : bridge
One of the following:
- ``bridge`` - Creates a new network stack for the container on the
docker bridge
- ``null`` - No networking (equivalent of the Docker CLI argument
``--net=none``)
- ``container:<name_or_id>`` - Reuses another container's network stack
- ``host`` - Use the host's network stack inside the container
.. warning::
Using ``host`` mode gives the container full access to the
hosts system's services (such as D-bus), and is therefore
considered insecure.
Example: ``network_mode=null``, ``network_mode=container:web1``
restart_policy
Set a restart policy for the container. Must be passed as a string in
the format ``policy[:retry_count]`` where ``policy`` is one of
``always`` or ``on-failure``, and ``retry_count`` is an optional limit
to the number of retries. The retry count is ignored when using the
``always`` restart policy.
Example 1: ``restart_policy=on-failure:5``
Example 2: ``restart_policy=always``
cap_add
List of capabilities to add within the container. Can be passed as a
comma-separated list or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_add=SYS_ADMIN,MKNOD``, ``cap_add="[SYS_ADMIN, MKNOD]"``
cap_drop
List of capabilities to drop within the container. Can be passed as a
comma-separated string or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_drop=SYS_ADMIN,MKNOD``,
``cap_drop="[SYS_ADMIN, MKNOD]"``
extra_hosts
Additional hosts to add to the container's /etc/hosts file. Can be
passed as a comma-separated list or a Python list. Requires Docker
1.3.0 or newer.
Example: ``extra_hosts=web1:10.9.8.7,web2:10.9.8.8``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
pid_mode
Set to ``host`` to use the host container's PID namespace within the
container. Requires Docker 1.5.0 or newer.
Example: ``pid_mode=host``
**RETURN DATA**
A dictionary containing the following keys:
@ -2732,7 +2874,7 @@ def create(image,
create_kwargs['hostname'] = create_kwargs['name']
if create_kwargs.pop('validate_input', False):
_validate_input('create', create_kwargs)
_validate_input(create_kwargs, validate_ip_addrs=validate_ip_addrs)
# Rename the kwargs whose names differ from their counterparts in the
# docker.client.Client class member functions. Can't use iterators here
@ -4231,174 +4373,13 @@ def signal_(name, signal):
@_refresh_mine_cache
@_ensure_exists
def start(name, validate_ip_addrs=True, **kwargs):
def start(name):
'''
Start a container
name
Container name or ID
validate_ip_addrs : True
For parameters which accept IP addresses as input, IP address
validation will be performed. To disable, set this to ``False``
binds
Files/directories to bind mount. Each bind mount should be passed in
the format ``<host_path>:<container_path>:<read_only>``, where
``<read_only>`` is one of ``rw`` (for read-write access) or ``ro`` (for
read-only access). Optionally, the read-only information can be left
off the end and the bind mount will be assumed to be read-write.
Examples 2 and 3 below are equivalent.
Example 1: ``binds=/srv/www:/var/www:ro``
Example 2: ``binds=/srv/www:/var/www:rw``
Example 3: ``binds=/srv/www:/var/www``
port_bindings
Bind exposed ports which were exposed using the ``ports`` argument to
:py:func:`dockerng.create <salt.modules.dockerng.create>`. These
should be passed in the same way as the ``--publish`` argument to the
``docker run`` CLI command:
- ``ip:hostPort:containerPort`` - Bind a specific IP and port on the
host to a specific port within the container.
- ``ip::containerPort`` - Bind a specific IP and an ephemeral port to a
specific port within the container.
- ``hostPort:containerPort`` - Bind a specific port on all of the
host's interfaces to a specific port within the container.
- ``containerPort`` - Bind an ephemeral port on all of the host's
interfaces to a specific port within the container.
Multiple bindings can be separated by commas, or passed as a Python
list. The below two examples are equivalent:
Example 1: ``port_bindings="5000:5000,2123:2123/udp,8080"``
Example 2: ``port_bindings="['5000:5000', '2123:2123/udp', '8080']"``
.. note::
When configuring bindings for UDP ports, the protocol must be
passed in the ``containerPort`` value, as seen in the examples
above.
lxc_conf
Additional LXC configuration parameters to set before starting the
container.
Example: ``lxc_conf="{lxc.utsname: docker}"``
.. note::
These LXC configuration parameters will only have the desired
effect if the container is using the LXC execution driver, which
has not been the default for some time.
publish_all_ports : False
Allocates a random host port for each port exposed using the ``ports``
argument to :py:func:`dockerng.create <salt.modules.dockerng.create>`.
Example: ``publish_all_ports=True``
links
Link this container to another. Links should be specified in the format
``<container_name_or_id>:<link_alias>``. Multiple links can be passed,
ether as a comma separated list or a Python list.
Example 1: ``links=mycontainer:myalias``,
``links=web1:link1,web2:link2``
Example 2: ``links="['mycontainer:myalias']"``
``links="['web1:link1', 'web2:link2']"``
dns
List of DNS nameservers. Can be passed as a comma-separated list or a
Python list.
Example: ``dns=8.8.8.8,8.8.4.4`` or ``dns="[8.8.8.8, 8.8.4.4]"``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
dns_search
List of DNS search domains. Can be passed as a comma-separated list
or a Python list.
Example: ``dns_search=foo1.domain.tld,foo2.domain.tld`` or
``dns_search="[foo1.domain.tld, foo2.domain.tld]"``
volumes_from
Container names or IDs from which the container will get volumes. Can
be passed as a comma-separated list or a Python list.
Example: ``volumes_from=foo``, ``volumes_from=foo,bar``,
``volumes_from="[foo, bar]"``
network_mode : bridge
One of the following:
- ``bridge`` - Creates a new network stack for the container on the
docker bridge
- ``null`` - No networking (equivalent of the Docker CLI argument
``--net=none``)
- ``container:<name_or_id>`` - Reuses another container's network stack
- ``host`` - Use the host's network stack inside the container
.. warning::
Using ``host`` mode gives the container full access to the
hosts system's services (such as D-bus), and is therefore
considered insecure.
Example: ``network_mode=null``, ``network_mode=container:web1``
restart_policy
Set a restart policy for the container. Must be passed as a string in
the format ``policy[:retry_count]`` where ``policy`` is one of
``always`` or ``on-failure``, and ``retry_count`` is an optional limit
to the number of retries. The retry count is ignored when using the
``always`` restart policy.
Example 1: ``restart_policy=on-failure:5``
Example 2: ``restart_policy=always``
cap_add
List of capabilities to add within the container. Can be passed as a
comma-separated list or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_add=SYS_ADMIN,MKNOD``, ``cap_add="[SYS_ADMIN, MKNOD]"``
cap_drop
List of capabilities to drop within the container. Can be passed as a
comma-separated string or a Python list. Requires Docker 1.2.0 or
newer.
Example: ``cap_drop=SYS_ADMIN,MKNOD``,
``cap_drop="[SYS_ADMIN, MKNOD]"``
extra_hosts
Additional hosts to add to the container's /etc/hosts file. Can be
passed as a comma-separated list or a Python list. Requires Docker
1.3.0 or newer.
Example: ``extra_hosts=web1:10.9.8.7,web2:10.9.8.8``
.. note::
To skip IP address validation, use ``validate_ip_addrs=False``
pid_mode
Set to ``host`` to use the host container's PID namespace within the
container. Requires Docker 1.5.0 or newer.
Example: ``pid_mode=host``
**RETURN DATA**
A dictionary will be returned, containing the following keys:
@ -4422,26 +4403,7 @@ def start(name, validate_ip_addrs=True, **kwargs):
'comment': ('Container \'{0}\' is paused, cannot start'
.format(name))}
runtime_kwargs = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
if runtime_kwargs.pop('validate_input', False):
_validate_input('runtime',
runtime_kwargs,
validate_ip_addrs=validate_ip_addrs)
# Rename the kwargs whose names differ from their counterparts in the
# docker.client.Client class member functions. Can't use iterators here
# because we're going to be modifying the dict.
for key in list(six.iterkeys(VALID_RUNTIME_OPTS)):
if key in runtime_kwargs:
val = VALID_RUNTIME_OPTS[key]
if 'api_name' in val:
runtime_kwargs[val['api_name']] = runtime_kwargs.pop(key)
log.debug(
'dockerng.start is using the following kwargs to start container '
'\'{0}\': {1}'.format(name, runtime_kwargs)
)
return _change_state(name, 'start', 'running', **runtime_kwargs)
return _change_state(name, 'start', 'running')
@_refresh_mine_cache

1173
salt/modules/dracr.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1394,10 +1394,8 @@ def fetch(cwd,
command.extend(
[x for x in _format_opts(opts) if x not in ('-f', '--force')]
)
if remote and not isinstance(remote, six.string_types):
remote = str(remote)
if remote:
command.append(remote)
command.append(str(remote))
if refspecs is not None:
if isinstance(refspecs, (list, tuple)):
refspec_list = []

View file

@ -159,7 +159,7 @@ def set_host(ip, alias):
if not os.path.isfile(hfn):
return False
line_to_add = ip + '\t\t' + alias + '\n'
line_to_add = ip + '\t\t' + alias + os.linesep
# support removing a host entry by providing an empty string
if not alias.strip():
line_to_add = ''
@ -180,8 +180,8 @@ def set_host(ip, alias):
lines[ind] = ''
if not ovr:
# make sure there is a newline
if lines and not lines[-1].endswith(('\n', '\r')):
lines[-1] = '{0}\n'.format(lines[-1])
if lines and not lines[-1].endswith(os.linesep):
lines[-1] += os.linesep
line = line_to_add
lines.append(line)
with salt.utils.fopen(hfn, 'w+') as ofile:
@ -221,7 +221,7 @@ def rm_host(ip, alias):
lines[ind] = ''
else:
# Only an alias was removed
lines[ind] = '{0}\n'.format(newline)
lines[ind] = newline + os.linesep
with salt.utils.fopen(hfn, 'w+') as ofile:
ofile.writelines(lines)
return True
@ -277,4 +277,4 @@ def _write_hosts(hosts):
if line.strip():
# /etc/hosts needs to end with EOL so that some utils that read
# it do not break
ofile.write('{0}\n'.format(line.strip()))
ofile.write(line.strip() + os.linesep)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
Support for htpasswd command. Requires apache2-utils package.
Support for htpasswd command. Requires the apache2-utils package for Debian-based distros.
.. versionadded:: 2014.1.0

View file

@ -32,9 +32,9 @@ def __virtual__():
Only work on POSIX-like systems
'''
if HAS_DBUS is False and _uses_dbus():
return False
return (False, 'Cannot load locale module: dbus python module unavailable')
if salt.utils.is_windows():
return False
return (False, 'Cannot load locale module: windows platforms are unsupported')
return __virtualname__

View file

@ -41,8 +41,9 @@ def _parse_conf(conf_file=default_conf):
'''
ret = {}
mode = 'single'
multi_name = ''
multi_names = []
multi = {}
prev_comps = None
with salt.utils.fopen(conf_file, 'r') as ifile:
for line in ifile:
line = line.strip()
@ -54,12 +55,17 @@ def _parse_conf(conf_file=default_conf):
comps = line.split()
if '{' in line and '}' not in line:
mode = 'multi'
multi_name = comps[0]
if len(comps) == 1 and prev_comps:
multi_names = prev_comps
else:
multi_names = comps
multi_names.pop()
continue
if '}' in line:
mode = 'single'
ret[multi_name] = multi
multi_name = ''
for multi_name in multi_names:
ret[multi_name] = multi
multi_names = []
multi = {}
continue
@ -80,6 +86,7 @@ def _parse_conf(conf_file=default_conf):
ret[file_key] = include_conf[file_key]
ret['include files'][include].append(file_key)
prev_comps = comps
if len(comps) > 1:
key[comps[0]] = ' '.join(comps[1:])
else:

View file

@ -429,10 +429,27 @@ def info(*packages):
salt '*' lowpkg.info apache2 bash
'''
cmd = packages and "rpm -qi {0}".format(' '.join(packages)) or "rpm -qai"
cmd = packages and "rpm -q {0}".format(' '.join(packages)) or "rpm -qa"
# Locale needs to be en_US instead of C, because RPM otherwise will yank the timezone from the timestamps
call = __salt__['cmd.run_all'](cmd + " --queryformat '-----\n'",
call = __salt__['cmd.run_all'](cmd + (" --queryformat 'Name: %{NAME}\n"
"Relocations: %|PREFIXES?{[%{PREFIXES} ]}:{(not relocatable)}|\n"
"Version: %{VERSION}\n"
"Vendor: %{VENDOR}\n"
"Release: %{RELEASE}\n"
"Build Date: %{BUILDTIME:date}\n"
"Install Date: %|INSTALLTIME?{%{INSTALLTIME:date}}:{(not installed)}|\n"
"Build Host: %{BUILDHOST}\n"
"Group: %{GROUP}\n"
"Source RPM: %{SOURCERPM}\n"
"Size: %{LONGSIZE}\n"
"%|LICENSE?{License: %{LICENSE}\n}|"
"Signature: %|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|\n"
"%|PACKAGER?{Packager: %{PACKAGER}\n}|"
"%|URL?{URL: %{URL}\n}|"
"Summary: %{SUMMARY}\n"
"Description:\n%{DESCRIPTION}\n"
"-----\n'"),
output_loglevel='trace', env={'LC_ALL': 'en_US', 'TZ': 'UTC'}, clean_env=True)
if call['retcode'] != 0:
comment = ''

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
'''
Service support for the REST example
'''
from __future__ import absolute_import
# Import python libs
import logging
# Import Salt's libs
import salt.utils
log = logging.getLogger(__name__)
__proxyenabled__ = ['ssh_sample']
# Define the module's virtual name
__virtualname__ = 'pkg'
def __virtual__():
'''
Only work on proxy
'''
if salt.utils.is_proxy():
return __virtualname__
return False
def list_pkgs(versions_as_list=False, **kwargs):
return __proxy__['ssh_sample.package_list']()
def install(name=None, refresh=False, fromrepo=None,
pkgs=None, sources=None, **kwargs):
return __proxy__['ssh_sample.package_install'](name, **kwargs)
def remove(name=None, pkgs=None, **kwargs):
return __proxy__['ssh_sample.package_remove'](name)

View file

@ -75,11 +75,7 @@ def get_path():
'''
ret = __salt__['reg.read_value']('HKEY_LOCAL_MACHINE',
'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
'PATH')
if isinstance(ret, dict):
ret = ret['vdata'].split(';')
if isinstance(ret, str):
ret = ret.split(';')
'PATH')['vdata'].split(';')
# Trim ending backslash
return list(map(_normalize_dir, ret))

View file

@ -726,37 +726,61 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
cached_pkg = cached_pkg.replace('/', '\\')
cache_path, _ = os.path.split(cached_pkg)
# Get settings for msiexec and allusers
msiexec = pkginfo[version_num].get('msiexec')
all_users = pkginfo[version_num].get('allusers')
# all_users defaults to True
if all_users is None:
all_users = True
# Get install flags
install_flags = '{0}'.format(pkginfo[version_num].get('install_flags'))
if options and options.get('extra_install_flags'):
install_flags = '{0} {1}'.format(install_flags,
options.get('extra_install_flags', ''))
# Build the install command
cmd = []
if msiexec:
cmd.extend(['msiexec', '/i'])
cmd.append(cached_pkg)
cmd.extend(shlex.split(install_flags))
if msiexec and all_users:
cmd.append('ALLUSERS="1"')
# Install the software
result = __salt__['cmd.run_stdout'](cmd, cache_path, output_loglevel='trace', python_shell=False)
if result:
log.error('Failed to install {0}'.format(pkg_name))
log.error('error message: {0}'.format(result))
ret[pkg_name] = {'failed': result}
# Check Use Scheduler Option
if pkginfo[version_num].get('use_scheduler', False):
# Build Scheduled Task Parameters
if pkginfo[version_num].get('msiexec'):
cmd = 'msiexec.exe'
arguments = ['/i', cached_pkg]
if pkginfo['version_num'].get('allusers', True):
arguments.append('ALLUSERS="1"')
arguments.extend(shlex.split(install_flags))
else:
cmd = cached_pkg
arguments = shlex.split(install_flags)
# Create Scheduled Task
__salt__['task.create_task'](name='update-salt-software',
user_name='System',
force=True,
action_type='Execute',
cmd=cmd,
arguments=' '.join(arguments),
start_in=cache_path,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00')
# Run Scheduled Task
__salt__['task.run_wait'](name='update-salt-software')
else:
changed.append(pkg_name)
# Build the install command
cmd = []
if pkginfo[version_num].get('msiexec'):
cmd.extend(['msiexec', '/i', cached_pkg])
if pkginfo[version_num].get('allusers', True):
cmd.append('ALLUSERS="1"')
else:
cmd.append(cached_pkg)
cmd.extend(shlex.split(install_flags))
# Launch the command
result = __salt__['cmd.run_stdout'](cmd,
cache_path,
output_loglevel='trace',
python_shell=False)
if result:
log.error('Failed to install {0}'.format(pkg_name))
log.error('error message: {0}'.format(result))
ret[pkg_name] = {'failed': result}
else:
changed.append(pkg_name)
# Get a new list of installed software
new = list_pkgs()
@ -920,33 +944,61 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
# Fix non-windows slashes
cached_pkg = cached_pkg.replace('/', '\\')
cache_path, _ = os.path.split(cached_pkg)
# Get parameters for cmd
expanded_cached_pkg = str(os.path.expandvars(cached_pkg))
uninstall_flags = ''
if pkginfo[version_num].get('uninstall_flags'):
uninstall_flags = '{0}'.format(pkginfo[version_num].get('uninstall_flags'))
# Get uninstall flags
uninstall_flags = '{0}'.format(pkginfo[version_num].get('uninstall_flags', ''))
if kwargs.get('extra_uninstall_flags'):
uninstall_flags = '{0} {1}'.format(uninstall_flags,
kwargs.get('extra_uninstall_flags', ""))
# Build the install command
cmd = []
if pkginfo[version_num].get('msiexec'):
cmd.extend(['msiexec', '/x'])
cmd.append(expanded_cached_pkg)
cmd.extend(shlex.split(uninstall_flags))
# Uninstall the software
result = __salt__['cmd.run_stdout'](cmd, output_loglevel='trace', python_shell=False)
if result:
log.error('Failed to install {0}'.format(target))
log.error('error message: {0}'.format(result))
ret[target] = {'failed': result}
# Check Use Scheduler Option
if pkginfo[version_num].get('use_scheduler', False):
# Build Scheduled Task Parameters
if pkginfo[version_num].get('msiexec'):
cmd = 'msiexec.exe'
arguments = ['/x']
arguments.extend(shlex.split(uninstall_flags))
else:
cmd = expanded_cached_pkg
arguments = shlex.split(uninstall_flags)
# Create Scheduled Task
__salt__['task.create_task'](name='update-salt-software',
user_name='System',
force=True,
action_type='Execute',
cmd=cmd,
arguments=' '.join(arguments),
start_in=cache_path,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00')
# Run Scheduled Task
__salt__['task.run_wait'](name='update-salt-software')
else:
changed.append(target)
# Build the install command
cmd = []
if pkginfo[version_num].get('msiexec'):
cmd.extend(['msiexec', '/x', expanded_cached_pkg])
else:
cmd.append(expanded_cached_pkg)
cmd.extend(shlex.split(uninstall_flags))
# Launch the command
result = __salt__['cmd.run_stdout'](cmd,
output_loglevel='trace',
python_shell=False)
if result:
log.error('Failed to install {0}'.format(target))
log.error('error message: {0}'.format(result))
ret[target] = {'failed': result}
else:
changed.append(target)
# Get a new list of installed software
new = list_pkgs()

View file

@ -228,11 +228,14 @@ def create_win_salt_restart_task():
salt '*' service.create_win_salt_restart_task()
'''
cmd = 'cmd /c ping -n 3 127.0.0.1 && net stop salt-minion && net start salt-minion'
cmd = 'cmd'
args = '/c ping -n 3 127.0.0.1 && net stop salt-minion && net start salt-minion'
return __salt__['task.create_task'](name='restart-salt-minion',
user_name='System',
force=True,
action_type='Execute',
cmd=cmd,
arguments=args,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00')

View file

@ -15,10 +15,15 @@ from __future__ import absolute_import
import salt.utils
from datetime import datetime
import logging
import time
# Import 3rd Party Libraries
import pythoncom
import win32com.client
try:
import pythoncom
import win32com.client
HAS_DEPENDENCIES = True
except ImportError:
HAS_DEPENDENCIES = False
from salt.ext.six.moves import range
log = logging.getLogger(__name__)
@ -67,7 +72,14 @@ TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD = 6
TASK_RUNLEVEL_LUA = 0
TASK_RUNLEVEL_HIGHEST = 1
# TASK_TRIGGER_TYPE2
# TASK_STATE_TYPE
TASK_STATE_UNKNOWN = 0
TASK_STATE_DISABLED = 1
TASK_STATE_QUEUED = 2
TASK_STATE_READY = 3
TASK_STATE_RUNNING = 4
# TASK_TRIGGER_TYPE
TASK_TRIGGER_EVENT = 0
TASK_TRIGGER_TIME = 1
TASK_TRIGGER_DAILY = 2
@ -105,12 +117,41 @@ action_types = {'Execute': TASK_ACTION_EXEC,
'Email': TASK_ACTION_SEND_EMAIL,
'Message': TASK_ACTION_SHOW_MESSAGE}
states = {TASK_STATE_UNKNOWN: 'Unknown',
TASK_STATE_DISABLED: 'Disabled',
TASK_STATE_QUEUED: 'Queued',
TASK_STATE_READY: 'Ready',
TASK_STATE_RUNNING: 'Running'}
instances = {'Parallel': TASK_INSTANCES_PARALLEL,
'Queue': TASK_INSTANCES_QUEUE,
'No New Instance': TASK_INSTANCES_IGNORE_NEW,
'Stop Existing': TASK_INSTANCES_STOP_EXISTING}
results = {0x0: 'The operation completed successfully',
0x1: 'Incorrect or unknown function called',
0x2: 'File not found',
0xA: 'The environment is incorrect',
0x41300: 'Task is ready to run at its next scheduled time',
0x41301: 'Task is currently running',
0x41302: 'Task is disabled',
0x41303: 'Task has not yet run',
0x41304: 'There are no more runs scheduled for this task',
0x41306: 'Task was terminated by the user',
0x8004130F: 'Credentials became corrupted',
0x8004131F: 'An instance of this task is already running',
0x800704DD: 'The service is not available (Run only when logged in?)',
0xC000013A: 'The application terminated as a result of CTRL+C',
0xC06D007E: 'Unknown software exception'}
def __virtual__():
'''
Only works on Windows systems
'''
if salt.utils.is_windows():
if not HAS_DEPENDENCIES:
log.warn('Could not load dependencies for {0}'.format(__virtualname__))
return __virtualname__
return False
@ -148,6 +189,37 @@ def _get_date_time_format(dt_string):
return False
def _get_date_value(date):
'''
Function for dealing with PyTime values with invalid dates. ie: 12/30/1899
which is the windows task scheduler value for Never
:param obj date: A PyTime object
:return: A string value representing the date or the word "Never" for
invalid date strings
:rtype: str
'''
try:
return '{0}'.format(date)
except ValueError:
return 'Never'
def _reverse_lookup(dictionary, value):
'''
Lookup the key in a dictionary by it's value. Will return the first match.
:param dict dictionary: The dictionary to search
:param str value: The value to search for.
:return: Returns the first key to match the value
:rtype: str
'''
return dictionary.keys()[dictionary.values().index(value)]
def _save_task_definition(name,
task_folder,
task_definition,
@ -318,6 +390,7 @@ def create_task(name,
location='\\',
user_name='System',
password=None,
force=False,
**kwargs):
r'''
Create a new task in the designated location. This function has many keyword
@ -341,11 +414,13 @@ def create_task(name,
the task to run whether the user is logged in or not, but is currently not
working.
:param bool force: If the task exists, overwrite the existing task.
:return: True if successful, False if unsuccessful
:rtype: bool
'''
# Check for existing task
if name in list_tasks(location):
if name in list_tasks(location) and not force:
# Connect to an existing task definition
return '{0} already exists'.format(name)
@ -687,12 +762,6 @@ def edit_task(name=None,
'''
# TODO: Add more detailed return for items changed
# Define Lookup Dictionaries
instances = {'Parallel': TASK_INSTANCES_PARALLEL,
'Queue': TASK_INSTANCES_QUEUE,
'No New Instance': TASK_INSTANCES_IGNORE_NEW,
'Stop Existing': TASK_INSTANCES_STOP_EXISTING}
# Check for passed task_definition
# If not passed, open a task definition for an existing task
save_definition = False
@ -953,6 +1022,212 @@ def run(name, location='\\'):
return False
def run_wait(name, location='\\'):
r'''
Run a scheduled task and return when the task finishes
:param str name: The name of the task to run.
:param str location: A string value representing the location of the task.
Default is '\\' which is the root for the task scheduler
(C:\Windows\System32\tasks).
:return: True if successful, False if unsuccessful
:rtype: bool
'''
# Check for existing folder
if name not in list_tasks(location):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
task_folder = task_service.GetFolder(location)
task = task_folder.GetTask(name)
# Is the task already running
if task.State == TASK_STATE_RUNNING:
return 'Task already running'
try:
task.Run('')
time.sleep(1)
running = True
except pythoncom.com_error:
return False
while running:
running = False
try:
running_tasks = task_service.GetRunningTasks(0)
if running_tasks.Count:
for item in running_tasks:
if item.Name == name:
running = True
except pythoncom.com_error:
running = False
return True
def stop(name, location='\\'):
r'''
Stop a scheduled task.
:param str name: The name of the task to stop.
:param str location: A string value representing the location of the task.
Default is '\\' which is the root for the task scheduler
(C:\Windows\System32\tasks).
:return: True if successful, False if unsuccessful
:rtype: bool
'''
# Check for existing folder
if name not in list_tasks(location):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
task_folder = task_service.GetFolder(location)
task = task_folder.GetTask(name)
try:
task.Stop(0)
return True
except pythoncom.com_error as error:
return False
def status(name, location='\\'):
r'''
Determine the status of a task. Is it Running, Queued, Ready, etc.
:param str name: The name of the task for which to return the status
:param str location: A string value representing the location of the task.
Default is '\\' which is the root for the task scheduler
(C:\Windows\System32\tasks).
:return: The current status of the task. Will be one of the following:
- Unknown
- Disabled
- Queued
- Ready
- Running
:rtype: string
'''
# Check for existing folder
if name not in list_tasks(location):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
task_folder = task_service.GetFolder(location)
task = task_folder.GetTask(name)
return states[task.State]
def info(name, location='\\'):
r'''
Get the details about a task in the task scheduler.
:param str name: The name of the task for which to return the status
:param str location: A string value representing the location of the task.
Default is '\\' which is the root for the task scheduler
(C:\Windows\System32\tasks).
:return:
:rtype: dict
'''
# Check for existing folder
if name not in list_tasks(location):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
task_folder = task_service.GetFolder(location)
task = task_folder.GetTask(name)
properties = {'enabled': task.Enabled,
'last_run': _get_date_value(task.LastRunTime),
'last_run_result': results[task.LastTaskResult],
'missed_runs': task.NumberOfMissedRuns,
'next_run': _get_date_value(task.NextRunTime),
'status': states[task.State]}
def_set = task.Definition.Settings
settings = {}
settings['allow_demand_start'] = def_set.AllowDemandStart
settings['force_stop'] = def_set.AllowHardTerminate
if def_set.DeleteExpiredTaskAfter == '':
settings['delete_after'] = False
elif def_set.DeleteExpiredTaskAfter == 'PT0S':
settings['delete_after'] = 'Immediately'
else:
settings['delete_after'] = _reverse_lookup(duration, def_set.DeleteExpiredTaskAfter)
if def_set.ExecutionTimeLimit == '':
settings['execution_time_limit'] = False
else:
settings['execution_time_limit'] = _reverse_lookup(duration, def_set.ExecutionTimeLimit)
settings['multiple_instances'] = _reverse_lookup(instances, def_set.MultipleInstances)
if def_set.RestartInterval == '':
settings['restart_interval'] = False
else:
settings['restart_interval'] = _reverse_lookup(duration, def_set.RestartInterval)
if settings['restart_interval']:
settings['restart_count'] = def_set.RestartCount
settings['stop_if_on_batteries'] = def_set.StopIfGoingOnBatteries
settings['wake_to_run'] = def_set.WakeToRun
conditions = {}
conditions['ac_only'] = def_set.DisallowStartIfOnBatteries
conditions['run_if_idle'] = def_set.RunOnlyIfIdle
conditions['run_if_network'] = def_set.RunOnlyIfNetworkAvailable
conditions['start_when_available'] = def_set.StartWhenAvailable
if conditions['run_if_idle']:
idle_set = def_set.IdleSettings
conditions['idle_duration'] = idle_set.IdleDuration
conditions['idle_restart'] = idle_set.RestartOnIdle
conditions['idle_stop_on_end'] = idle_set.StopOnIdleEnd
conditions['idle_wait_timeout'] = idle_set.WaitTimeout
if conditions['run_if_network']:
net_set = def_set.NetworkSettings
conditions['network_id'] = net_set.Id
conditions['network_name'] = net_set.Name
properties['settings'] = settings
properties['conditions'] = conditions
ret = properties
return ret
def add_action(name=None,
location='\\',
action_type='Execute',
@ -980,13 +1255,21 @@ def add_action(name=None,
*Execute*
Execute a command or an executable.
:param str cmd: (required) The command/executable to run along with any
required arguments. For launching a script the first command will need to be
the interpreter for the script. For example, to run a vbscript you would
first call `cscript.exe` and pass the script as an argument as follows:
- ``cscript.exe c:\scripts\myscript.vbs``
:param str cmd: (required) The command / executable to run.
:param str start_in: The current working directory for the command.
:param str arguments: (optional) Arguments to be passed to the command /
executable. To launch a script the first command will need to be the
interpreter for the script. For example, to run a vbscript you would
pass `cscript.exe` in the `cmd` parameter and pass the script in the
`arguments` parameter as follows:
- ``cmd='cscript.exe' arguments='c:\scripts\myscript.vbs'``
Batch files do not need an interpreter and may be passed to the cmd
parameter directly.
:param str start_in: (optional) The current working directory for the
command.
*Email*
Send and email. Requires ``server``, ``from``, and ``to`` or ``cc``.
@ -1045,11 +1328,10 @@ def add_action(name=None,
if action_types[action_type] == TASK_ACTION_EXEC:
task_action.Id = 'Execute_ID1'
if kwargs.get('cmd', False):
cmd = kwargs.get('cmd').split()
task_action.Path = cmd[0]
task_action.Arguments = u' '.join(cmd[1:])
task_action.Path = kwargs.get('cmd')
else:
return 'Required parameter "cmd" not found'
task_action.Arguments = kwargs.get('arguments', '')
task_action.WorkingDirectory = kwargs.get('start_in', '')
elif action_types[action_type] == TASK_ACTION_SEND_EMAIL:

View file

@ -675,10 +675,11 @@ def _get_userprofile_from_registry(user, sid):
In case net user doesn't return the userprofile
we can get it from the registry
'''
profile_dir = __salt__['reg.read_key'](
'HKEY_LOCAL_MACHINE', u'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{0}'.format(sid),
profile_dir = __salt__['reg.read_value'](
'HKEY_LOCAL_MACHINE',
u'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{0}'.format(sid),
'ProfileImagePath'
)
)['vdata']
log.debug(u'user {0} with sid={2} profile is located at "{1}"'.format(user, profile_dir, sid))
return profile_dir

View file

@ -251,7 +251,7 @@ def ext_pillar(minion_id, repo, pillar_dirs):
'smart'
)
for pillar_dir, env in six.iteritems(pillar.pillar_dirs):
opts['pillar_roots'] = {env: [pillar_dir]}
opts['pillar_roots'] = {env: [d for (d, e) in six.iteritems(pillar.pillar_dirs) if env == e]}
local_pillar = Pillar(opts, __grains__, minion_id, env)
ret = salt.utils.dictupdate.merge(
ret,

252
salt/proxy/fx2.py Normal file
View file

@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
'''
======
fx2.py
======
.. versionadded:: 2015.8.2
Proxy minion interface module for managing Dell FX2 chassis (Dell
Chassis Management Controller version 1.2 and above, iDRAC8 version 2.00
and above)
Dependencies
------------
- :doc:`iDRAC Remote execution module (salt.modules.dracr) </ref/modules/all/salt.modules.dracr>`
- :doc:`Chassis command shim (salt.modules.chassis) </ref/modules/all/salt.modules.chassis>`
- :doc:`Dell Chassis States (salt.states.dellchassis) </ref/states/all/salt.states.dellchassis>`
- Dell's ``racadm`` command line interface to CMC and iDRAC devices.
**Special Note: SaltStack thanks** `Adobe Corporation <http://adobe.com/>`_
**for their support in creating this proxy minion integration.**
This proxy minion enables Dell FX2 and FX2s (hereafter referred to as
simply "chassis", "CMC", or "FX2") chassis to be treated individually
like a salt-minion.
Since the CMC embedded in the chassis does not run an OS capable of hosting a
Python stack, the chassis can't run a minion directly. Salt's "Proxy Minion"
functionality enables you to designate another machine to host a minion
process that "proxies" communication from the salt-master. The master does not
know nor care that the target is not a real minion.
More in-depth conceptual reading on Proxy Minions can be found
:doc:`in the Proxy Minion section </topics/proxyminion/index>` of
Salt's documentation.
To configure this integration, follow these steps:
Pillar
------
Proxy minions get their configuration from Salt's Pillar. Every proxy must
have a stanza in Pillar, and a reference in the Pillar topfile that matches
the ID. At a minimum for communication with the chassis the pillar should
look like this:
.. code-block:: yaml
proxy:
host: <ip or dns name of chassis controller>
admin_username: <iDRAC username for the CMC, usually 'root'>
admin_password: <iDRAC password. Dell default is 'calvin'>
proxytype: fx2
The ``proxytype`` line above is critical, it tells Salt which interface to load
from the ``proxy`` directory in Salt's install hierarchy, or from ``/srv/salt/_proxy``
on the salt-master (if you have created your own proxy module, for example).
salt-proxy
----------
After your pillar is in place, you can test the proxy. The proxy can run on
any machine that has network connectivity to your salt-master and to the chassis in question.
SaltStack recommends that this machine also run a regular minion, though
it is not strictly necessary.
On the machine that will run the proxy, make sure there is an ``/etc/salt/proxy``
file with at least the following in it:
.. code-block:: yaml
master: <ip or hostname of salt-master>
You can start the proxy with
.. code-block:: bash
salt-proxy --proxyid <id you want to give the chassis>
You may want to add ``-l debug`` to run the above in the foreground in debug
mode just to make sure everything is OK.
Next, accept the key for the proxy on your salt-master, just like you would
for a regular minion:
.. code-block:: bash
salt-key -a <id you want to give the chassis>
You can confirm that the pillar data is in place for the proxy:
.. code-block:: bash
salt <id> pillar.items
And now you should be able to ping the chassis to make sure it is responding:
.. code-block:: bash
salt <id> test.ping
At this point you can execute one-off commands against the chassis. For
example, you can get the chassis inventory:
.. code-block:: bash
salt <id> chassis.cmd inventory
Note that you don't need to provide credentials or an ip/hostname. Salt knows
to use the credentials you stored in Pillar.
It's important to understand how this particular proxy works.
:doc:`Salt.modules.dracr </ref/modules/all/salt.modules.dracr>` is a standard Salt execution
module. If you pull up the docs for it you'll see that almost every function
in the module takes credentials and a target host. When credentials and a host
aren't passed, Salt runs ``racadm`` against the local machine. If you wanted
you could run functions from this module on any host where an appropriate
version of ``racadm`` is installed, and that host would reach out over the network
and communicate with the chassis.
``Chassis.cmd`` acts as a "shim" between the execution module and the proxy. It's
first parameter is always the function from salt.modules.dracr to execute. If the
function takes more positional or keyword arguments you can append them to the call.
It's this shim that speaks to the chassis through the proxy, arranging for the
credentials and hostname to be pulled from the pillar section for this proxy minion.
Because of the presence of the shim, to lookup documentation for what
functions you can use to interface with the chassis, you'll want to
look in :doc:`salt.modules.dracr </ref/modules/all/salt.modules.dracr>` instead
of :doc:`salt.modules.chassis </ref/modules/all/salt.modules.chassis>`.
States
------
Associated states are thoroughly documented in :doc:`salt.states.dellchassis </ref/states/all/salt.states.dellchassis>`.
Look there to find an example structure for pillar as well as an example
``.sls`` file for standing up a Dell Chassis from scratch.
'''
from __future__ import absolute_import
# Import python libs
import logging
import salt.utils
import salt.utils.http
# This must be present or the Salt loader won't load this module
__proxyenabled__ = ['fx2']
# Variables are scoped to this module so we can have persistent data
# across calls to fns in here.
GRAINS_CACHE = {}
DETAILS = {}
# Want logging!
log = logging.getLogger(__file__)
def __virtual__():
'''
Only return if all the modules are available
'''
if not salt.utils.which('racadm'):
log.critical('fx2 proxy minion needs "racadm" to be installed.')
return False
return True
def init(opts):
'''
This function gets called when the proxy starts up. For
FX2 devices we just cache the credentials and hostname.
'''
# Save the login details
DETAILS['admin_username'] = opts['proxy']['admin_username']
DETAILS['admin_password'] = opts['proxy']['admin_password']
DETAILS['host'] = opts['proxy']['host']
def grains():
'''
Get the grains from the proxied device
'''
if not GRAINS_CACHE:
r = __salt__['dracr.system_info'](host=DETAILS['host'],
admin_username=DETAILS['admin_username'],
admin_password=DETAILS['admin_password'])
GRAINS_CACHE = r
return GRAINS_CACHE
def grains_refresh():
'''
Refresh the grains from the proxied device
'''
GRAINS_CACHE = {}
return grains()
def chconfig(cmd, *args, **kwargs):
'''
This function is called by the :doc:`salt.modules.chassis.cmd </ref/modules/all/salt.modules.chassis>`
shim. It then calls whatever is passed in ``cmd``
inside the :doc:`salt.modules.dracr </ref/modules/all/salt.modules.dracr>`
module.
:param cmd: The command to call inside salt.modules.dracr
:param args: Arguments that need to be passed to that command
:param kwargs: Keyword arguments that need to be passed to that command
:return: Passthrough the return from the dracr module.
'''
# Strip the __pub_ keys...is there a better way to do this?
for k in kwargs.keys():
if k.startswith('__pub_'):
kwargs.pop(k)
if 'dracr.'+cmd not in __salt__:
return {'retcode': -1, 'message': 'dracr.' + cmd + ' is not available'}
else:
return __salt__['dracr.'+cmd](*args, **kwargs)
def ping():
'''
Is the chassis responding?
:return: Returns False if the chassis didn't respond, True otherwise.
'''
r = __salt__['dracr.system_info'](host=DETAILS['host'],
admin_username=DETAILS['admin_username'],
admin_password=DETAILS['admin_password'])
if r.get('retcode', 0) == 1:
return False
else:
return True
try:
return r['dict'].get('ret', False)
except Exception:
return False
def shutdown(opts):
'''
Shutdown the connection to the proxied device.
For this proxy shutdown is a no-op.
'''
log.debug('fx2 proxy shutdown() called...')

124
salt/proxy/ssh_sample.py Normal file
View file

@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
'''
This is a simple proxy-minion designed to connect to and communicate with
a server that exposes functionality via SSH.
This can be used as an option when the device does not provide
an api over HTTP and doesn't have the python stack to run a minion.
'''
from __future__ import absolute_import
# Import python libs
import json
import logging
# Import Salt's libs
from salt.utils.vt_helper import SSHConnection
from salt.utils.vt import TerminalException
# This must be present or the Salt loader won't load this module
__proxyenabled__ = ['ssh_sample']
DETAILS = {}
# Want logging!
log = logging.getLogger(__file__)
# This does nothing, it's here just as an example and to provide a log
# entry when the module is loaded.
def __virtual__():
'''
Only return if all the modules are available
'''
log.info('ssh_sample proxy __virtual__() called...')
return True
def init(opts):
'''
Required.
Can be used to initialize the server connection.
'''
try:
DETAILS['server'] = SSHConnection(host=__opts__['proxy']['host'],
username=__opts__['proxy']['username'],
password=__opts__['proxy']['password'])
out, err = DETAILS['server'].sendline('help')
except TerminalException as e:
log.error(e)
return False
def shutdown(opts):
'''
Disconnect
'''
DETAILS['server'].close_connection()
def parse(out):
'''
Extract json from out.
Parameter
out: Type string. The data returned by the
ssh command.
'''
jsonret = []
in_json = False
for ln_ in out.split('\n'):
if '{' in ln_:
in_json = True
if in_json:
jsonret.append(ln_)
if '}' in ln_:
in_json = False
return json.loads('\n'.join(jsonret))
def package_list():
'''
List "packages" by executing a command via ssh
This function is called in response to the salt command
..code-block::bash
salt target_minion pkg.list_pkgs
'''
# Send the command to execute
out, err = DETAILS['server'].sendline('pkg_list')
# "scrape" the output and return the right fields as a dict
return parse(out)
def package_install(name, **kwargs):
'''
Install a "package" on the REST server
'''
cmd = 'pkg_install ' + name
if 'version' in kwargs:
cmd += '/'+kwargs['version']
else:
cmd += '/1.0'
# Send the command to execute
out, err = DETAILS['server'].sendline(cmd)
# "scrape" the output and return the right fields as a dict
return parse(out)
def package_remove(name):
'''
Remove a "package" on the REST server
'''
cmd = 'pkg_remove ' + name
# Send the command to execute
out, err = DETAILS['server'].sendline(cmd)
# "scrape" the output and return the right fields as a dict
return parse(out)

View file

@ -100,15 +100,19 @@ def extracted(name,
archive_user
The user to own each extracted file.
.. deprecated:: 2014.7.2
.. deprecated:: Boron
replaced by standardized `user` parameter.
user
The user to own each extracted file.
.. versionadded:: 2015.8.0
group
The group to own each extracted file.
.. versionadded:: 2015.8.0
if_missing
Some archives, such as tar, extract themselves in a subfolder.
This directive can be used to validate if the archive had been

325
salt/states/dellchassis.py Normal file
View file

@ -0,0 +1,325 @@
# -*- coding: utf-8 -*-
'''
Manage chassis via Salt Proxies.
.. versionadded:: 2015.8.2
Example managing a Dell chassis:
.. code-block:: yaml
my-dell-chassis:
chassis.dell:
- name: my-dell-chassis
- location: my-location
- mode: 2
- idrac_launch: 1
- slot_names:
- 1: my-slot-name
- 2: my-other-slot-name
- blade_power_states:
- server-1: on
- server-2: off
- server-3: powercycle
'''
# Import python libs
from __future__ import absolute_import
import logging
log = logging.getLogger(__name__)
def __virtual__():
return 'chassis.cmd' in __salt__
def blade_idrac(name, idrac_password=None, idrac_ipmi=None,
idrac_ip=None, idrac_netmask=None, idrac_gateway=None,
drac_dhcp=None):
'''
Set parameters for iDRAC in a blade.
:param name: The name of the blade to address
:param idrac_password: Password to establish for the iDRAC interface
:param idrac_ipmi: Enable/Disable IPMI over LAN
:param idrac_ip: Set IP address for iDRAC
:param idrac_netmask: Set netmask for iDRAC
:param idrac_gateway: Set gateway for iDRAC
:param drac_dhcp: Turn on DHCP for iDRAC (True turns on, False does nothing
becaause setting a static IP will disable DHCP).
:return: A standard Salt changes dictionary
'''
pass
def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None,
blade_power_states=None):
'''
Manage a Dell Chassis.
name
The name of the chassis.
location
The location of the chassis.
mode
The management mode of the chassis. Viable options are:
- 0: None
- 1: Monitor
- 2: Manage and Monitor
idrac_launch
The iDRAC launch method of the chassis. Viable options are:
- 0: Disabled (launch iDRAC using IP address)
- 1: Enabled (launch iDRAC using DNS name)
slot_names
The names of the slots, provided as a list identified by
their slot numbers.
blade_power_states
The power states of a blade server, provided as a list and
identified by their server numbers. Viable options are:
- on: Ensure the blade server is powered on.
- off: Ensure the blade server is powered off.
- powercycle: Power cycle the blade server.
Example:
.. code-block:: yaml
my-dell-chassis:
chassis.dell:
- name: my-dell-chassis
- location: my-location
- mode: 2
- idrac_launch: 1
- slot_names:
- 1: my-slot-name
- 2: my-other-slot-name
- blade_power_states:
- server-1: on
- server-2: off
- server-3: powercycle
'''
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
chassis_cmd = 'chassis.cmd'
cfg_tuning = 'cfgRacTuning'
mode_cmd = 'cfgRacTuneChassisMgmtAtServer'
launch_cmd = 'cfgRacTuneIdracDNSLaunchEnable'
current_name = __salt__[chassis_cmd]('get_chassis_name')
if name != current_name:
ret['changes'].update({'Name':
{'Old': current_name,
'New': name}})
if location:
current_location = __salt__[chassis_cmd]('get_chassis_location')
if location != current_location:
ret['changes'].update({'Location':
{'Old': current_location,
'New': location}})
if mode:
current_mode = __salt__[chassis_cmd]('get_general', cfg_tuning, mode_cmd)
if mode != current_mode:
ret['changes'].update({'Management Mode':
{'Old': current_mode,
'New': mode}})
if idrac_launch:
current_launch_method = __salt__[chassis_cmd]('get_general', cfg_tuning, launch_cmd)
if idrac_launch != current_launch_method:
ret['changes'].update({'iDrac Launch Method':
{'Old': current_launch_method,
'New': idrac_launch}})
if slot_names:
current_slot_names = __salt__[chassis_cmd]('list_slotnames')
for key, val in slot_names:
current_slot_name = current_slot_names.get(key).get('slotname')
if current_slot_name != val['name']:
old = {key: current_slot_name}
new = {key: val}
if ret['changes'].get('Slot Names') is None:
ret['changes'].update({'Slot Names':
{'Old': {},
'New': {}}})
ret['changes']['Slot Names']['Old'].update(old)
ret['changes']['Slot Names']['New'].update(new)
# TODO: Refactor this and make DRY - can probable farm this out to a new funciton
if blade_power_states:
# TODO: Get the power state list working
current_power_states = 'get a list of current power states'
for key, val in blade_power_states:
# TODO: Get the correct state infos
current_power_state = current_power_states.get(key).get('state')
# TODO: Don't just compare values, check if True should be "on" or "off" etc
if current_power_state != val:
old = {key: current_power_state}
new = {key: val}
if ret['changes'].get('Blade Power States') is None:
ret['changes'].update({'Blade Power States':
{'Old': {},
'New': {}}})
ret['changes']['Blade Power States']['Old'].update(old)
ret['changes']['Blade Power States']['New'].update(new)
if ret['changes'] == {}:
ret['comment'] = 'Dell chassis is already in the desired state.'
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Dell chassis configuration will change.'
return ret
# Finally, set the necessary configurations on the chassis.
name = __salt__[chassis_cmd]('set_chassis_name', name)
if location:
location = __salt__[chassis_cmd]('set_chassis_location', location)
if mode:
mode = __salt__[chassis_cmd]('set_general', cfg_tuning, mode_cmd, mode)
if idrac_launch:
idrac_launch = __salt__[chassis_cmd]('set_general', cfg_tuning, launch_cmd, idrac_launch)
if slot_names:
slot_rets = []
for key, val in slot_names.iteritems():
slot_name = val.get('slotname')
slot_rets.append(__salt__[chassis_cmd]('set_slotname', key, slot_name))
if any(slot_rets) is False:
slot_names = False
else:
slot_names = True
if any([name, location, mode, idrac_launch, slot_names]) is False:
ret['result'] = False
ret['comment'] = 'There was an error setting the Dell chassis.'
ret['comment'] = 'Dell chassis was updated.'
return ret
def dell_switch(name, ip=None, netmask=None, gateway=None, dhcp=None,
password=None, snmp=None):
'''
Manage switches in a Dell Chassis.
name
The switch designation (e.g. switch-1, switch-2)
ip
The Static IP Address of the switch
netmask
The netmask for the static IP
gateway
The gateway for the static IP
dhcp
True: Enable DHCP
False: Do not change DHCP setup
(disabling DHCP is automatic when a static IP is set)
password
The access (root) password for the switch
snmp
The SNMP community string for the switch
Example:
.. code-block:: yaml
my-dell-chassis:
chassis.dell_switch:
- switch: switch-1
- ip: 192.168.1.1
- netmask: 255.255.255.0
- gateway: 192.168.1.254
- dhcp: True
- password: secret
- snmp: public
'''
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
current_nic = __salt__['chassis.cmd']('network_info', module=name)
if current_nic.get('retcode', 0) != 0:
ret['result'] = False
ret['comment'] = current_nic['stdout']
return ret
if ip or netmask or gateway:
if not ip:
ip = current_nic['Network']['IP Address']
if not netmask:
ip = current_nic['Network']['Subnet Mask']
if not gateway:
ip = current_nic['Network']['Gateway']
if current_nic['Network']['DHCP Enabled'] == '0' and dhcp:
ret['changes'].update({'DHCP': {'Old': {'DHCP Enabled': current_nic['Network']['DHCP Enabled']},
'New': {'DHCP Enabled': dhcp}}})
if ((ip or netmask or gateway) and not dhcp and (ip != current_nic['Network']['IP Address'] or
netmask != current_nic['Network']['Subnet Mask'] or
gateway != current_nic['Network']['Gateway'])):
ret['changes'].update({'IP': {'Old': current_nic['Network'],
'New': {'IP Address': ip,
'Subnet Mask': netmask,
'Gateway': gateway}}})
if password:
if 'New' not in ret['changes']:
ret['changes']['New'] = {}
ret['changes']['New'].update({'Password': '*****'})
if snmp:
if 'New' not in ret['changes']:
ret['changes']['New'] = {}
ret['changes']['New'].update({'SNMP': '*****'})
if ret['changes'] == {}:
ret['comment'] = 'Switch ' + name + ' is already in desired state'
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Switch ' + name + ' configuration will change'
return ret
# Finally, set the necessary configurations on the chassis.
dhcp_ret = net_ret = password_ret = snmp_ret = True
if dhcp:
dhcp_ret = __salt__['chassis.cmd']('set_niccfg', module=name, dhcp=dhcp)
if ip or netmask or gateway:
net_ret = __salt__['chassis.cmd']('set_niccfg', ip, netmask, gateway, module=name)
if password:
password_ret = __salt__['chassis.cmd']('deploy_password', 'root', password, module=name)
if snmp:
snmp_ret = __salt__['chassis.cmd']('deploy_snmp', password, module=name)
if any([password_ret, snmp_ret, net_ret, dhcp_ret]) is False:
ret['result'] = False
ret['comment'] = 'There was an error setting the switch {0}.'.format(name)
ret['comment'] = 'Dell chassis switch {0} was updated.'.format(name)
return ret

View file

@ -49,7 +49,6 @@ from salt.modules.dockerng import (
CLIENT_TIMEOUT,
STOP_TIMEOUT,
VALID_CREATE_OPTS,
VALID_RUNTIME_OPTS,
_validate_input,
_get_repo_tag
)
@ -76,7 +75,7 @@ def __virtual__():
_validate_input, globals()
)
return __virtualname__
return False
return (False, __modules__.missing_fun_string('dockerng.version')) # pylint: disable=E0602
def _format_comments(comments):
@ -122,7 +121,7 @@ def _prep_input(kwargs):
raise SaltInvocationError(err)
def _compare(actual, create_kwargs, runtime_kwargs):
def _compare(actual, create_kwargs):
'''
Compare the desired configuration against the actual configuration returned
by dockerng.inspect_container
@ -131,250 +130,247 @@ def _compare(actual, create_kwargs, runtime_kwargs):
salt.utils.traverse_dict(actual, path, NOTSET, delimiter=':')
)
ret = {}
for desired, valid_opts in ((create_kwargs, VALID_CREATE_OPTS),
(runtime_kwargs, VALID_RUNTIME_OPTS)):
for item, data, in six.iteritems(desired):
if item not in valid_opts:
log.error(
'Trying to compare \'{0}\', but it is not a valid '
'parameter. Skipping.'.format(item)
)
continue
log.trace('dockerng.running: comparing ' + item)
conf_path = valid_opts[item]['path']
if isinstance(conf_path, tuple):
actual_data = [_get(x) for x in conf_path]
for val in actual_data:
if val is NOTSET:
_api_mismatch(item)
else:
actual_data = _get(conf_path)
if actual_data is NOTSET:
for item, data, in six.iteritems(create_kwargs):
if item not in VALID_CREATE_OPTS:
log.error(
'Trying to compare \'{0}\', but it is not a valid '
'parameter. Skipping.'.format(item)
)
continue
log.trace('dockerng.running: comparing ' + item)
conf_path = VALID_CREATE_OPTS[item]['path']
if isinstance(conf_path, tuple):
actual_data = [_get(x) for x in conf_path]
for val in actual_data:
if val is NOTSET:
_api_mismatch(item)
log.trace('dockerng.running ({0}): desired value: {1}'
.format(item, data))
log.trace('dockerng.running ({0}): actual value: {1}'
.format(item, actual_data))
else:
actual_data = _get(conf_path)
if actual_data is NOTSET:
_api_mismatch(item)
log.trace('dockerng.running ({0}): desired value: {1}'
.format(item, data))
log.trace('dockerng.running ({0}): actual value: {1}'
.format(item, actual_data))
if actual_data is None and data is not None \
or actual_data is not None and data is None:
ret.update({item: {'old': actual_data, 'new': data}})
continue
if actual_data is None and data is not None \
or actual_data is not None and data is None:
ret.update({item: {'old': actual_data, 'new': data}})
continue
# 'create' comparison params
if item == 'detach':
# Something unique here. Two fields to check, if both are False
# then detach is True
actual_detach = all(x is False for x in actual_data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_detach))
if actual_detach != data:
ret.update({item: {'old': actual_detach, 'new': data}})
continue
# 'create' comparison params
if item == 'detach':
# Something unique here. Two fields to check, if both are False
# then detach is True
actual_detach = all(x is False for x in actual_data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_detach))
if actual_detach != data:
ret.update({item: {'old': actual_detach, 'new': data}})
continue
elif item == 'environment':
actual_env = {}
for env_var in actual_data:
try:
key, val = env_var.split('=', 1)
except (AttributeError, ValueError):
log.warning(
'Unexpected environment variable in inspect '
'output {0}'.format(env_var)
)
continue
else:
actual_env[key] = val
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_env))
env_diff = {}
for key in data:
actual_val = actual_env.get(key)
if data[key] != actual_val:
env_ptr = env_diff.setdefault(item, {})
env_ptr.setdefault('old', {})[key] = actual_val
env_ptr.setdefault('new', {})[key] = data[key]
if env_diff:
ret.update(env_diff)
continue
elif item == 'ports':
# Munge the desired configuration instead of the actual
# configuration here, because the desired configuration is a
# list of ints or tuples, and that won't look as good in the
# nested outputter as a simple comparison of lists of
# port/protocol pairs (as found in the "actual" dict).
actual_ports = sorted(actual_data)
desired_ports = []
for port_def in data:
if isinstance(port_def, tuple):
desired_ports.append('{0}/{1}'.format(*port_def))
else:
desired_ports.append('{0}/tcp'.format(port_def))
desired_ports.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_ports))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_ports))
if actual_ports != desired_ports:
ret.update({item: {'old': actual_ports,
'new': desired_ports}})
continue
# 'runtime' comparison params
elif item == 'binds':
actual_binds = []
for bind in actual_data:
bind_parts = bind.split(':')
if len(bind_parts) == 2:
actual_binds.append(bind + ':rw')
else:
actual_binds.append(bind)
desired_binds = []
for host_path, bind_data in six.iteritems(data):
desired_binds.append(
'{0}:{1}:{2}'.format(
host_path,
bind_data['bind'],
'ro' if bind_data['ro'] else 'rw'
)
elif item == 'environment':
actual_env = {}
for env_var in actual_data:
try:
key, val = env_var.split('=', 1)
except (AttributeError, ValueError):
log.warning(
'Unexpected environment variable in inspect '
'output {0}'.format(env_var)
)
actual_binds.sort()
desired_binds.sort()
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
else:
actual_env[key] = val
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_env))
env_diff = {}
for key in data:
actual_val = actual_env.get(key)
if data[key] != actual_val:
env_ptr = env_diff.setdefault(item, {})
env_ptr.setdefault('old', {})[key] = actual_val
env_ptr.setdefault('new', {})[key] = data[key]
if env_diff:
ret.update(env_diff)
continue
elif item == 'port_bindings':
actual_binds = []
for container_port, bind_list in six.iteritems(actual_data):
elif item == 'ports':
# Munge the desired configuration instead of the actual
# configuration here, because the desired configuration is a
# list of ints or tuples, and that won't look as good in the
# nested outputter as a simple comparison of lists of
# port/protocol pairs (as found in the "actual" dict).
actual_ports = sorted(actual_data)
desired_ports = []
for port_def in data:
if isinstance(port_def, tuple):
desired_ports.append('{0}/{1}'.format(*port_def))
else:
desired_ports.append('{0}/tcp'.format(port_def))
desired_ports.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_ports))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_ports))
if actual_ports != desired_ports:
ret.update({item: {'old': actual_ports,
'new': desired_ports}})
continue
elif item == 'binds':
actual_binds = []
for bind in actual_data:
bind_parts = bind.split(':')
if len(bind_parts) == 2:
actual_binds.append(bind + ':rw')
else:
actual_binds.append(bind)
desired_binds = []
for host_path, bind_data in six.iteritems(data):
desired_binds.append(
'{0}:{1}:{2}'.format(
host_path,
bind_data['bind'],
'ro' if bind_data['ro'] else 'rw'
)
)
actual_binds.sort()
desired_binds.sort()
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
elif item == 'port_bindings':
actual_binds = []
for container_port, bind_list in six.iteritems(actual_data):
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
for bind_data in bind_list:
# Port range will have to be updated for future Docker
# versions (see
# https://github.com/docker/docker/issues/10220). Note
# that Docker 1.5.0 (released a few weeks after the fix
# was merged) does not appear to have this fix in it,
# so we're probably looking at 1.6.0 for this fix.
if bind_data['HostPort'] == '' or \
49153 <= int(bind_data['HostPort']) <= 65535:
host_port = ''
else:
host_port = bind_data['HostPort']
if bind_data['HostIp'] in ('0.0.0.0', ''):
if host_port:
bind_def = (host_port, container_port)
else:
bind_def = (container_port,)
else:
bind_def = (bind_data['HostIp'],
host_port,
container_port)
actual_binds.append(':'.join(bind_def))
desired_binds = []
for container_port, bind_list in six.iteritems(data):
try:
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
for bind_data in bind_list:
# Port range will have to be updated for future Docker
# versions (see
# https://github.com/docker/docker/issues/10220). Note
# that Docker 1.5.0 (released a few weeks after the fix
# was merged) does not appear to have this fix in it,
# so we're probably looking at 1.6.0 for this fix.
if bind_data['HostPort'] == '' or \
49153 <= int(bind_data['HostPort']) <= 65535:
except AttributeError:
# The port's protocol was not specified, so it is
# assumed to be TCP. Thus, according to docker-py usage
# examples, the port was passed as an int. Convert it
# to a string here.
container_port = str(container_port)
for bind_data in bind_list:
if isinstance(bind_data, tuple):
try:
host_ip, host_port = bind_data
host_port = str(host_port)
except ValueError:
host_ip = bind_data[0]
host_port = ''
else:
host_port = bind_data['HostPort']
if bind_data['HostIp'] in ('0.0.0.0', ''):
if host_port:
bind_def = (host_port, container_port)
else:
bind_def = (container_port,)
else:
bind_def = (bind_data['HostIp'],
host_port,
container_port)
actual_binds.append(':'.join(bind_def))
desired_binds = []
for container_port, bind_list in six.iteritems(data):
try:
if container_port.endswith('/tcp'):
container_port = container_port[:-4]
except AttributeError:
# The port's protocol was not specified, so it is
# assumed to be TCP. Thus, according to docker-py usage
# examples, the port was passed as an int. Convert it
# to a string here.
container_port = str(container_port)
for bind_data in bind_list:
if isinstance(bind_data, tuple):
try:
host_ip, host_port = bind_data
host_port = str(host_port)
except ValueError:
host_ip = bind_data[0]
host_port = ''
bind_def = '{0}:{1}:{2}'.format(
host_ip, host_port, container_port
bind_def = '{0}:{1}:{2}'.format(
host_ip, host_port, container_port
)
else:
if bind_data is not None:
bind_def = '{0}:{1}'.format(
bind_data, container_port
)
else:
if bind_data is not None:
bind_def = '{0}:{1}'.format(
bind_data, container_port
)
else:
bind_def = container_port
desired_binds.append(bind_def)
actual_binds.sort()
desired_binds.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_binds))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_binds))
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
elif item == 'links':
actual_links = []
for link in actual_data:
try:
link_name, alias_info = link.split(':')
except ValueError:
log.error(
'Failed to compare link {0}, unrecognized format'
.format(link)
)
continue
container_name, _, link_alias = alias_info.rpartition('/')
if not container_name:
log.error(
'Failed to interpret link alias from {0}, '
'unrecognized format'.format(alias_info)
)
continue
actual_links.append((link_name, link_alias))
actual_links.sort()
desired_links = sorted(data)
if actual_links != desired_links:
ret.update({item: {'old': actual_links,
'new': desired_links}})
continue
elif item == 'extra_hosts':
actual_hosts = sorted(actual_data)
desired_hosts = sorted(
['{0}:{1}'.format(x, y) for x, y in six.iteritems(data)]
)
if actual_hosts != desired_hosts:
ret.update({item: {'old': actual_hosts,
'new': desired_hosts}})
continue
elif isinstance(data, list):
# Compare two sorted lists of items. Won't work for "command"
# or "entrypoint" because those are both shell commands and the
# original order matters. It will, however, work for "volumes"
# because even though "volumes" is a sub-dict nested within the
# "actual" dict sorted(somedict) still just gives you a sorted
# list of the dictionary's keys. And we don't care about the
# value for "volumes", just its keys.
actual_data = sorted(actual_data)
desired_data = sorted(data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_data))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_data))
if actual_data != desired_data:
ret.update({item: {'old': actual_data,
'new': desired_data}})
bind_def = container_port
desired_binds.append(bind_def)
actual_binds.sort()
desired_binds.sort()
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_binds))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_binds))
if actual_binds != desired_binds:
ret.update({item: {'old': actual_binds,
'new': desired_binds}})
continue
else:
# Generic comparison, works on strings, numeric types, and
# booleans
if actual_data != data:
ret.update({item: {'old': actual_data, 'new': data}})
elif item == 'links':
actual_links = []
for link in actual_data:
try:
link_name, alias_info = link.split(':')
except ValueError:
log.error(
'Failed to compare link {0}, unrecognized format'
.format(link)
)
continue
container_name, _, link_alias = alias_info.rpartition('/')
if not container_name:
log.error(
'Failed to interpret link alias from {0}, '
'unrecognized format'.format(alias_info)
)
continue
actual_links.append((link_name, link_alias))
actual_links.sort()
desired_links = sorted(data)
if actual_links != desired_links:
ret.update({item: {'old': actual_links,
'new': desired_links}})
continue
elif item == 'extra_hosts':
actual_hosts = sorted(actual_data)
desired_hosts = sorted(
['{0}:{1}'.format(x, y) for x, y in six.iteritems(data)]
)
if actual_hosts != desired_hosts:
ret.update({item: {'old': actual_hosts,
'new': desired_hosts}})
continue
elif isinstance(data, list):
# Compare two sorted lists of items. Won't work for "command"
# or "entrypoint" because those are both shell commands and the
# original order matters. It will, however, work for "volumes"
# because even though "volumes" is a sub-dict nested within the
# "actual" dict sorted(somedict) still just gives you a sorted
# list of the dictionary's keys. And we don't care about the
# value for "volumes", just its keys.
actual_data = sorted(actual_data)
desired_data = sorted(data)
log.trace('dockerng.running ({0}): munged actual value: {1}'
.format(item, actual_data))
log.trace('dockerng.running ({0}): munged desired value: {1}'
.format(item, desired_data))
if actual_data != desired_data:
ret.update({item: {'old': actual_data,
'new': desired_data}})
continue
else:
# Generic comparison, works on strings, numeric types, and
# booleans
if actual_data != data:
ret.update({item: {'old': actual_data, 'new': data}})
return ret
@ -1450,19 +1446,10 @@ def running(name,
# Strip __pub kwargs and divide the remaining arguments into the ones for
# container creation and the ones for starting containers.
invalid_kwargs = []
create_kwargs = {}
runtime_kwargs = {}
kwargs_copy = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
send_signal = kwargs_copy.pop('send_signal', False)
create_kwargs = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
send_signal = create_kwargs.pop('send_signal', False)
for key, val in six.iteritems(kwargs_copy):
if key in VALID_CREATE_OPTS:
create_kwargs[key] = val
elif key in VALID_RUNTIME_OPTS:
runtime_kwargs[key] = val
else:
invalid_kwargs.append(key)
invalid_kwargs = set(create_kwargs.keys()).difference(set(VALID_CREATE_OPTS.keys()))
if invalid_kwargs:
ret['comment'] = (
'The following arguments are invalid: {0}'
@ -1473,18 +1460,14 @@ def running(name,
# Input validation
try:
# Repack any dictlists that need it
_prep_input(runtime_kwargs)
_prep_input(create_kwargs)
# Perform data type validation and, where necessary, munge
# the data further so it is in a format that can be passed
# to dockerng.start.
_validate_input('runtime',
runtime_kwargs,
# to dockerng.create.
_validate_input(create_kwargs,
validate_ip_addrs=validate_ip_addrs)
# Add any needed container creation arguments based on the validated
# runtime arguments.
if runtime_kwargs.get('binds') is not None:
if create_kwargs.get('binds') is not None:
if 'volumes' not in create_kwargs:
# Check if there are preconfigured volumes in the image
for step in __salt__['dockerng.history'](image, quiet=True):
@ -1495,9 +1478,9 @@ def running(name,
# the ones from the "binds" configuration.
create_kwargs['volumes'] = [
x['bind']
for x in six.itervalues(runtime_kwargs['binds'])
for x in six.itervalues(create_kwargs['binds'])
]
if runtime_kwargs.get('port_bindings') is not None:
if create_kwargs.get('port_bindings') is not None:
if 'ports' not in create_kwargs:
# Check if there are preconfigured ports in the image
for step in __salt__['dockerng.history'](image, quiet=True):
@ -1507,14 +1490,8 @@ def running(name,
# No preconfigured ports, we need to make our own. Use
# the ones from the "port_bindings" configuration.
create_kwargs['ports'] = list(
runtime_kwargs['port_bindings'])
create_kwargs['port_bindings'])
# Perform data type validation and, where necessary, munge
# the data further so it is in a format that can be passed
# to dockerng.create.
_validate_input('create',
create_kwargs,
validate_ip_addrs=validate_ip_addrs)
except SaltInvocationError as exc:
ret['comment'] = '{0}'.format(exc)
return ret
@ -1540,9 +1517,7 @@ def running(name,
# Container is the correct image, let's check the container
# config and see if we need to replace the container
try:
changes_needed = _compare(pre_config,
create_kwargs,
runtime_kwargs)
changes_needed = _compare(pre_config, create_kwargs)
if changes_needed:
log.debug(
'dockerng.running: Analysis of container \'{0}\' '
@ -1634,6 +1609,7 @@ def running(name,
create_result = __salt__['dockerng.create'](
image,
name=name,
validate_ip_addrs=False,
# Already validated input
validate_input=False,
client_timeout=client_timeout,
@ -1653,10 +1629,6 @@ def running(name,
# Start container
__salt__['dockerng.start'](
name,
# Already validated input earlier, no need to repeat it
validate_ip_addrs=False,
validate_input=False,
**runtime_kwargs
)
except Exception as exc:
comments.append(
@ -1678,9 +1650,7 @@ def running(name,
if changes_needed:
try:
post_config = __salt__['dockerng.inspect_container'](name)
changes_still_needed = _compare(post_config,
create_kwargs,
runtime_kwargs)
changes_still_needed = _compare(post_config, create_kwargs)
if changes_still_needed:
log.debug(
'dockerng.running: Analysis of container \'{0}\' after '

View file

@ -12,14 +12,25 @@ import time
from salt.utils import warn_until
# Import OpenStack libs
from keystoneclient.apiclient.exceptions import \
Unauthorized as kstone_Unauthorized
from glanceclient.exc import \
HTTPUnauthorized as glance_Unauthorized
try:
from keystoneclient.apiclient.exceptions import \
Unauthorized as kstone_Unauthorized
from glanceclient.exc import \
HTTPUnauthorized as glance_Unauthorized
HAS_DEPENDENCIES = True
except ImportError:
HAS_DEPENDENCIES = False
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load if dependencies are loaded
'''
return HAS_DEPENDENCIES
def _find_image(name):
'''
Tries to find image with given name, returns

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
Support for htpasswd module. Requires apache2-utils package.
Support for htpasswd module. Requires the apache2-utils package for Debian-based distros.
.. versionadded:: 2014.7.0

View file

@ -104,7 +104,7 @@ def _check_pkg_version_format(pkg):
if not HAS_PIP:
ret['comment'] = (
'An importable pip module is required but could not be found on '
'your system. This usually means that the system''s pip package '
'your system. This usually means that the system\'s pip package '
'is not installed properly.'
)
@ -173,7 +173,7 @@ def _check_if_installed(prefix, state_pkg_name, version_spec,
# result: False means the package is not installed
ret = {'result': False, 'comment': None}
# Check if the requested packated is already installed.
# Check if the requested package is already installed.
try:
pip_list = __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd)
@ -250,12 +250,6 @@ def installed(name,
'''
Make sure the package is installed
.. note::
There is a known issue when using pip v1.0 that causes ``pip install`` to return
1 when executed without arguments. See :issue:`21845` for details and
potential workarounds.
name
The name of the python package to install. You can also specify version
numbers here using the standard operators ``==, >=, <=``. If
@ -597,7 +591,6 @@ def installed(name,
target_pkgs = []
already_installed_comments = []
if requirements or editable:
name = ''
comments = []
# Append comments if this is a dry run.
if __opts__['test']:
@ -626,6 +619,12 @@ def installed(name,
out = _check_if_installed(prefix, state_pkg_name, version_spec,
ignore_installed, force_reinstall,
upgrade, user, cwd, bin_env)
# If _check_if_installed result is None, something went wrong with
# the command running. This way we keep stateful output.
if out['result'] is None:
ret['result'] = False
ret['comment'] = out['comment']
return ret
else:
out = {'result': False, 'comment': None}
@ -705,7 +704,12 @@ def installed(name,
trusted_host=trusted_host
)
if pip_install_call and (pip_install_call.get('retcode', 1) == 0):
# Check the retcode for success, but don't fail if using pip1 and the package is
# already present. Pip1 returns a retcode of 1 (instead of 0 for pip2) if you run
# "pip install" without any arguments. See issue #21845.
if pip_install_call and \
(pip_install_call.get('retcode', 1) == 0 or pip_install_call.get('stdout', '').startswith(
'You must give at least one requirement to install')):
ret['result'] = True
if requirements or editable:

View file

@ -223,6 +223,8 @@ def managed(name, **kwargs):
If set to true, empty file before config repo, dangerous if use
multiple sources in one file.
.. versionadded:: 2015.8.0
refresh_db
If set to false this will skip refreshing the apt package database on
debian based systems.

View file

@ -247,8 +247,12 @@ def state(
if mdata.get('failed', False):
m_state = False
else:
m_ret = mdata['ret']
m_state = salt.utils.check_state_result(m_ret)
try:
m_ret = mdata['ret']
except KeyError:
m_state = False
if not m_state:
m_state = salt.utils.check_state_result(m_ret)
if not m_state:
if minion not in fail_minions:

View file

@ -218,7 +218,7 @@ class AESReqServerMixin(object):
elif os.path.isfile(pubfn):
# The key has been accepted, check it
if salt.utils.fopen(pubfn, 'r').read() != load['pub']:
if salt.utils.fopen(pubfn, 'r').read().strip() != load['pub'].strip():
log.error(
'Authentication attempt from {id} failed, the public '
'keys did not match. This may be an attempt to compromise '

View file

@ -354,6 +354,9 @@ class AsyncZeroMQPubChannel(salt.transport.mixins.auth.AESPubClientMixin, salt.t
payload = self.serial.loads(messages[0])
# 2 includes a header which says who should do it
elif messages_len == 2:
if messages[0] not in ('broadcast', self.hexid):
log.debug('Publish recieved for not this minion: {0}'.format(messages[0]))
raise tornado.gen.Return(None)
payload = self.serial.loads(messages[1])
else:
raise Exception(('Invalid number of messages ({0}) in zeromq pub'
@ -384,7 +387,8 @@ class AsyncZeroMQPubChannel(salt.transport.mixins.auth.AESPubClientMixin, salt.t
@tornado.gen.coroutine
def wrap_callback(messages):
payload = yield self._decode_messages(messages)
callback(payload)
if payload is not None:
callback(payload)
return self.stream.on_recv(wrap_callback)

View file

@ -290,6 +290,20 @@ def _get_jinja_error(trace, context=None):
return line, out
def _decode_recursively(object_):
if isinstance(object_, list):
return [_decode_recursively(o) for o in object_]
if isinstance(object_, tuple):
return tuple([_decode_recursively(o) for o in object_])
if isinstance(object_, dict):
return dict([(_decode_recursively(key), _decode_recursively(value))
for key, value in six.iteritems(object_)])
elif isinstance(object_, string_types):
return salt.utils.locales.sdecode(object_)
else:
return object_
def render_jinja_tmpl(tmplstr, context, tmplpath=None):
opts = context['opts']
saltenv = context['saltenv']
@ -354,13 +368,7 @@ def render_jinja_tmpl(tmplstr, context, tmplpath=None):
jinja_env.tests['list'] = salt.utils.is_list
decoded_context = {}
for key, value in six.iteritems(context):
if not isinstance(value, string_types):
decoded_context[key] = value
continue
decoded_context[key] = salt.utils.locales.sdecode(value)
decoded_context = _decode_recursively(context)
try:
template = jinja_env.from_string(tmplstr)

View file

@ -1,7 +1,7 @@
{% if grains['os'] == 'CentOS' %}
# START CentOS pkgrepo tests
{% if grains['osrelease'].startswith('7.') %}
{% if grains['osmajorrelease'] == '7' %}
epel-salttest:
pkgrepo.managed:
- humanname: Extra Packages for Enterprise Linux 7 - $basearch (salttest)

View file

@ -138,6 +138,56 @@ class ScheduleTestCase(TestCase):
test=True),
{'comment': comm4, 'result': True})
# 'modify' function tests: 1
def test_modify(self):
'''
Test if it modify an existing job in the schedule.
'''
comm1 = 'Error: Unable to use "seconds", "minutes", "hours", ' \
'or "days" with "when" option.'
comm2 = 'Unable to use "when" and "cron" options together. Ignoring.'
comm3 = 'Job job2 does not exist in schedule.'
comm4 = 'Job: job3 would be modified in schedule.'
with patch.dict(schedule.__opts__, {'schedule': {'job1': JOB1,
'job3': {}},
'sock_dir': SOCK_DIR}):
mock = MagicMock(return_value=True)
with patch.dict(schedule.__salt__, {'event.fire': mock}):
_ret_value = {'complete': True, 'schedule': {'job1': JOB1,
'job3': {}}}
with patch.object(SaltEvent, 'get_event', return_value=_ret_value):
self.assertDictEqual(schedule.modify('job1', function='test.ping',
seconds=3600, when='2400'),
{'changes': {}, 'comment': comm1,
'result': False})
self.assertDictEqual(schedule.modify('job1', function='test.ping',
when='2400', cron='2'),
{'changes': {}, 'comment': comm2,
'result': False})
self.assertDictEqual(schedule.modify('job2'), {'changes': {},
'comment': comm3,
'result': False})
if sys.version_info[1] >= 7:
self.assertDictEqual(schedule.modify('job1', function='test.ping'),
{'changes': {},
'comment': 'Job job1 in correct state',
'result': True})
elif sys.version_info[1] >= 6:
self.assertDictEqual(schedule.modify('job1', function='test.ping', seconds=2600),
{'changes': {'diff': '--- \n+++ \n@@ -3,3 +3,4 @@\n jid_include:True\n maxrunning:1\n name:job1\n+seconds:2600\n'},
'comment': 'Modified job: job1 in schedule.',
'result': True})
ret = schedule.modify('job3', function='test.ping', test=True)
if 'diff' in ret['changes']:
del ret['changes']['diff'] # difflib formatting changes between 2.6 and 2.7
self.assertDictEqual(ret, {'changes': {}, 'comment': comm4, 'result': True})
# 'run_job' function tests: 1
def test_run_job(self):

View file

@ -66,13 +66,11 @@ class DockerngTestCase(TestCase):
'image:latest',
validate_input=False,
name='cont',
volumes=['/container-0'],
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
binds={'/host-0': {'bind': '/container-0', 'ro': True}},
volumes=['/container-0'],
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_predifined_volume(self):
'''
@ -103,13 +101,11 @@ class DockerngTestCase(TestCase):
dockerng_create.assert_called_with(
'image:latest',
validate_input=False,
name='cont',
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
binds={'/host-0': {'bind': '/container-0', 'ro': True}},
validate_ip_addrs=False,
validate_input=False)
name='cont',
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_no_predifined_ports(self):
'''
@ -142,12 +138,10 @@ class DockerngTestCase(TestCase):
validate_input=False,
name='cont',
ports=[9797],
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
port_bindings={9797: [9090]},
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_predifined_ports(self):
'''
@ -179,12 +173,10 @@ class DockerngTestCase(TestCase):
'image:latest',
validate_input=False,
name='cont',
client_timeout=60)
dockerng_start.assert_called_with(
'cont',
port_bindings={9797: [9090]},
validate_ip_addrs=False,
validate_input=False)
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_compare_images_by_id(self):
'''
@ -456,6 +448,7 @@ class DockerngTestCase(TestCase):
dockerng_create.assert_called_with(
'image:latest',
validate_input=False,
validate_ip_addrs=False,
name='cont',
labels=['LABEL1', 'LABEL2'],
client_timeout=60)