mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
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:
commit
a471832fa7
65 changed files with 3452 additions and 679 deletions
|
@ -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
|
||||
|
|
12
doc/conf.py
12
doc/conf.py
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
6
doc/ref/modules/all/salt.modules.chassis.rst
Normal file
6
doc/ref/modules/all/salt.modules.chassis.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
====================
|
||||
salt.modules.chassis
|
||||
====================
|
||||
|
||||
.. automodule:: salt.modules.chassis
|
||||
:members:
|
6
doc/ref/modules/all/salt.modules.dracr.rst
Normal file
6
doc/ref/modules/all/salt.modules.dracr.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
==================
|
||||
salt.modules.dracr
|
||||
==================
|
||||
|
||||
.. automodule:: salt.modules.dracr
|
||||
:members:
|
|
@ -10,5 +10,6 @@ Full list of builtin proxy modules
|
|||
:toctree:
|
||||
:template: autosummary.rst.tmpl
|
||||
|
||||
fx2
|
||||
junos
|
||||
rest_sample
|
||||
|
|
6
doc/ref/proxy/all/salt.proxy.fx2.rst
Normal file
6
doc/ref/proxy/all/salt.proxy.fx2.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
==============
|
||||
salt.proxy.fx2
|
||||
==============
|
||||
|
||||
.. automodule:: salt.proxy.fx2
|
||||
:members:
|
|
@ -50,6 +50,7 @@ Full list of builtin state modules
|
|||
cyg
|
||||
ddns
|
||||
debconfmod
|
||||
dellchassis
|
||||
disk
|
||||
dockerio
|
||||
dockerng
|
||||
|
|
6
doc/ref/states/all/salt.states.dellchassis.rst
Normal file
6
doc/ref/states/all/salt.states.dellchassis.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
=======================
|
||||
salt.states.dellchassis
|
||||
=======================
|
||||
|
||||
.. automodule:: salt.states.dellchassis
|
||||
:members:
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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``.
|
||||
|
||||
|
|
|
@ -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``.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
82
doc/topics/proxyminion/ssh.rst
Normal file
82
doc/topics/proxyminion/ssh.rst
Normal 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.
|
|
@ -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
|
||||
--------------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
===============
|
||||
|
||||
|
|
|
@ -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`)
|
||||
|
|
|
@ -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
|
||||
==========
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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']
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
43
salt/modules/chassis.py
Normal 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)
|
|
@ -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
1173
salt/modules/dracr.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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 = []
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
40
salt/modules/ssh_package.py
Normal file
40
salt/modules/ssh_package.py
Normal 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)
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
252
salt/proxy/fx2.py
Normal 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
124
salt/proxy/ssh_sample.py
Normal 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)
|
|
@ -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
325
salt/states/dellchassis.py
Normal 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
|
|
@ -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 '
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 '
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue