mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Blacken docs
This commit is contained in:
parent
807bb03fee
commit
5e72e192f9
57 changed files with 1069 additions and 1022 deletions
|
@ -705,7 +705,7 @@ repos:
|
||||||
)$
|
)$
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: stable
|
rev: 19.10b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
# This tells pre-commit not to pass files to black.
|
# This tells pre-commit not to pass files to black.
|
||||||
|
@ -717,6 +717,13 @@ repos:
|
||||||
tests/kitchen/.*
|
tests/kitchen/.*
|
||||||
)$
|
)$
|
||||||
|
|
||||||
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
|
rev: v1.7.0
|
||||||
|
hooks:
|
||||||
|
- id: blacken-docs
|
||||||
|
args: [--skip-errors]
|
||||||
|
files: ^doc/.*\.rst
|
||||||
|
additional_dependencies: [black==19.10b0]
|
||||||
|
|
||||||
- repo: https://github.com/saltstack/salt-nox-pre-commit
|
- repo: https://github.com/saltstack/salt-nox-pre-commit
|
||||||
rev: master
|
rev: master
|
||||||
|
|
|
@ -183,10 +183,10 @@ custom LogRecord attributes to colorize console log output:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'%(colorlevel)s' # log level name colorized by level
|
"%(colorlevel)s" # log level name colorized by level
|
||||||
'%(colorname)s' # colorized module name
|
"%(colorname)s" # colorized module name
|
||||||
'%(colorprocess)s' # colorized process number
|
"%(colorprocess)s" # colorized process number
|
||||||
'%(colormsg)s' # log message colorized by level
|
"%(colormsg)s" # log message colorized by level
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The ``%(colorlevel)s``, ``%(colorname)s``, and ``%(colorprocess)``
|
The ``%(colorlevel)s``, ``%(colorname)s``, and ``%(colorprocess)``
|
||||||
|
@ -212,9 +212,9 @@ these custom LogRecord attributes that include padding and enclosing brackets
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'%(bracketlevel)s' # equivalent to [%(levelname)-8s]
|
"%(bracketlevel)s" # equivalent to [%(levelname)-8s]
|
||||||
'%(bracketname)s' # equivalent to [%(name)-17s]
|
"%(bracketname)s" # equivalent to [%(name)-17s]
|
||||||
'%(bracketprocess)s' # equivalent to [%(process)5s]
|
"%(bracketprocess)s" # equivalent to [%(process)5s]
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
|
|
@ -876,10 +876,11 @@ For example, with these custom grains functions:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def custom1_k1():
|
def custom1_k1():
|
||||||
return {'custom1': {'k1': 'v1'}}
|
return {"custom1": {"k1": "v1"}}
|
||||||
|
|
||||||
|
|
||||||
def custom1_k2():
|
def custom1_k2():
|
||||||
return {'custom1': {'k2': 'v2'}}
|
return {"custom1": {"k2": "v2"}}
|
||||||
|
|
||||||
Without ``grains_deep_merge``, the result would be:
|
Without ``grains_deep_merge``, the result would be:
|
||||||
|
|
||||||
|
@ -2710,8 +2711,7 @@ the ``extra_minion_data`` parameter will be
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'opt1': 'value1',
|
{"opt1": "value1", "opt2": {"subopt1": "value2"}}
|
||||||
'opt2': {'subopt1': 'value2'}}
|
|
||||||
|
|
||||||
Security Settings
|
Security Settings
|
||||||
=================
|
=================
|
||||||
|
|
|
@ -59,7 +59,8 @@ the ``execute`` function with the following signature:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def execute(opts, data, func, args, kwargs)
|
def execute(opts, data, func, args, kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
Where the args are:
|
Where the args are:
|
||||||
|
|
||||||
|
|
|
@ -133,13 +133,14 @@ from within a minion module the built in ``__opts__`` data can be passed:
|
||||||
import salt.minion
|
import salt.minion
|
||||||
import salt.fileclient
|
import salt.fileclient
|
||||||
|
|
||||||
def get_file(path, dest, saltenv='base'):
|
|
||||||
'''
|
def get_file(path, dest, saltenv="base"):
|
||||||
|
"""
|
||||||
Used to get a single file from the Salt master
|
Used to get a single file from the Salt master
|
||||||
|
|
||||||
CLI Example:
|
CLI Example:
|
||||||
salt '*' cp.get_file salt://vimrc /etc/vimrc
|
salt '*' cp.get_file salt://vimrc /etc/vimrc
|
||||||
'''
|
"""
|
||||||
# Get the fileclient object
|
# Get the fileclient object
|
||||||
client = salt.fileclient.get_file_client(__opts__)
|
client = salt.fileclient.get_file_client(__opts__)
|
||||||
# Call get_file
|
# Call get_file
|
||||||
|
@ -153,12 +154,13 @@ data is not available, it needs to be generated:
|
||||||
import salt.fileclient
|
import salt.fileclient
|
||||||
import salt.config
|
import salt.config
|
||||||
|
|
||||||
def get_file(path, dest, saltenv='base'):
|
|
||||||
'''
|
def get_file(path, dest, saltenv="base"):
|
||||||
|
"""
|
||||||
Used to get a single file from the Salt master
|
Used to get a single file from the Salt master
|
||||||
'''
|
"""
|
||||||
# Get the configuration data
|
# Get the configuration data
|
||||||
opts = salt.config.minion_config('/etc/salt/minion')
|
opts = salt.config.minion_config("/etc/salt/minion")
|
||||||
# Get the fileclient object
|
# Get the fileclient object
|
||||||
client = salt.fileclient.get_file_client(opts)
|
client = salt.fileclient.get_file_client(opts)
|
||||||
# Call get_file
|
# Call get_file
|
||||||
|
|
|
@ -25,7 +25,8 @@ for a minion instance:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import salt.config
|
import salt.config
|
||||||
opts = salt.config.minion_config('/etc/salt/minion')
|
|
||||||
|
opts = salt.config.minion_config("/etc/salt/minion")
|
||||||
print(opts)
|
print(opts)
|
||||||
|
|
||||||
To generate and display `opts` for a master, the process is similar:
|
To generate and display `opts` for a master, the process is similar:
|
||||||
|
@ -33,5 +34,6 @@ To generate and display `opts` for a master, the process is similar:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import salt.config
|
import salt.config
|
||||||
opts = salt.config.master_config('/etc/salt/master')
|
|
||||||
|
opts = salt.config.master_config("/etc/salt/master")
|
||||||
print(opts)
|
print(opts)
|
||||||
|
|
|
@ -92,7 +92,7 @@ included libraries.
|
||||||
|
|
||||||
|
|
||||||
def is_ok(person):
|
def is_ok(person):
|
||||||
''' Checks whether a person is really a lumberjack '''
|
""" Checks whether a person is really a lumberjack """
|
||||||
return sleep.all_night(person) and work.all_day(person)
|
return sleep.all_night(person) and work.all_day(person)
|
||||||
|
|
||||||
Then, create the zip:
|
Then, create the zip:
|
||||||
|
@ -154,7 +154,7 @@ dict:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def foo(bar):
|
def foo(bar):
|
||||||
return __salt__['cmd.run'](bar)
|
return __salt__["cmd.run"](bar)
|
||||||
|
|
||||||
This code will call the `run` function in the :mod:`cmd <salt.modules.cmdmod>`
|
This code will call the `run` function in the :mod:`cmd <salt.modules.cmdmod>`
|
||||||
module and pass the argument ``bar`` to it.
|
module and pass the argument ``bar`` to it.
|
||||||
|
@ -229,14 +229,16 @@ as also available in the ``__opts__`` dict.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Cheese module initialization example
|
Cheese module initialization example
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(opts):
|
def __init__(opts):
|
||||||
'''
|
"""
|
||||||
Allow foreign imports if configured to do so
|
Allow foreign imports if configured to do so
|
||||||
'''
|
"""
|
||||||
if opts.get('cheese.allow_foreign', False):
|
if opts.get("cheese.allow_foreign", False):
|
||||||
_enable_foreign_products()
|
_enable_foreign_products()
|
||||||
|
|
||||||
|
|
||||||
|
@ -266,9 +268,7 @@ names to Salt :ref:`outputters <all-salt.output>`.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__outputter__ = {
|
__outputter__ = {"run": "txt"}
|
||||||
'run': 'txt'
|
|
||||||
}
|
|
||||||
|
|
||||||
This will ensure that the ``txt`` outputter is used to display output from the
|
This will ensure that the ``txt`` outputter is used to display output from the
|
||||||
``run`` function.
|
``run`` function.
|
||||||
|
@ -326,44 +326,50 @@ the case when the dependency is unavailable.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Cheese execution (or returner/beacon/etc.) module
|
Cheese execution (or returner/beacon/etc.) module
|
||||||
'''
|
"""
|
||||||
try:
|
try:
|
||||||
import enzymes
|
import enzymes
|
||||||
|
|
||||||
HAS_ENZYMES = True
|
HAS_ENZYMES = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_ENZYMES = False
|
HAS_ENZYMES = False
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
only load cheese if enzymes are available
|
only load cheese if enzymes are available
|
||||||
'''
|
"""
|
||||||
if HAS_ENZYMES:
|
if HAS_ENZYMES:
|
||||||
return 'cheese'
|
return "cheese"
|
||||||
else:
|
else:
|
||||||
return False, 'The cheese execution module cannot be loaded: enzymes unavailable.'
|
return (
|
||||||
|
False,
|
||||||
|
"The cheese execution module cannot be loaded: enzymes unavailable.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def slice():
|
def slice():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Cheese state module. Note that this works in state modules because it is
|
Cheese state module. Note that this works in state modules because it is
|
||||||
guaranteed that execution modules are loaded first
|
guaranteed that execution modules are loaded first
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
only load cheese if enzymes are available
|
only load cheese if enzymes are available
|
||||||
'''
|
"""
|
||||||
# predicate loading of the cheese state on the corresponding execution module
|
# predicate loading of the cheese state on the corresponding execution module
|
||||||
if 'cheese.slice' in __salt__:
|
if "cheese.slice" in __salt__:
|
||||||
return 'cheese'
|
return "cheese"
|
||||||
else:
|
else:
|
||||||
return False, 'The cheese state module cannot be loaded: enzymes unavailable.'
|
return False, "The cheese state module cannot be loaded: enzymes unavailable."
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
@ -445,15 +451,15 @@ similar to the following:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Define the module's virtual name
|
# Define the module's virtual name
|
||||||
__virtualname__ = 'pkg'
|
__virtualname__ = "pkg"
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Confine this module to Mac OS with Homebrew.
|
Confine this module to Mac OS with Homebrew.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if salt.utils.path.which('brew') and __grains__['os'] == 'MacOS':
|
if salt.utils.path.which("brew") and __grains__["os"] == "MacOS":
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -472,12 +478,11 @@ For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Only load if git exists on the system
|
Only load if git exists on the system
|
||||||
'''
|
"""
|
||||||
if salt.utils.path.which('git') is None:
|
if salt.utils.path.which("git") is None:
|
||||||
return (False,
|
return (False, "The git execution module cannot be loaded: git unavailable.")
|
||||||
'The git execution module cannot be loaded: git unavailable.')
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -505,13 +510,13 @@ To add documentation add a `Python docstring`_ to the function.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def spam(eggs):
|
def spam(eggs):
|
||||||
'''
|
"""
|
||||||
A function to make some spam with eggs!
|
A function to make some spam with eggs!
|
||||||
|
|
||||||
CLI Example::
|
CLI Example::
|
||||||
|
|
||||||
salt '*' test.spam eggs
|
salt '*' test.spam eggs
|
||||||
'''
|
"""
|
||||||
return eggs
|
return eggs
|
||||||
|
|
||||||
Now when the sys.doc call is executed the docstring will be cleanly returned
|
Now when the sys.doc call is executed the docstring will be cleanly returned
|
||||||
|
@ -560,9 +565,9 @@ logs. The following code snippet demonstrates writing log messages:
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
log.info('Here is Some Information')
|
log.info("Here is Some Information")
|
||||||
log.warning('You Should Not Do That')
|
log.warning("You Should Not Do That")
|
||||||
log.error('It Is Busted')
|
log.error("It Is Busted")
|
||||||
|
|
||||||
Aliasing Functions
|
Aliasing Functions
|
||||||
==================
|
==================
|
||||||
|
@ -580,8 +585,8 @@ module, or from the cli, the alias name should be used.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__func_alias__ = {
|
__func_alias__ = {
|
||||||
'set_': 'set',
|
"set_": "set",
|
||||||
'list_': 'list',
|
"list_": "list",
|
||||||
}
|
}
|
||||||
|
|
||||||
Private Functions
|
Private Functions
|
||||||
|
@ -604,10 +609,11 @@ Objects NOT Loaded into the Salt Minion
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def _foobar(baz): # Preceded with an _
|
def _foobar(baz): # Preceded with an _
|
||||||
return baz
|
return baz
|
||||||
|
|
||||||
cheese = {} # Not a callable Python object
|
|
||||||
|
cheese = {} # Not a callable Python object
|
||||||
|
|
||||||
Useful Decorators for Modules
|
Useful Decorators for Modules
|
||||||
=============================
|
=============================
|
||||||
|
@ -640,29 +646,32 @@ removing it
|
||||||
try:
|
try:
|
||||||
import dependency_that_sometimes_exists
|
import dependency_that_sometimes_exists
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.trace('Failed to import dependency_that_sometimes_exists: {0}'.format(e))
|
log.trace("Failed to import dependency_that_sometimes_exists: {0}".format(e))
|
||||||
|
|
||||||
@depends('dependency_that_sometimes_exists')
|
|
||||||
|
@depends("dependency_that_sometimes_exists")
|
||||||
def foo():
|
def foo():
|
||||||
'''
|
"""
|
||||||
Function with a dependency on the "dependency_that_sometimes_exists" module,
|
Function with a dependency on the "dependency_that_sometimes_exists" module,
|
||||||
if the "dependency_that_sometimes_exists" is missing this function will not exist
|
if the "dependency_that_sometimes_exists" is missing this function will not exist
|
||||||
'''
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _fallback():
|
def _fallback():
|
||||||
'''
|
"""
|
||||||
Fallback function for the depends decorator to replace a function with
|
Fallback function for the depends decorator to replace a function with
|
||||||
'''
|
"""
|
||||||
return '"dependency_that_sometimes_exists" needs to be installed for this function to exist'
|
return '"dependency_that_sometimes_exists" needs to be installed for this function to exist'
|
||||||
|
|
||||||
@depends('dependency_that_sometimes_exists', fallback_function=_fallback)
|
|
||||||
|
@depends("dependency_that_sometimes_exists", fallback_function=_fallback)
|
||||||
def foo():
|
def foo():
|
||||||
'''
|
"""
|
||||||
Function with a dependency on the "dependency_that_sometimes_exists" module.
|
Function with a dependency on the "dependency_that_sometimes_exists" module.
|
||||||
If the "dependency_that_sometimes_exists" is missing this function will be
|
If the "dependency_that_sometimes_exists" is missing this function will be
|
||||||
replaced with "_fallback"
|
replaced with "_fallback"
|
||||||
'''
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
In addition to global dependencies the depends decorator also supports raw
|
In addition to global dependencies the depends decorator also supports raw
|
||||||
|
@ -675,10 +684,12 @@ booleans.
|
||||||
HAS_DEP = False
|
HAS_DEP = False
|
||||||
try:
|
try:
|
||||||
import dependency_that_sometimes_exists
|
import dependency_that_sometimes_exists
|
||||||
|
|
||||||
HAS_DEP = True
|
HAS_DEP = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@depends(HAS_DEP)
|
@depends(HAS_DEP)
|
||||||
def foo():
|
def foo():
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -40,7 +40,7 @@ In Python, the above maps to:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'my_key': 'my_value'}
|
{"my_key": "my_value"}
|
||||||
|
|
||||||
Dictionaries can be nested:
|
Dictionaries can be nested:
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ And in Python:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'first_level_dict_key': {'second_level_dict_key': 'value_in_second_level_dict' } }
|
{"first_level_dict_key": {"second_level_dict_key": "value_in_second_level_dict"}}
|
||||||
|
|
||||||
Rule Three: Dashes
|
Rule Three: Dashes
|
||||||
------------------
|
------------------
|
||||||
|
|
|
@ -73,17 +73,14 @@ to install a package:
|
||||||
|
|
||||||
#!py
|
#!py
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
'''
|
"""
|
||||||
Install version 1.5-1.el7 of package "python-foo"
|
Install version 1.5-1.el7 of package "python-foo"
|
||||||
'''
|
"""
|
||||||
return {
|
return {
|
||||||
'include': ['python'],
|
"include": ["python"],
|
||||||
'python-foo': {
|
"python-foo": {"pkg.installed": [{"version": "1.5-1.el7"},]},
|
||||||
'pkg.installed': [
|
|
||||||
{'version': '1.5-1.el7'},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
This would be equivalent to the following:
|
This would be equivalent to the following:
|
||||||
|
@ -185,7 +182,8 @@ strings or file-like objects as input. For example:
|
||||||
import mycoolmodule
|
import mycoolmodule
|
||||||
from salt.ext import six
|
from salt.ext import six
|
||||||
|
|
||||||
def render(data, saltenv='base', sls='', **kwargs):
|
|
||||||
|
def render(data, saltenv="base", sls="", **kwargs):
|
||||||
if not isinstance(data, six.string_types):
|
if not isinstance(data, six.string_types):
|
||||||
# Read from file-like object
|
# Read from file-like object
|
||||||
data = data.read()
|
data = data.read()
|
||||||
|
@ -227,7 +225,8 @@ Here is a simple YAML renderer example:
|
||||||
from salt.utils.yamlloader import SaltYamlSafeLoader
|
from salt.utils.yamlloader import SaltYamlSafeLoader
|
||||||
from salt.ext import six
|
from salt.ext import six
|
||||||
|
|
||||||
def render(yaml_data, saltenv='', sls='', **kws):
|
|
||||||
|
def render(yaml_data, saltenv="", sls="", **kws):
|
||||||
if not isinstance(yaml_data, six.string_types):
|
if not isinstance(yaml_data, six.string_types):
|
||||||
yaml_data = yaml_data.read()
|
yaml_data = yaml_data.read()
|
||||||
data = salt.utils.yaml.safe_load(yaml_data)
|
data = salt.utils.yaml.safe_load(yaml_data)
|
||||||
|
|
|
@ -74,19 +74,17 @@ Other optional functions can be included to add support for
|
||||||
import redis
|
import redis
|
||||||
import salt.utils.json
|
import salt.utils.json
|
||||||
|
|
||||||
|
|
||||||
def returner(ret):
|
def returner(ret):
|
||||||
'''
|
"""
|
||||||
Return information to a redis server
|
Return information to a redis server
|
||||||
'''
|
"""
|
||||||
# Get a redis connection
|
# Get a redis connection
|
||||||
serv = redis.Redis(
|
serv = redis.Redis(host="redis-serv.example.com", port=6379, db="0")
|
||||||
host='redis-serv.example.com',
|
serv.sadd("%(id)s:jobs" % ret, ret["jid"])
|
||||||
port=6379,
|
serv.set("%(jid)s:%(id)s" % ret, salt.utils.json.dumps(ret["return"]))
|
||||||
db='0')
|
serv.sadd("jobs", ret["jid"])
|
||||||
serv.sadd("%(id)s:jobs" % ret, ret['jid'])
|
serv.sadd(ret["jid"], ret["id"])
|
||||||
serv.set("%(jid)s:%(id)s" % ret, salt.utils.json.dumps(ret['return']))
|
|
||||||
serv.sadd('jobs', ret['jid'])
|
|
||||||
serv.sadd(ret['jid'], ret['id'])
|
|
||||||
|
|
||||||
The above example of a returner set to send the data to a Redis server
|
The above example of a returner set to send the data to a Redis server
|
||||||
serializes the data as JSON and sets it in redis.
|
serializes the data as JSON and sets it in redis.
|
||||||
|
@ -120,11 +118,13 @@ loaded as simply ``redis``:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import redis
|
import redis
|
||||||
|
|
||||||
HAS_REDIS = True
|
HAS_REDIS = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_REDIS = False
|
HAS_REDIS = False
|
||||||
|
|
||||||
__virtualname__ = 'redis'
|
__virtualname__ = "redis"
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
if not HAS_REDIS:
|
if not HAS_REDIS:
|
||||||
|
@ -154,9 +154,9 @@ must implement the following functions:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def prep_jid(nocache, passed_jid=None): # pylint: disable=unused-argument
|
def prep_jid(nocache, passed_jid=None): # pylint: disable=unused-argument
|
||||||
'''
|
"""
|
||||||
Do any work necessary to prepare a JID, including sending a custom id
|
Do any work necessary to prepare a JID, including sending a custom id
|
||||||
'''
|
"""
|
||||||
return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid()
|
return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid()
|
||||||
|
|
||||||
``save_load``
|
``save_load``
|
||||||
|
@ -171,26 +171,27 @@ must implement the following functions:
|
||||||
|
|
||||||
import salt.utils.json
|
import salt.utils.json
|
||||||
|
|
||||||
|
|
||||||
def save_load(jid, load, minions=None):
|
def save_load(jid, load, minions=None):
|
||||||
'''
|
"""
|
||||||
Save the load to the specified jid id
|
Save the load to the specified jid id
|
||||||
'''
|
"""
|
||||||
query = '''INSERT INTO salt.jids (
|
query = """INSERT INTO salt.jids (
|
||||||
jid, load
|
jid, load
|
||||||
) VALUES (
|
) VALUES (
|
||||||
'{0}', '{1}'
|
'{0}', '{1}'
|
||||||
);'''.format(jid, salt.utils.json.dumps(load))
|
);""".format(
|
||||||
|
jid, salt.utils.json.dumps(load)
|
||||||
|
)
|
||||||
|
|
||||||
# cassandra_cql.cql_query may raise a CommandExecutionError
|
# cassandra_cql.cql_query may raise a CommandExecutionError
|
||||||
try:
|
try:
|
||||||
__salt__['cassandra_cql.cql_query'](query)
|
__salt__["cassandra_cql.cql_query"](query)
|
||||||
except CommandExecutionError:
|
except CommandExecutionError:
|
||||||
log.critical('Could not save load in jids table.')
|
log.critical("Could not save load in jids table.")
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.critical(
|
log.critical("Unexpected error while inserting into jids: {0}".format(e))
|
||||||
'Unexpected error while inserting into jids: {0}'.format(e)
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,26 +202,30 @@ must implement the following functions:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def get_load(jid):
|
def get_load(jid):
|
||||||
'''
|
"""
|
||||||
Return the load data that marks a specified jid
|
Return the load data that marks a specified jid
|
||||||
'''
|
"""
|
||||||
query = '''SELECT load FROM salt.jids WHERE jid = '{0}';'''.format(jid)
|
query = """SELECT load FROM salt.jids WHERE jid = '{0}';""".format(jid)
|
||||||
|
|
||||||
ret = {}
|
ret = {}
|
||||||
|
|
||||||
# cassandra_cql.cql_query may raise a CommandExecutionError
|
# cassandra_cql.cql_query may raise a CommandExecutionError
|
||||||
try:
|
try:
|
||||||
data = __salt__['cassandra_cql.cql_query'](query)
|
data = __salt__["cassandra_cql.cql_query"](query)
|
||||||
if data:
|
if data:
|
||||||
load = data[0].get('load')
|
load = data[0].get("load")
|
||||||
if load:
|
if load:
|
||||||
ret = json.loads(load)
|
ret = json.loads(load)
|
||||||
except CommandExecutionError:
|
except CommandExecutionError:
|
||||||
log.critical('Could not get load from jids table.')
|
log.critical("Could not get load from jids table.")
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.critical('''Unexpected error while getting load from
|
log.critical(
|
||||||
jids: {0}'''.format(str(e)))
|
"""Unexpected error while getting load from
|
||||||
|
jids: {0}""".format(
|
||||||
|
str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -322,20 +327,21 @@ contains the jid and therefore is guaranteed to be unique.
|
||||||
|
|
||||||
import salt.utils.json
|
import salt.utils.json
|
||||||
|
|
||||||
def event_return(events):
|
|
||||||
'''
|
|
||||||
Return event to mysql server
|
|
||||||
|
|
||||||
Requires that configuration be enabled via 'event_return'
|
def event_return(events):
|
||||||
option in master config.
|
"""
|
||||||
'''
|
Return event to mysql server
|
||||||
with _get_serv(events, commit=True) as cur:
|
|
||||||
for event in events:
|
Requires that configuration be enabled via 'event_return'
|
||||||
tag = event.get('tag', '')
|
option in master config.
|
||||||
data = event.get('data', '')
|
"""
|
||||||
sql = '''INSERT INTO `salt_events` (`tag`, `data`, `master_id` )
|
with _get_serv(events, commit=True) as cur:
|
||||||
VALUES (%s, %s, %s)'''
|
for event in events:
|
||||||
cur.execute(sql, (tag, salt.utils.json.dumps(data), __opts__['id']))
|
tag = event.get("tag", "")
|
||||||
|
data = event.get("data", "")
|
||||||
|
sql = """INSERT INTO `salt_events` (`tag`, `data`, `master_id` )
|
||||||
|
VALUES (%s, %s, %s)"""
|
||||||
|
cur.execute(sql, (tag, salt.utils.json.dumps(data), __opts__["id"]))
|
||||||
|
|
||||||
|
|
||||||
Testing the Returner
|
Testing the Returner
|
||||||
|
|
|
@ -36,7 +36,7 @@ fired onto the master event bus where. For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def a_runner(outputter=None, display_progress=False):
|
def a_runner(outputter=None, display_progress=False):
|
||||||
print('Hello world')
|
print("Hello world")
|
||||||
...
|
...
|
||||||
|
|
||||||
The above would result in an event fired as follows:
|
The above would result in an event fired as follows:
|
||||||
|
@ -62,7 +62,7 @@ A custom runner may send its own progress event by using the
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if display_progress:
|
if display_progress:
|
||||||
__jid_event__.fire_event({'message': 'A progress message'}, 'progress')
|
__jid_event__.fire_event({"message": "A progress message"}, "progress")
|
||||||
|
|
||||||
The above would produce output on the console reading: ``A progress message``
|
The above would produce output on the console reading: ``A progress message``
|
||||||
as well as an event on the event similar to:
|
as well as an event on the event similar to:
|
||||||
|
@ -117,11 +117,12 @@ responding to Salt calls could look like this:
|
||||||
# Import salt modules
|
# Import salt modules
|
||||||
import salt.client
|
import salt.client
|
||||||
|
|
||||||
|
|
||||||
def up():
|
def up():
|
||||||
'''
|
"""
|
||||||
Print a list of all of the minions that are up
|
Print a list of all of the minions that are up
|
||||||
'''
|
"""
|
||||||
client = salt.client.LocalClient(__opts__['conf_file'])
|
client = salt.client.LocalClient(__opts__["conf_file"])
|
||||||
minions = client.cmd('*', 'test.version', timeout=1)
|
minions = client.cmd("*", "test.version", timeout=1)
|
||||||
for minion in sorted(minions):
|
for minion in sorted(minions):
|
||||||
print minion
|
print minion
|
||||||
|
|
|
@ -102,48 +102,48 @@ This example, simplified from the pkg state, shows how to create mod_aggregate f
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def mod_aggregate(low, chunks, running):
|
def mod_aggregate(low, chunks, running):
|
||||||
'''
|
"""
|
||||||
The mod_aggregate function which looks up all packages in the available
|
The mod_aggregate function which looks up all packages in the available
|
||||||
low chunks and merges them into a single pkgs ref in the present low data
|
low chunks and merges them into a single pkgs ref in the present low data
|
||||||
'''
|
"""
|
||||||
pkgs = []
|
pkgs = []
|
||||||
# What functions should we aggregate?
|
# What functions should we aggregate?
|
||||||
agg_enabled = [
|
agg_enabled = [
|
||||||
'installed',
|
"installed",
|
||||||
'latest',
|
"latest",
|
||||||
'removed',
|
"removed",
|
||||||
'purged',
|
"purged",
|
||||||
]
|
]
|
||||||
# The `low` data is just a dict with the state, function (fun) and
|
# The `low` data is just a dict with the state, function (fun) and
|
||||||
# arguments passed in from the sls
|
# arguments passed in from the sls
|
||||||
if low.get('fun') not in agg_enabled:
|
if low.get("fun") not in agg_enabled:
|
||||||
return low
|
return low
|
||||||
# Now look into what other things are set to execute
|
# Now look into what other things are set to execute
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
# The state runtime uses "tags" to track completed jobs, it may
|
# The state runtime uses "tags" to track completed jobs, it may
|
||||||
# look familiar with the _|-
|
# look familiar with the _|-
|
||||||
tag = __utils__['state.gen_tag'](chunk)
|
tag = __utils__["state.gen_tag"](chunk)
|
||||||
if tag in running:
|
if tag in running:
|
||||||
# Already ran the pkg state, skip aggregation
|
# Already ran the pkg state, skip aggregation
|
||||||
continue
|
continue
|
||||||
if chunk.get('state') == 'pkg':
|
if chunk.get("state") == "pkg":
|
||||||
if '__agg__' in chunk:
|
if "__agg__" in chunk:
|
||||||
continue
|
continue
|
||||||
# Check for the same function
|
# Check for the same function
|
||||||
if chunk.get('fun') != low.get('fun'):
|
if chunk.get("fun") != low.get("fun"):
|
||||||
continue
|
continue
|
||||||
# Pull out the pkg names!
|
# Pull out the pkg names!
|
||||||
if 'pkgs' in chunk:
|
if "pkgs" in chunk:
|
||||||
pkgs.extend(chunk['pkgs'])
|
pkgs.extend(chunk["pkgs"])
|
||||||
chunk['__agg__'] = True
|
chunk["__agg__"] = True
|
||||||
elif 'name' in chunk:
|
elif "name" in chunk:
|
||||||
pkgs.append(chunk['name'])
|
pkgs.append(chunk["name"])
|
||||||
chunk['__agg__'] = True
|
chunk["__agg__"] = True
|
||||||
if pkgs:
|
if pkgs:
|
||||||
if 'pkgs' in low:
|
if "pkgs" in low:
|
||||||
low['pkgs'].extend(pkgs)
|
low["pkgs"].extend(pkgs)
|
||||||
else:
|
else:
|
||||||
low['pkgs'] = pkgs
|
low["pkgs"] = pkgs
|
||||||
# The low has been modified and needs to be returned to the state
|
# The low has been modified and needs to be returned to the state
|
||||||
# runtime for execution
|
# runtime for execution
|
||||||
return low
|
return low
|
||||||
|
|
|
@ -66,13 +66,10 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ret = {'name': name,
|
ret = {"name": name, "result": False, "changes": {}, "comment": ""}
|
||||||
'result': False,
|
|
||||||
'changes': {},
|
|
||||||
'comment': ''}
|
|
||||||
|
|
||||||
if foo and bar:
|
if foo and bar:
|
||||||
ret['comment'] = 'Only one of foo and bar is permitted'
|
ret["comment"] = "Only one of foo and bar is permitted"
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
2. Check if changes need to be made. This is best done with an
|
2. Check if changes need to be made. This is best done with an
|
||||||
|
@ -83,7 +80,7 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
result = __salt__['modname.check'](name)
|
result = __salt__["modname.check"](name)
|
||||||
|
|
||||||
3. If step 2 found that the minion is already in the desired state, then exit
|
3. If step 2 found that the minion is already in the desired state, then exit
|
||||||
immediately with a ``True`` result and without making any changes.
|
immediately with a ``True`` result and without making any changes.
|
||||||
|
@ -91,8 +88,8 @@ A well-written state function will follow these steps:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
ret['result'] = True
|
ret["result"] = True
|
||||||
ret['comment'] = '{0} is already installed'.format(name)
|
ret["comment"] = "{0} is already installed".format(name)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
4. If step 2 found that changes *do* need to be made, then check to see if the
|
4. If step 2 found that changes *do* need to be made, then check to see if the
|
||||||
|
@ -102,10 +99,10 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if __opts__['test']:
|
if __opts__["test"]:
|
||||||
ret['result'] = None
|
ret["result"] = None
|
||||||
ret['comment'] = '{0} would be installed'.format(name)
|
ret["comment"] = "{0} would be installed".format(name)
|
||||||
ret['changes'] = result
|
ret["changes"] = result
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
5. Make the desired changes. This should again be done using a function from an
|
5. Make the desired changes. This should again be done using a function from an
|
||||||
|
@ -115,7 +112,7 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
result = __salt__['modname.install'](name)
|
result = __salt__["modname.install"](name)
|
||||||
|
|
||||||
6. Perform the same check from step 2 again to confirm whether or not the
|
6. Perform the same check from step 2 again to confirm whether or not the
|
||||||
minion is in the desired state. Just as in step 2, this function should be
|
minion is in the desired state. Just as in step 2, this function should be
|
||||||
|
@ -123,7 +120,7 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ret['changes'] = __salt__['modname.check'](name)
|
ret["changes"] = __salt__["modname.check"](name)
|
||||||
|
|
||||||
As you can see here, we are setting the ``changes`` key in the return
|
As you can see here, we are setting the ``changes`` key in the return
|
||||||
dictionary to the result of the ``modname.check`` function (just as we did
|
dictionary to the result of the ``modname.check`` function (just as we did
|
||||||
|
@ -135,11 +132,11 @@ A well-written state function will follow these steps:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if ret['changes']:
|
if ret["changes"]:
|
||||||
ret['comment'] = '{0} failed to install'.format(name)
|
ret["comment"] = "{0} failed to install".format(name)
|
||||||
else:
|
else:
|
||||||
ret['result'] = True
|
ret["result"] = True
|
||||||
ret['comment'] = '{0} was installed'.format(name)
|
ret["comment"] = "{0} was installed".format(name)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -202,7 +199,7 @@ Salt state modules can be cross-called by accessing the value in the
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile')
|
ret = __states__["file.managed"](name="/tmp/myfile", source="salt://myfile")
|
||||||
|
|
||||||
This code will call the `managed` function in the :mod:`file
|
This code will call the `managed` function in the :mod:`file
|
||||||
<salt.states.file>` state module and pass the arguments ``name`` and ``source``
|
<salt.states.file>` state module and pass the arguments ``name`` and ``source``
|
||||||
|
@ -225,8 +222,7 @@ A State Module must return a dict containing the following keys/values:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ret['changes'].update({'my_pkg_name': {'old': '',
|
ret["changes"].update({"my_pkg_name": {"old": "", "new": "my_pkg_name-1.0"}})
|
||||||
'new': 'my_pkg_name-1.0'}})
|
|
||||||
|
|
||||||
|
|
||||||
- **result:** A tristate value. ``True`` if the action was successful,
|
- **result:** A tristate value. ``True`` if the action was successful,
|
||||||
|
@ -273,9 +269,9 @@ run. An example of such a check could look like this:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Return comment of changes if test.
|
# Return comment of changes if test.
|
||||||
if __opts__['test']:
|
if __opts__["test"]:
|
||||||
ret['result'] = None
|
ret["result"] = None
|
||||||
ret['comment'] = 'State Foo will execute with param {0}'.format(bar)
|
ret["comment"] = "State Foo will execute with param {0}".format(bar)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
Make sure to test and return before performing any real actions on the minion.
|
Make sure to test and return before performing any real actions on the minion.
|
||||||
|
@ -337,13 +333,13 @@ A good example of the mod_init function is found in the pkg state module:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def mod_init(low):
|
def mod_init(low):
|
||||||
'''
|
"""
|
||||||
Refresh the package database here so that it only needs to happen once
|
Refresh the package database here so that it only needs to happen once
|
||||||
'''
|
"""
|
||||||
if low['fun'] == 'installed' or low['fun'] == 'latest':
|
if low["fun"] == "installed" or low["fun"] == "latest":
|
||||||
rtag = __gen_rtag()
|
rtag = __gen_rtag()
|
||||||
if not os.path.exists(rtag):
|
if not os.path.exists(rtag):
|
||||||
open(rtag, 'w+').write('')
|
open(rtag, "w+").write("")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -368,9 +364,9 @@ logs. The following code snippet demonstrates writing log messages:
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
log.info('Here is Some Information')
|
log.info("Here is Some Information")
|
||||||
log.warning('You Should Not Do That')
|
log.warning("You Should Not Do That")
|
||||||
log.error('It Is Busted')
|
log.error("It Is Busted")
|
||||||
|
|
||||||
|
|
||||||
Strings and Unicode
|
Strings and Unicode
|
||||||
|
@ -424,8 +420,9 @@ Example state module
|
||||||
|
|
||||||
import salt.exceptions
|
import salt.exceptions
|
||||||
|
|
||||||
|
|
||||||
def enforce_custom_thing(name, foo, bar=True):
|
def enforce_custom_thing(name, foo, bar=True):
|
||||||
'''
|
"""
|
||||||
Enforce the state of a custom thing
|
Enforce the state of a custom thing
|
||||||
|
|
||||||
This state module does a custom thing. It calls out to the execution module
|
This state module does a custom thing. It calls out to the execution module
|
||||||
|
@ -438,52 +435,53 @@ Example state module
|
||||||
A required argument
|
A required argument
|
||||||
bar : True
|
bar : True
|
||||||
An argument with a default value
|
An argument with a default value
|
||||||
'''
|
"""
|
||||||
ret = {
|
ret = {
|
||||||
'name': name,
|
"name": name,
|
||||||
'changes': {},
|
"changes": {},
|
||||||
'result': False,
|
"result": False,
|
||||||
'comment': '',
|
"comment": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start with basic error-checking. Do all the passed parameters make sense
|
# Start with basic error-checking. Do all the passed parameters make sense
|
||||||
# and agree with each-other?
|
# and agree with each-other?
|
||||||
if bar == True and foo.startswith('Foo'):
|
if bar == True and foo.startswith("Foo"):
|
||||||
raise salt.exceptions.SaltInvocationError(
|
raise salt.exceptions.SaltInvocationError(
|
||||||
'Argument "foo" cannot start with "Foo" if argument "bar" is True.')
|
'Argument "foo" cannot start with "Foo" if argument "bar" is True.'
|
||||||
|
)
|
||||||
|
|
||||||
# Check the current state of the system. Does anything need to change?
|
# Check the current state of the system. Does anything need to change?
|
||||||
current_state = __salt__['my_custom_module.current_state'](name)
|
current_state = __salt__["my_custom_module.current_state"](name)
|
||||||
|
|
||||||
if current_state == foo:
|
if current_state == foo:
|
||||||
ret['result'] = True
|
ret["result"] = True
|
||||||
ret['comment'] = 'System already in the correct state'
|
ret["comment"] = "System already in the correct state"
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
# The state of the system does need to be changed. Check if we're running
|
# The state of the system does need to be changed. Check if we're running
|
||||||
# in ``test=true`` mode.
|
# in ``test=true`` mode.
|
||||||
if __opts__['test'] == True:
|
if __opts__["test"] == True:
|
||||||
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
|
ret["comment"] = 'The state of "{0}" will be changed.'.format(name)
|
||||||
ret['changes'] = {
|
ret["changes"] = {
|
||||||
'old': current_state,
|
"old": current_state,
|
||||||
'new': 'Description, diff, whatever of the new state',
|
"new": "Description, diff, whatever of the new state",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Return ``None`` when running with ``test=true``.
|
# Return ``None`` when running with ``test=true``.
|
||||||
ret['result'] = None
|
ret["result"] = None
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
# Finally, make the actual change and return the result.
|
# Finally, make the actual change and return the result.
|
||||||
new_state = __salt__['my_custom_module.change_state'](name, foo)
|
new_state = __salt__["my_custom_module.change_state"](name, foo)
|
||||||
|
|
||||||
ret['comment'] = 'The state of "{0}" was changed!'.format(name)
|
ret["comment"] = 'The state of "{0}" was changed!'.format(name)
|
||||||
|
|
||||||
ret['changes'] = {
|
ret["changes"] = {
|
||||||
'old': current_state,
|
"old": current_state,
|
||||||
'new': new_state,
|
"new": new_state,
|
||||||
}
|
}
|
||||||
|
|
||||||
ret['result'] = True
|
ret["result"] = True
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -360,8 +360,7 @@ The return data structure would look something like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
[{'changes': ['/foo/bar'], 'tag': 'foo'},
|
[{"changes": ["/foo/bar"], "tag": "foo"}, {"changes": ["/foo/baz"], "tag": "bar"}]
|
||||||
{'changes': ['/foo/baz'], 'tag': 'bar'}]
|
|
||||||
|
|
||||||
Calling Execution Modules
|
Calling Execution Modules
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
|
@ -145,7 +145,7 @@ library. The following two lines set up the imports:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from salt.cloud.libcloudfuncs import * # pylint: disable=W0614,W0401
|
from salt.cloud.libcloudfuncs import * # pylint: disable=W0614,W0401
|
||||||
import salt.utils.functools
|
import salt.utils.functools
|
||||||
|
|
||||||
And then a series of declarations will make the necessary functions available
|
And then a series of declarations will make the necessary functions available
|
||||||
|
@ -162,7 +162,9 @@ within the cloud module.
|
||||||
destroy = salt.utils.functools.namespaced_function(destroy, globals())
|
destroy = salt.utils.functools.namespaced_function(destroy, globals())
|
||||||
list_nodes = salt.utils.functools.namespaced_function(list_nodes, globals())
|
list_nodes = salt.utils.functools.namespaced_function(list_nodes, globals())
|
||||||
list_nodes_full = salt.utils.functools.namespaced_function(list_nodes_full, globals())
|
list_nodes_full = salt.utils.functools.namespaced_function(list_nodes_full, globals())
|
||||||
list_nodes_select = salt.utils.functools.namespaced_function(list_nodes_select, globals())
|
list_nodes_select = salt.utils.functools.namespaced_function(
|
||||||
|
list_nodes_select, globals()
|
||||||
|
)
|
||||||
show_instance = salt.utils.functools.namespaced_function(show_instance, globals())
|
show_instance = salt.utils.functools.namespaced_function(show_instance, globals())
|
||||||
|
|
||||||
If necessary, these functions may be replaced by removing the appropriate
|
If necessary, these functions may be replaced by removing the appropriate
|
||||||
|
@ -300,11 +302,11 @@ general, the following code can be used as-is:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def list_nodes_select(call=None):
|
def list_nodes_select(call=None):
|
||||||
'''
|
"""
|
||||||
Return a list of the VMs that are on the provider, with select fields
|
Return a list of the VMs that are on the provider, with select fields
|
||||||
'''
|
"""
|
||||||
return salt.utils.cloud.list_nodes_select(
|
return salt.utils.cloud.list_nodes_select(
|
||||||
list_nodes_full('function'), __opts__['query.selection'], call,
|
list_nodes_full("function"), __opts__["query.selection"], call,
|
||||||
)
|
)
|
||||||
|
|
||||||
However, depending on the cloud provider, additional variables may be required.
|
However, depending on the cloud provider, additional variables may be required.
|
||||||
|
@ -315,16 +317,14 @@ appropriately:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def list_nodes_select(conn=None, call=None):
|
def list_nodes_select(conn=None, call=None):
|
||||||
'''
|
"""
|
||||||
Return a list of the VMs that are on the provider, with select fields
|
Return a list of the VMs that are on the provider, with select fields
|
||||||
'''
|
"""
|
||||||
if not conn:
|
if not conn:
|
||||||
conn = get_conn() # pylint: disable=E0602
|
conn = get_conn() # pylint: disable=E0602
|
||||||
|
|
||||||
return salt.utils.cloud.list_nodes_select(
|
return salt.utils.cloud.list_nodes_select(
|
||||||
list_nodes_full(conn, 'function'),
|
list_nodes_full(conn, "function"), __opts__["query.selection"], call,
|
||||||
__opts__['query.selection'],
|
|
||||||
call,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
This function is normally called with the ``-S`` option:
|
This function is normally called with the ``-S`` option:
|
||||||
|
@ -372,15 +372,15 @@ useful information to the user. A basic action looks like:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def show_instance(name, call=None):
|
def show_instance(name, call=None):
|
||||||
'''
|
"""
|
||||||
Show the details from EC2 concerning an AMI
|
Show the details from EC2 concerning an AMI
|
||||||
'''
|
"""
|
||||||
if call != 'action':
|
if call != "action":
|
||||||
raise SaltCloudSystemExit(
|
raise SaltCloudSystemExit(
|
||||||
'The show_instance action must be called with -a or --action.'
|
"The show_instance action must be called with -a or --action."
|
||||||
)
|
)
|
||||||
|
|
||||||
return _get_node(name)
|
return _get_node(name)
|
||||||
|
|
||||||
Please note that generic kwargs, if used, are passed through to actions as
|
Please note that generic kwargs, if used, are passed through to actions as
|
||||||
``kwargs`` and not ``**kwargs``. An example of this is seen in the Functions
|
``kwargs`` and not ``**kwargs``. An example of this is seen in the Functions
|
||||||
|
@ -406,16 +406,15 @@ useful information to the user. A basic function looks like:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def show_image(kwargs, call=None):
|
def show_image(kwargs, call=None):
|
||||||
'''
|
"""
|
||||||
Show the details from EC2 concerning an AMI
|
Show the details from EC2 concerning an AMI
|
||||||
'''
|
"""
|
||||||
if call != 'function':
|
if call != "function":
|
||||||
raise SaltCloudSystemExit(
|
raise SaltCloudSystemExit(
|
||||||
'The show_image action must be called with -f or --function.'
|
"The show_image action must be called with -f or --function."
|
||||||
)
|
)
|
||||||
|
|
||||||
params = {'ImageId.1': kwargs['image'],
|
params = {"ImageId.1": kwargs["image"], "Action": "DescribeImages"}
|
||||||
'Action': 'DescribeImages'}
|
|
||||||
result = query(params)
|
result = query(params)
|
||||||
log.info(result)
|
log.info(result)
|
||||||
|
|
||||||
|
|
|
@ -572,33 +572,43 @@ data:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
[{'deploy': False,
|
[
|
||||||
'image': 'ami-08d97e61',
|
{
|
||||||
'profile': 'Fedora-17',
|
"deploy": False,
|
||||||
'provider': 'my-ec2-config',
|
"image": "ami-08d97e61",
|
||||||
'securitygroup': ['default'],
|
"profile": "Fedora-17",
|
||||||
'size': 't1.micro',
|
"provider": "my-ec2-config",
|
||||||
'ssh_username': 'ec2_user'},
|
"securitygroup": ["default"],
|
||||||
{'deploy': False,
|
"size": "t1.micro",
|
||||||
'image': 'ami-09b61d60',
|
"ssh_username": "ec2_user",
|
||||||
'profile': 'CentOS-5',
|
},
|
||||||
'provider': 'my-aws-config',
|
{
|
||||||
'securitygroup': ['default'],
|
"deploy": False,
|
||||||
'size': 't1.micro',
|
"image": "ami-09b61d60",
|
||||||
'ssh_username': 'ec2_user'},
|
"profile": "CentOS-5",
|
||||||
{'deploy': False,
|
"provider": "my-aws-config",
|
||||||
'image': 'ami-54cf5c3d',
|
"securitygroup": ["default"],
|
||||||
'profile': 'Amazon-Linux-AMI-2012.09-64bit',
|
"size": "t1.micro",
|
||||||
'provider': 'my-ec2-config',
|
"ssh_username": "ec2_user",
|
||||||
'securitygroup': ['default'],
|
},
|
||||||
'size': 't1.micro',
|
{
|
||||||
'ssh_username': 'ec2_user'},
|
"deploy": False,
|
||||||
{'deploy': False,
|
"image": "ami-54cf5c3d",
|
||||||
'profile': 'development-instances',
|
"profile": "Amazon-Linux-AMI-2012.09-64bit",
|
||||||
'provider': 'my-ec2-config',
|
"provider": "my-ec2-config",
|
||||||
'securitygroup': ['default'],
|
"securitygroup": ["default"],
|
||||||
'size': 't1.micro',
|
"size": "t1.micro",
|
||||||
'ssh_username': 'ec2_user'}]
|
"ssh_username": "ec2_user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deploy": False,
|
||||||
|
"profile": "development-instances",
|
||||||
|
"provider": "my-ec2-config",
|
||||||
|
"securitygroup": ["default"],
|
||||||
|
"size": "t1.micro",
|
||||||
|
"ssh_username": "ec2_user",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
Pretty cool right?
|
Pretty cool right?
|
||||||
|
|
||||||
|
@ -642,33 +652,36 @@ data:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'providers': {
|
"providers": {
|
||||||
'my-develop-envs': [
|
"my-develop-envs": [
|
||||||
{'availability_zone': 'ap-southeast-1b',
|
{
|
||||||
'id': 'HJGRYCILJLKJYG',
|
"availability_zone": "ap-southeast-1b",
|
||||||
'key': 'kdjgfsgm;woormgl/aserigjksjdhasdfgn',
|
"id": "HJGRYCILJLKJYG",
|
||||||
'keyname': 'test',
|
"key": "kdjgfsgm;woormgl/aserigjksjdhasdfgn",
|
||||||
'location': 'ap-southeast-1',
|
"keyname": "test",
|
||||||
'private_key': '/root/test.pem',
|
"location": "ap-southeast-1",
|
||||||
'driver': 'aws',
|
"private_key": "/root/test.pem",
|
||||||
'securitygroup': 'quick-start'
|
"driver": "aws",
|
||||||
|
"securitygroup": "quick-start",
|
||||||
},
|
},
|
||||||
{'location': 'Raleigh',
|
{
|
||||||
'password': 'mypass',
|
"location": "Raleigh",
|
||||||
'driver': 'ibmsce',
|
"password": "mypass",
|
||||||
'ssh_key_file': '/etc/salt/ibm/mykey.pem',
|
"driver": "ibmsce",
|
||||||
'ssh_key_name': 'mykey',
|
"ssh_key_file": "/etc/salt/ibm/mykey.pem",
|
||||||
'user': 'myuser@mycorp.com'
|
"ssh_key_name": "mykey",
|
||||||
|
"user": "myuser@mycorp.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"my-productions-envs": [
|
||||||
|
{
|
||||||
|
"availability_zone": "us-east-1",
|
||||||
|
"location": "us-east-1",
|
||||||
|
"password": "mypass",
|
||||||
|
"driver": "ibmsce",
|
||||||
|
"ssh_key_file": "/etc/salt/ibm/mykey.pem",
|
||||||
|
"ssh_key_name": "mykey",
|
||||||
|
"user": "my-production-user@mycorp.com",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'my-productions-envs': [
|
|
||||||
{'availability_zone': 'us-east-1',
|
|
||||||
'location': 'us-east-1',
|
|
||||||
'password': 'mypass',
|
|
||||||
'driver': 'ibmsce',
|
|
||||||
'ssh_key_file': '/etc/salt/ibm/mykey.pem',
|
|
||||||
'ssh_key_name': 'mykey',
|
|
||||||
'user': 'my-production-user@mycorp.com'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,7 @@ using the ``ec2-config`` provider, the payload for this tag would look like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'name': 'web1',
|
{"name": "web1", "profile": "ec2-centos", "provider": "ec2-config:ec2"}
|
||||||
'profile': 'ec2-centos',
|
|
||||||
'provider': 'ec2-config:ec2'}
|
|
||||||
|
|
||||||
Available Events
|
Available Events
|
||||||
================
|
================
|
||||||
|
|
|
@ -309,7 +309,8 @@ the master or a minion), create a client object and issue a command against it:
|
||||||
|
|
||||||
import salt.cloud
|
import salt.cloud
|
||||||
import pprint
|
import pprint
|
||||||
client = salt.cloud.CloudClient('/etc/salt/cloud')
|
|
||||||
|
client = salt.cloud.CloudClient("/etc/salt/cloud")
|
||||||
nodes = client.query()
|
nodes = client.query()
|
||||||
pprint.pprint(nodes)
|
pprint.pprint(nodes)
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ code and can contain special formatting. For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def my_function(value):
|
def my_function(value):
|
||||||
'''
|
"""
|
||||||
Upper-case the given value
|
Upper-case the given value
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
@ -110,7 +110,7 @@ code and can contain special formatting. For example:
|
||||||
|
|
||||||
:param value: a string
|
:param value: a string
|
||||||
:return: a copy of ``value`` that has been upper-cased
|
:return: a copy of ``value`` that has been upper-cased
|
||||||
'''
|
"""
|
||||||
return value.upper()
|
return value.upper()
|
||||||
|
|
||||||
Specify a release for additions or changes
|
Specify a release for additions or changes
|
||||||
|
@ -122,13 +122,13 @@ denotes what Salt release will be affected. For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def my_function(value):
|
def my_function(value):
|
||||||
'''
|
"""
|
||||||
Upper-case the given value
|
Upper-case the given value
|
||||||
|
|
||||||
.. versionadded:: 2014.7.0
|
.. versionadded:: 2014.7.0
|
||||||
|
|
||||||
<...snip...>
|
<...snip...>
|
||||||
'''
|
"""
|
||||||
return value.upper()
|
return value.upper()
|
||||||
|
|
||||||
For changes to a function:
|
For changes to a function:
|
||||||
|
@ -136,14 +136,14 @@ For changes to a function:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def my_function(value, strip=False):
|
def my_function(value, strip=False):
|
||||||
'''
|
"""
|
||||||
Upper-case the given value
|
Upper-case the given value
|
||||||
|
|
||||||
.. versionchanged:: 2016.3.0
|
.. versionchanged:: 2016.3.0
|
||||||
Added a flag to also strip whitespace from the string.
|
Added a flag to also strip whitespace from the string.
|
||||||
|
|
||||||
<...snip...>
|
<...snip...>
|
||||||
'''
|
"""
|
||||||
if strip:
|
if strip:
|
||||||
return value.upper().strip()
|
return value.upper().strip()
|
||||||
return value.upper()
|
return value.upper()
|
||||||
|
|
|
@ -642,32 +642,29 @@ example is a state tree of two sls files, one simple and one complicated.
|
||||||
# This example has the minion id in the form 'web-03-dev'.
|
# This example has the minion id in the form 'web-03-dev'.
|
||||||
# Easily access the grains dictionary:
|
# Easily access the grains dictionary:
|
||||||
try:
|
try:
|
||||||
app, instance_number, environment = __grains__['id'].split('-')
|
app, instance_number, environment = __grains__["id"].split("-")
|
||||||
instance_number = int(instance_number)
|
instance_number = int(instance_number)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
app, instance_number, environment = ['Unknown', 0, 'dev']
|
app, instance_number, environment = ["Unknown", 0, "dev"]
|
||||||
|
|
||||||
list_of_roles.add(app)
|
list_of_roles.add(app)
|
||||||
|
|
||||||
if app == 'web' and environment == 'dev':
|
if app == "web" and environment == "dev":
|
||||||
list_of_roles.add('primary')
|
list_of_roles.add("primary")
|
||||||
list_of_roles.add('secondary')
|
list_of_roles.add("secondary")
|
||||||
elif app == 'web' and environment == 'staging':
|
elif app == "web" and environment == "staging":
|
||||||
if instance_number == 0:
|
if instance_number == 0:
|
||||||
list_of_roles.add('primary')
|
list_of_roles.add("primary")
|
||||||
else:
|
else:
|
||||||
list_of_roles.add('secondary')
|
list_of_roles.add("secondary")
|
||||||
|
|
||||||
# Easily cross-call Salt execution modules:
|
# Easily cross-call Salt execution modules:
|
||||||
if __salt__['myutils.query_valid_ec2_instance']():
|
if __salt__["myutils.query_valid_ec2_instance"]():
|
||||||
list_of_roles.add('is_ec2_instance')
|
list_of_roles.add("is_ec2_instance")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'set_roles_grains': {
|
"set_roles_grains": {
|
||||||
'grains.present': [
|
"grains.present": [{"name": "roles"}, {"value": list(list_of_roles)},],
|
||||||
{'name': 'roles'},
|
|
||||||
{'value': list(list_of_roles)},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,8 +68,8 @@ All strings which require formatting should use the `.format` string method:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
data = 'some text'
|
data = "some text"
|
||||||
more = '{0} and then some'.format(data)
|
more = "{0} and then some".format(data)
|
||||||
|
|
||||||
Make sure to use indices or identifiers in the format brackets, since empty
|
Make sure to use indices or identifiers in the format brackets, since empty
|
||||||
brackets are not supported by python 2.6.
|
brackets are not supported by python 2.6.
|
||||||
|
@ -84,15 +84,15 @@ When adding a new function or state, where possible try to use a
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def new_func(msg=''):
|
def new_func(msg=""):
|
||||||
'''
|
"""
|
||||||
.. versionadded:: 0.16.0
|
.. versionadded:: 0.16.0
|
||||||
|
|
||||||
Prints what was passed to the function.
|
Prints what was passed to the function.
|
||||||
|
|
||||||
msg : None
|
msg : None
|
||||||
The string to be printed.
|
The string to be printed.
|
||||||
'''
|
"""
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
If you are uncertain what version should be used, either consult a core
|
If you are uncertain what version should be used, either consult a core
|
||||||
|
@ -109,8 +109,8 @@ significantly, the ``versionchanged`` directive can be used to clarify this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def new_func(msg='', signature=''):
|
def new_func(msg="", signature=""):
|
||||||
'''
|
"""
|
||||||
.. versionadded:: 0.16.0
|
.. versionadded:: 0.16.0
|
||||||
|
|
||||||
Prints what was passed to the function.
|
Prints what was passed to the function.
|
||||||
|
@ -124,8 +124,8 @@ significantly, the ``versionchanged`` directive can be used to clarify this:
|
||||||
An optional signature.
|
An optional signature.
|
||||||
|
|
||||||
.. versionadded 0.17.0
|
.. versionadded 0.17.0
|
||||||
'''
|
"""
|
||||||
print('Greetings! {0}\n\n{1}'.format(msg, signature))
|
print("Greetings! {0}\n\n{1}".format(msg, signature))
|
||||||
|
|
||||||
|
|
||||||
Dictionaries
|
Dictionaries
|
||||||
|
@ -154,8 +154,9 @@ To say this more directly with an example, this is `GOOD`:
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def minion_path():
|
def minion_path():
|
||||||
path = os.path.join(self.opts['cachedir'], 'minions')
|
path = os.path.join(self.opts["cachedir"], "minions")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
This on the other hand is `DISCOURAGED`:
|
This on the other hand is `DISCOURAGED`:
|
||||||
|
@ -164,8 +165,9 @@ This on the other hand is `DISCOURAGED`:
|
||||||
|
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
||||||
|
|
||||||
def minion_path():
|
def minion_path():
|
||||||
path = join(self.opts['cachedir'], 'minions')
|
path = join(self.opts["cachedir"], "minions")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
The time when this is changed is for importing exceptions, generally directly
|
The time when this is changed is for importing exceptions, generally directly
|
||||||
|
|
|
@ -45,10 +45,10 @@ Consider the following example:
|
||||||
def some_function(bar=False, foo=None):
|
def some_function(bar=False, foo=None):
|
||||||
if foo is not None:
|
if foo is not None:
|
||||||
salt.utils.versions.warn_until(
|
salt.utils.versions.warn_until(
|
||||||
'Aluminum',
|
"Aluminum",
|
||||||
'The \'foo\' argument has been deprecated and its '
|
"The 'foo' argument has been deprecated and its "
|
||||||
'functionality removed, as such, its usage is no longer '
|
"functionality removed, as such, its usage is no longer "
|
||||||
'required.'
|
"required.",
|
||||||
)
|
)
|
||||||
|
|
||||||
Development begins on the ``Aluminum`` release when the ``Magnesium`` branch is
|
Development begins on the ``Aluminum`` release when the ``Magnesium`` branch is
|
||||||
|
|
|
@ -79,8 +79,8 @@ Example file in the template directory
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
print('Hello {{module_name}}')
|
print("Hello {{module_name}}")
|
||||||
__virtual__ = '{{__virtual_name__}}'
|
__virtual__ = "{{__virtual_name__}}"
|
||||||
|
|
||||||
Default context properties
|
Default context properties
|
||||||
**************************
|
**************************
|
||||||
|
|
|
@ -30,8 +30,10 @@ the debugger should be started:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
test = 'test123'
|
test = "test123"
|
||||||
import IPython; IPython.embed_kernel()
|
import IPython
|
||||||
|
|
||||||
|
IPython.embed_kernel()
|
||||||
|
|
||||||
After running a Salt command that hits that line, the following will show up in
|
After running a Salt command that hits that line, the following will show up in
|
||||||
the log file:
|
the log file:
|
||||||
|
@ -146,8 +148,8 @@ functions to be called as they have been set up by the salt loader.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['cmd.run']('fdisk -l')
|
__salt__["cmd.run"]("fdisk -l")
|
||||||
__salt__['network.ip_addrs']()
|
__salt__["network.ip_addrs"]()
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -205,8 +207,8 @@ each file. Here is an example from salt/modules/cp.py:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if not 'cp.fileclient' in __context__:
|
if not "cp.fileclient" in __context__:
|
||||||
__context__['cp.fileclient'] = salt.fileclient.get_file_client(__opts__)
|
__context__["cp.fileclient"] = salt.fileclient.get_file_client(__opts__)
|
||||||
|
|
||||||
|
|
||||||
.. note:: Because __context__ may or may not have been destroyed, always be
|
.. note:: Because __context__ may or may not have been destroyed, always be
|
||||||
|
|
|
@ -65,6 +65,7 @@ import errors and set a flag that the ``__virtual__`` function can use later.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import weird_thing
|
import weird_thing
|
||||||
|
|
||||||
EXAMPLE_A_LOADED = True
|
EXAMPLE_A_LOADED = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
EXAMPLE_A_LOADED = False
|
EXAMPLE_A_LOADED = False
|
||||||
|
@ -80,7 +81,7 @@ things ``modulename.option``.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__opts__ = { 'example_a.someconfig': 137 }
|
__opts__ = {"example_a.someconfig": 137}
|
||||||
|
|
||||||
|
|
||||||
Initialization
|
Initialization
|
||||||
|
@ -91,8 +92,9 @@ signature:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def __init__( __opts__ ):
|
def __init__(__opts__):
|
||||||
# Do init work here
|
# Do init work here
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
**Note**: The ``__init__`` function is ran every time a particular minion causes
|
**Note**: The ``__init__`` function is ran every time a particular minion causes
|
||||||
|
@ -127,7 +129,8 @@ installed.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# This external pillar will be known as `something_else`
|
# This external pillar will be known as `something_else`
|
||||||
__virtualname__ = 'something_else'
|
__virtualname__ = "something_else"
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
if EXAMPLE_A_LOADED:
|
if EXAMPLE_A_LOADED:
|
||||||
|
@ -151,9 +154,9 @@ Using our example above:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ext_pillar( id, pillar, 'some argument' ) # example_a
|
ext_pillar(id, pillar, "some argument") # example_a
|
||||||
ext_pillar( id, pillar, 'argumentA', 'argumentB' ) # example_b
|
ext_pillar(id, pillar, "argumentA", "argumentB") # example_b
|
||||||
ext_pillar( id, pillar, keyA='valueA', keyB='valueB' ) # example_c
|
ext_pillar(id, pillar, keyA="valueA", keyB="valueB") # example_c
|
||||||
|
|
||||||
|
|
||||||
In the ``example_a`` case, ``pillar`` will contain the items from the
|
In the ``example_a`` case, ``pillar`` will contain the items from the
|
||||||
|
@ -168,11 +171,11 @@ is called once for each minion that fetches its pillar data.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def ext_pillar( minion_id, pillar, *args, **kwargs ):
|
def ext_pillar(minion_id, pillar, *args, **kwargs):
|
||||||
|
|
||||||
my_pillar = {'external_pillar': {}}
|
my_pillar = {"external_pillar": {}}
|
||||||
|
|
||||||
my_pillar['external_pillar'] = get_external_pillar_dictionary()
|
my_pillar["external_pillar"] = get_external_pillar_dictionary()
|
||||||
|
|
||||||
return my_pillar
|
return my_pillar
|
||||||
|
|
||||||
|
|
|
@ -101,11 +101,7 @@ This is done via setuptools entry points:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
# ...
|
# ...
|
||||||
entry_points={
|
entry_points={"salt.loader": ["module_dirs=spirofs.loader:module"]},
|
||||||
'salt.loader': [
|
|
||||||
'module_dirs=spirofs.loader:module',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
# ...
|
# ...
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ so:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg_resource.add_pkg'](ret, name, version)
|
__salt__["pkg_resource.add_pkg"](ret, name, version)
|
||||||
|
|
||||||
The last thing that should be done before returning is to execute
|
The last thing that should be done before returning is to execute
|
||||||
:mod:`pkg_resource.sort_pkglist <salt.modules.pkg_resource.sort_pkglist>`. This
|
:mod:`pkg_resource.sort_pkglist <salt.modules.pkg_resource.sort_pkglist>`. This
|
||||||
|
@ -32,7 +32,7 @@ future versions of Salt.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg_resource.sort_pkglist'](ret)
|
__salt__["pkg_resource.sort_pkglist"](ret)
|
||||||
|
|
||||||
|
|
||||||
``list_pkgs`` returns a dictionary of installed packages, with the keys being
|
``list_pkgs`` returns a dictionary of installed packages, with the keys being
|
||||||
|
@ -41,8 +41,7 @@ data:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'foo': '1.2.3-4',
|
{"foo": "1.2.3-4", "bar": "5.6.7-8"}
|
||||||
'bar': '5.6.7-8'}
|
|
||||||
|
|
||||||
|
|
||||||
latest_version
|
latest_version
|
||||||
|
@ -83,7 +82,7 @@ Deprecated and destined to be removed. For now, should just do the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
return __salt__['pkg.latest_version'](name) != ''
|
return __salt__["pkg.latest_version"](name) != ""
|
||||||
|
|
||||||
|
|
||||||
install
|
install
|
||||||
|
@ -106,9 +105,7 @@ system.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](name,
|
pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](name, pkgs, sources)
|
||||||
pkgs,
|
|
||||||
sources)
|
|
||||||
|
|
||||||
Two values will be returned to the :strong:`install` function. The first of
|
Two values will be returned to the :strong:`install` function. The first of
|
||||||
them will be a dictionary. The keys of this dictionary will be package names,
|
them will be a dictionary. The keys of this dictionary will be package names,
|
||||||
|
@ -164,8 +161,8 @@ changed.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
basedir = '/etc/yum.repos.d'
|
basedir = "/etc/yum.repos.d"
|
||||||
__salt__['pkg.list_repos'](basedir)
|
__salt__["pkg.list_repos"](basedir)
|
||||||
|
|
||||||
list_repos
|
list_repos
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
@ -173,7 +170,7 @@ Lists the repositories that are currently configured on this system.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg.list_repos']()
|
__salt__["pkg.list_repos"]()
|
||||||
|
|
||||||
Returns a dictionary, in the following format:
|
Returns a dictionary, in the following format:
|
||||||
|
|
||||||
|
@ -190,7 +187,7 @@ Displays all local configuration for a specific repository.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg.get_repo'](repo='myrepo')
|
__salt__["pkg.get_repo"](repo="myrepo")
|
||||||
|
|
||||||
The information is formatted in much the same way as list_repos, but is
|
The information is formatted in much the same way as list_repos, but is
|
||||||
specific to only one repo.
|
specific to only one repo.
|
||||||
|
@ -211,7 +208,7 @@ success.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg.del_repo'](repo='myrepo')
|
__salt__["pkg.del_repo"](repo="myrepo")
|
||||||
|
|
||||||
mod_repo
|
mod_repo
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
@ -224,7 +221,7 @@ refer to the documentation for your specific repo manager for specifics.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__salt__['pkg.mod_repo'](repo='myrepo', url='http://myurl.com/repo')
|
__salt__["pkg.mod_repo"](repo="myrepo", url="http://myurl.com/repo")
|
||||||
|
|
||||||
|
|
||||||
Low-Package Functions
|
Low-Package Functions
|
||||||
|
@ -251,14 +248,13 @@ packages will be listed.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
installed = __salt__['lowpkg.list_pkgs']('foo', 'bar')
|
installed = __salt__["lowpkg.list_pkgs"]("foo", "bar")
|
||||||
|
|
||||||
Example output:
|
Example output:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'foo': '1.2.3-4',
|
{"foo": "1.2.3-4", "bar": "5.6.7-8"}
|
||||||
'bar': '5.6.7-8'}
|
|
||||||
|
|
||||||
verify
|
verify
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
@ -269,14 +265,18 @@ included.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
installed = __salt__['lowpkg.verify']('httpd')
|
installed = __salt__["lowpkg.verify"]("httpd")
|
||||||
|
|
||||||
Example output:
|
Example output:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'/etc/httpd/conf/httpd.conf': {'mismatch': ['size', 'md5sum', 'mtime'],
|
{
|
||||||
'type': 'config'}}
|
"/etc/httpd/conf/httpd.conf": {
|
||||||
|
"mismatch": ["size", "md5sum", "mtime"],
|
||||||
|
"type": "config",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
file_list
|
file_list
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
@ -285,7 +285,7 @@ specified, then all files for all known packages are returned.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
installed = __salt__['lowpkg.file_list']('httpd', 'apache')
|
installed = __salt__["lowpkg.file_list"]("httpd", "apache")
|
||||||
|
|
||||||
This function does not return which files belong to which packages; all files
|
This function does not return which files belong to which packages; all files
|
||||||
are returned as one giant list (hence the `file_list` function name. However,
|
are returned as one giant list (hence the `file_list` function name. However,
|
||||||
|
@ -294,11 +294,10 @@ any errors to the user in a sane manner.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'errors': ['package apache is not installed'],
|
{
|
||||||
'files': ['/etc/httpd',
|
"errors": ["package apache is not installed"],
|
||||||
'/etc/httpd/conf',
|
"files": ["/etc/httpd", "/etc/httpd/conf", "/etc/httpd/conf.d", "...SNIP..."],
|
||||||
'/etc/httpd/conf.d',
|
}
|
||||||
'...SNIP...']}
|
|
||||||
|
|
||||||
file_dict
|
file_dict
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
@ -307,17 +306,21 @@ specified, then all files for all known packages are returned.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
installed = __salt__['lowpkg.file_dict']('httpd', 'apache', 'kernel')
|
installed = __salt__["lowpkg.file_dict"]("httpd", "apache", "kernel")
|
||||||
|
|
||||||
Unlike `file_list`, this function will break down which files belong to which
|
Unlike `file_list`, this function will break down which files belong to which
|
||||||
packages. It will also return errors in the same manner as `file_list`.
|
packages. It will also return errors in the same manner as `file_list`.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'errors': ['package apache is not installed'],
|
{
|
||||||
'packages': {'httpd': ['/etc/httpd',
|
"errors": ["package apache is not installed"],
|
||||||
'/etc/httpd/conf',
|
"packages": {
|
||||||
'...SNIP...'],
|
"httpd": ["/etc/httpd", "/etc/httpd/conf", "...SNIP..."],
|
||||||
'kernel': ['/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac',
|
"kernel": [
|
||||||
'/boot/System.map-2.6.32-279.el6.x86_64',
|
"/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac",
|
||||||
'...SNIP...']}}
|
"/boot/System.map-2.6.32-279.el6.x86_64",
|
||||||
|
"...SNIP...",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -358,6 +358,7 @@ the actual testing, such as functions containing assertions, must start with
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_user_present(self):
|
def test_user_present(self):
|
||||||
|
...
|
||||||
|
|
||||||
When functions in test files are not prepended with ``test_``, the function
|
When functions in test files are not prepended with ``test_``, the function
|
||||||
acts as a normal, helper function and is not run as a test by the test suite.
|
acts as a normal, helper function and is not run as a test by the test suite.
|
||||||
|
|
|
@ -89,16 +89,12 @@ kernel is not present, then the test should be skipped.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
'''
|
"""
|
||||||
Sets up test requirements
|
Sets up test requirements
|
||||||
'''
|
"""
|
||||||
os_grain = self.run_function('grains.item', ['kernel'])
|
os_grain = self.run_function("grains.item", ["kernel"])
|
||||||
if os_grain['kernel'] not in 'Darwin':
|
if os_grain["kernel"] not in "Darwin":
|
||||||
self.skipTest(
|
self.skipTest("Test not applicable to '{kernel}' kernel".format(**os_grain))
|
||||||
'Test not applicable to \'{kernel}\' kernel'.format(
|
|
||||||
**os_grain
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
The ``setUp`` function can be used for many things. The above code snippet is
|
The ``setUp`` function can be used for many things. The above code snippet is
|
||||||
only one example. Another example might be to ensure that a particular setting
|
only one example. Another example might be to ensure that a particular setting
|
||||||
|
@ -272,24 +268,25 @@ Now the workhorse method ``run_function`` can be used to test a module:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tests.integration as integration
|
from tests.support.case import ModuleCase
|
||||||
|
|
||||||
|
|
||||||
class TestModuleTest(integration.ModuleCase):
|
class TestModuleTest(ModuleCase):
|
||||||
'''
|
"""
|
||||||
Validate the test module
|
Validate the test module
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def test_ping(self):
|
def test_ping(self):
|
||||||
'''
|
"""
|
||||||
test.ping
|
test.ping
|
||||||
'''
|
"""
|
||||||
self.assertTrue(self.run_function('test.ping'))
|
self.assertTrue(self.run_function("test.ping"))
|
||||||
|
|
||||||
def test_echo(self):
|
def test_echo(self):
|
||||||
'''
|
"""
|
||||||
test.echo
|
test.echo
|
||||||
'''
|
"""
|
||||||
self.assertEqual(self.run_function('test.echo', ['text']), 'text')
|
self.assertEqual(self.run_function("test.echo", ["text"]), "text")
|
||||||
|
|
||||||
The fist example illustrates the testing master issuing a ``test.ping`` call
|
The fist example illustrates the testing master issuing a ``test.ping`` call
|
||||||
to a testing minion. The test asserts that the minion returned with a ``True``
|
to a testing minion. The test asserts that the minion returned with a ``True``
|
||||||
|
@ -312,26 +309,29 @@ Validating the shell commands can be done via shell tests:
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import tests.integration as integration
|
from tests.support.case import ShellCase
|
||||||
|
|
||||||
class KeyTest(integration.ShellCase):
|
|
||||||
'''
|
class KeyTest(ShellCase):
|
||||||
|
"""
|
||||||
Test salt-key script
|
Test salt-key script
|
||||||
'''
|
"""
|
||||||
|
|
||||||
_call_binary_ = 'salt-key'
|
_call_binary_ = "salt-key"
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
'''
|
"""
|
||||||
test salt-key -L
|
test salt-key -L
|
||||||
'''
|
"""
|
||||||
data = self.run_key('-L')
|
data = self.run_key("-L")
|
||||||
expect = [
|
expect = [
|
||||||
'Unaccepted Keys:',
|
"Unaccepted Keys:",
|
||||||
'Accepted Keys:',
|
"Accepted Keys:",
|
||||||
'minion',
|
"minion",
|
||||||
'sub_minion',
|
"sub_minion",
|
||||||
'Rejected:', '']
|
"Rejected:",
|
||||||
|
"",
|
||||||
|
]
|
||||||
self.assertEqual(data, expect)
|
self.assertEqual(data, expect)
|
||||||
|
|
||||||
This example verifies that the ``salt-key`` command executes and returns as
|
This example verifies that the ``salt-key`` command executes and returns as
|
||||||
|
@ -345,9 +345,9 @@ Testing salt-ssh functionality can be done using the SSHCase test class:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import tests.integration as integration
|
from tests.support.case import SSHCase
|
||||||
|
|
||||||
class SSHGrainsTest(integration.SSHCase):
|
class SSHGrainsTest(SSHCase):
|
||||||
'''
|
'''
|
||||||
Test salt-ssh grains functionality
|
Test salt-ssh grains functionality
|
||||||
Depend on proper environment set by integration.SSHCase class
|
Depend on proper environment set by integration.SSHCase class
|
||||||
|
@ -370,20 +370,23 @@ on a minion event bus.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import tests.integration as integration
|
|
||||||
import salt.utils.event
|
import salt.utils.event
|
||||||
|
from tests.support.mixins import SaltEventAssertsMixin
|
||||||
|
|
||||||
class TestEvent(integration.SaltEventAssertsMixin):
|
|
||||||
'''
|
class TestEvent(SaltEventAssertsMixin):
|
||||||
|
"""
|
||||||
Example test of firing an event and receiving it
|
Example test of firing an event and receiving it
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def test_event(self):
|
def test_event(self):
|
||||||
e = salt.utils.event.get_event('minion', sock_dir=self.minion_opts['sock_dir'], opts=self.minion_opts)
|
e = salt.utils.event.get_event(
|
||||||
|
"minion", sock_dir=self.minion_opts["sock_dir"], opts=self.minion_opts
|
||||||
|
)
|
||||||
|
|
||||||
e.fire_event({'a': 'b'}, '/test_event')
|
e.fire_event({"a": "b"}, "/test_event")
|
||||||
|
|
||||||
self.assertMinionEventReceived({'a': 'b'})
|
self.assertMinionEventReceived({"a": "b"})
|
||||||
|
|
||||||
|
|
||||||
Syndic Example via SyndicCase
|
Syndic Example via SyndicCase
|
||||||
|
@ -393,17 +396,19 @@ Testing Salt's Syndic can be done via the SyndicCase test class:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import tests.integration as integration
|
from tests.support.case import SyndicCase
|
||||||
|
|
||||||
class TestSyndic(integration.SyndicCase):
|
|
||||||
'''
|
class TestSyndic(SyndicCase):
|
||||||
|
"""
|
||||||
Validate the syndic interface by testing the test module
|
Validate the syndic interface by testing the test module
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def test_ping(self):
|
def test_ping(self):
|
||||||
'''
|
"""
|
||||||
test.ping
|
test.ping
|
||||||
'''
|
"""
|
||||||
self.assertTrue(self.run_function('test.ping'))
|
self.assertTrue(self.run_function("test.ping"))
|
||||||
|
|
||||||
This example verifies that a ``test.ping`` command is issued from the testing
|
This example verifies that a ``test.ping`` command is issued from the testing
|
||||||
master, is passed through to the testing syndic, down to the minion, and back
|
master, is passed through to the testing syndic, down to the minion, and back
|
||||||
|
@ -446,16 +451,16 @@ to test states:
|
||||||
# Import salt libs
|
# Import salt libs
|
||||||
import salt.utils.files
|
import salt.utils.files
|
||||||
|
|
||||||
HFILE = os.path.join(TMP, 'hosts')
|
HFILE = os.path.join(TMP, "hosts")
|
||||||
|
|
||||||
|
|
||||||
class HostTest(ModuleCase, SaltReturnAssertsMixin):
|
class HostTest(ModuleCase, SaltReturnAssertsMixin):
|
||||||
'''
|
"""
|
||||||
Validate the host state
|
Validate the host state
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
shutil.copyfile(os.path.join(FILES, 'hosts'), HFILE)
|
shutil.copyfile(os.path.join(FILES, "hosts"), HFILE)
|
||||||
super(HostTest, self).setUp()
|
super(HostTest, self).setUp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -464,18 +469,18 @@ to test states:
|
||||||
super(HostTest, self).tearDown()
|
super(HostTest, self).tearDown()
|
||||||
|
|
||||||
def test_present(self):
|
def test_present(self):
|
||||||
'''
|
"""
|
||||||
host.present
|
host.present
|
||||||
'''
|
"""
|
||||||
name = 'spam.bacon'
|
name = "spam.bacon"
|
||||||
ip = '10.10.10.10'
|
ip = "10.10.10.10"
|
||||||
ret = self.run_state('host.present', name=name, ip=ip)
|
ret = self.run_state("host.present", name=name, ip=ip)
|
||||||
self.assertSaltTrueReturn(ret)
|
self.assertSaltTrueReturn(ret)
|
||||||
with salt.utils.files.fopen(HFILE) as fp_:
|
with salt.utils.files.fopen(HFILE) as fp_:
|
||||||
output = fp_.read()
|
output = fp_.read()
|
||||||
self.assertIn('{0}\t\t{1}'.format(ip, name), output)
|
self.assertIn("{0}\t\t{1}".format(ip, name), output)
|
||||||
|
|
||||||
To access the integration files, a variable named ``FILES`` points to the
|
To access the integration files, a variable named ``FILES`` points to the
|
||||||
``tests/integration/files`` directory. This is where the referenced
|
``tests/integration/files`` directory. This is where the referenced
|
||||||
``host.present`` sls file resides.
|
``host.present`` sls file resides.
|
||||||
|
|
||||||
|
@ -507,24 +512,25 @@ the test method:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import tests.integration as integration
|
from tests.support.case import ModuleCase
|
||||||
from tests.support.helpers import destructiveTest, skip_if_not_root
|
from tests.support.helpers import destructiveTest, skip_if_not_root
|
||||||
|
|
||||||
class DestructiveExampleModuleTest(integration.ModuleCase):
|
|
||||||
'''
|
class DestructiveExampleModuleTest(ModuleCase):
|
||||||
|
"""
|
||||||
Demonstrate a destructive test
|
Demonstrate a destructive test
|
||||||
'''
|
"""
|
||||||
|
|
||||||
@destructiveTest
|
@destructiveTest
|
||||||
@skip_if_not_root
|
@skip_if_not_root
|
||||||
def test_user_not_present(self):
|
def test_user_not_present(self):
|
||||||
'''
|
"""
|
||||||
This is a DESTRUCTIVE TEST it creates a new user on the minion.
|
This is a DESTRUCTIVE TEST it creates a new user on the minion.
|
||||||
And then destroys that user.
|
And then destroys that user.
|
||||||
'''
|
"""
|
||||||
ret = self.run_state('user.present', name='salt_test')
|
ret = self.run_state("user.present", name="salt_test")
|
||||||
self.assertSaltTrueReturn(ret)
|
self.assertSaltTrueReturn(ret)
|
||||||
ret = self.run_state('user.absent', name='salt_test')
|
ret = self.run_state("user.absent", name="salt_test")
|
||||||
self.assertSaltTrueReturn(ret)
|
self.assertSaltTrueReturn(ret)
|
||||||
|
|
||||||
|
|
||||||
|
@ -634,27 +640,28 @@ the test function:
|
||||||
|
|
||||||
from tests.support.helpers import expensiveTest
|
from tests.support.helpers import expensiveTest
|
||||||
|
|
||||||
|
|
||||||
@expensiveTest
|
@expensiveTest
|
||||||
def test_instance(self):
|
def test_instance(self):
|
||||||
'''
|
"""
|
||||||
Test creating an instance on Linode
|
Test creating an instance on Linode
|
||||||
'''
|
"""
|
||||||
name = 'linode-testing'
|
name = "linode-testing"
|
||||||
|
|
||||||
# create the instance
|
# create the instance
|
||||||
instance = self.run_cloud('-p linode-test {0}'.format(name))
|
instance = self.run_cloud("-p linode-test {0}".format(name))
|
||||||
str = ' {0}'.format(name)
|
str = " {0}".format(name)
|
||||||
|
|
||||||
# check if instance with salt installed returned as expected
|
# check if instance with salt installed returned as expected
|
||||||
try:
|
try:
|
||||||
self.assertIn(str, instance)
|
self.assertIn(str, instance)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
self.run_cloud('-d {0} --assume-yes'.format(name))
|
self.run_cloud("-d {0} --assume-yes".format(name))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# delete the instance
|
# delete the instance
|
||||||
delete = self.run_cloud('-d {0} --assume-yes'.format(name))
|
delete = self.run_cloud("-d {0} --assume-yes".format(name))
|
||||||
str = ' True'
|
str = " True"
|
||||||
try:
|
try:
|
||||||
self.assertIn(str, delete)
|
self.assertIn(str, delete)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
|
|
@ -113,14 +113,10 @@ to the module being tested one should do:
|
||||||
|
|
||||||
import salt.modules.somemodule as somemodule
|
import salt.modules.somemodule as somemodule
|
||||||
|
|
||||||
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
|
|
||||||
|
|
||||||
|
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
|
||||||
def setup_loader_modules(self):
|
def setup_loader_modules(self):
|
||||||
return {
|
return {somemodule: {"__opts__": {"test": True}}}
|
||||||
somemodule: {
|
|
||||||
'__opts__': {'test': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Consider this more extensive example from
|
Consider this more extensive example from
|
||||||
``tests/unit/modules/test_libcloud_dns.py``:
|
``tests/unit/modules/test_libcloud_dns.py``:
|
||||||
|
@ -133,10 +129,7 @@ Consider this more extensive example from
|
||||||
# Import Salt Testing Libs
|
# Import Salt Testing Libs
|
||||||
from tests.support.mixins import LoaderModuleMockMixin
|
from tests.support.mixins import LoaderModuleMockMixin
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, MagicMock
|
||||||
patch,
|
|
||||||
MagicMock,
|
|
||||||
)
|
|
||||||
import salt.modules.libcloud_dns as libcloud_dns
|
import salt.modules.libcloud_dns as libcloud_dns
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,23 +142,18 @@ Consider this more extensive example from
|
||||||
return MockDNSDriver()
|
return MockDNSDriver()
|
||||||
|
|
||||||
|
|
||||||
@patch('salt.modules.libcloud_dns._get_driver',
|
@patch("salt.modules.libcloud_dns._get_driver", MagicMock(return_value=MockDNSDriver()))
|
||||||
MagicMock(return_value=MockDNSDriver()))
|
|
||||||
class LibcloudDnsModuleTestCase(TestCase, LoaderModuleMockMixin):
|
class LibcloudDnsModuleTestCase(TestCase, LoaderModuleMockMixin):
|
||||||
|
|
||||||
def setup_loader_modules(self):
|
def setup_loader_modules(self):
|
||||||
module_globals = {
|
module_globals = {
|
||||||
'__salt__': {
|
"__salt__": {
|
||||||
'config.option': MagicMock(return_value={
|
"config.option": MagicMock(
|
||||||
'test': {
|
return_value={"test": {"driver": "test", "key": "2orgk34kgk34g"}}
|
||||||
'driver': 'test',
|
)
|
||||||
'key': '2orgk34kgk34g'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if libcloud_dns.HAS_LIBCLOUD is False:
|
if libcloud_dns.HAS_LIBCLOUD is False:
|
||||||
module_globals['sys.modules'] = {'libcloud': MagicMock()}
|
module_globals["sys.modules"] = {"libcloud": MagicMock()}
|
||||||
|
|
||||||
return {libcloud_dns: module_globals}
|
return {libcloud_dns: module_globals}
|
||||||
|
|
||||||
|
@ -193,18 +181,15 @@ a separate implementation which has additional functionality.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, mock_open
|
||||||
patch
|
|
||||||
mock_open,
|
|
||||||
)
|
|
||||||
|
|
||||||
import salt.modules.mymod as mymod
|
import salt.modules.mymod as mymod
|
||||||
|
|
||||||
class MyAwesomeTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class MyAwesomeTestCase(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n')
|
fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
|
||||||
with patch('salt.utils.files.fopen', fopen_mock):
|
with patch("salt.utils.files.fopen", fopen_mock):
|
||||||
result = mymod.myfunc()
|
result = mymod.myfunc()
|
||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
@ -245,30 +230,31 @@ those cases, you can pass ``read_data`` as a dictionary:
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, mock_open
|
||||||
patch
|
|
||||||
mock_open,
|
|
||||||
)
|
|
||||||
|
|
||||||
import salt.modules.mymod as mymod
|
import salt.modules.mymod as mymod
|
||||||
|
|
||||||
class MyAwesomeTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class MyAwesomeTestCase(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
contents = {
|
contents = {
|
||||||
'/etc/foo.conf': textwrap.dedent('''\
|
"/etc/foo.conf": textwrap.dedent(
|
||||||
|
"""\
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
'''),
|
"""
|
||||||
'/etc/b*.conf': textwrap.dedent('''\
|
),
|
||||||
|
"/etc/b*.conf": textwrap.dedent(
|
||||||
|
"""\
|
||||||
one
|
one
|
||||||
two
|
two
|
||||||
three
|
three
|
||||||
'''),
|
"""
|
||||||
|
),
|
||||||
}
|
}
|
||||||
fopen_mock = mock_open(read_data=contents)
|
fopen_mock = mock_open(read_data=contents)
|
||||||
with patch('salt.utils.files.fopen', fopen_mock):
|
with patch("salt.utils.files.fopen", fopen_mock):
|
||||||
result = mymod.myfunc()
|
result = mymod.myfunc()
|
||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
@ -284,8 +270,8 @@ below two ``mock_open`` calls would produce identical results:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
mock_open(read_data='foo\n')
|
mock_open(read_data="foo\n")
|
||||||
mock_open(read_data={'*': 'foo\n'})
|
mock_open(read_data={"*": "foo\n"})
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Take care when specifying the ``read_data`` as a dictionary, in cases where
|
Take care when specifying the ``read_data`` as a dictionary, in cases where
|
||||||
|
@ -300,9 +286,9 @@ below two ``mock_open`` calls would produce identical results:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
contents = OrderedDict()
|
contents = OrderedDict()
|
||||||
contents['/etc/bar.conf'] = 'foo\nbar\nbaz\n'
|
contents["/etc/bar.conf"] = "foo\nbar\nbaz\n"
|
||||||
contents['/etc/b*.conf'] = IOError(errno.EACCES, 'Permission denied')
|
contents["/etc/b*.conf"] = IOError(errno.EACCES, "Permission denied")
|
||||||
contents['*'] = 'This is a fallback for files not beginning with "/etc/b"\n'
|
contents["*"] = 'This is a fallback for files not beginning with "/etc/b"\n'
|
||||||
fopen_mock = mock_open(read_data=contents)
|
fopen_mock = mock_open(read_data=contents)
|
||||||
|
|
||||||
Raising Exceptions
|
Raising Exceptions
|
||||||
|
@ -315,19 +301,16 @@ Instead of a string, an exception can also be used as the ``read_data``:
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, mock_open
|
||||||
patch
|
|
||||||
mock_open,
|
|
||||||
)
|
|
||||||
|
|
||||||
import salt.modules.mymod as mymod
|
import salt.modules.mymod as mymod
|
||||||
|
|
||||||
class MyAwesomeTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class MyAwesomeTestCase(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
exc = IOError(errno.EACCES, 'Permission denied')
|
exc = IOError(errno.EACCES, "Permission denied")
|
||||||
fopen_mock = mock_open(read_data=exc)
|
fopen_mock = mock_open(read_data=exc)
|
||||||
with patch('salt.utils.files.fopen', fopen_mock):
|
with patch("salt.utils.files.fopen", fopen_mock):
|
||||||
mymod.myfunc()
|
mymod.myfunc()
|
||||||
|
|
||||||
The above example would raise the specified exception when any file is opened.
|
The above example would raise the specified exception when any file is opened.
|
||||||
|
@ -350,39 +333,42 @@ and produce a mocked filehandle with the specified contents. For example:
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, mock_open
|
||||||
patch
|
|
||||||
mock_open,
|
|
||||||
)
|
|
||||||
|
|
||||||
import salt.modules.mymod as mymod
|
import salt.modules.mymod as mymod
|
||||||
|
|
||||||
class MyAwesomeTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class MyAwesomeTestCase(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
contents = {
|
contents = {
|
||||||
'/etc/foo.conf': [
|
"/etc/foo.conf": [
|
||||||
textwrap.dedent('''\
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
'''),
|
"""
|
||||||
textwrap.dedent('''\
|
),
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
'''),
|
"""
|
||||||
|
),
|
||||||
],
|
],
|
||||||
'/etc/b*.conf': [
|
"/etc/b*.conf": [
|
||||||
IOError(errno.ENOENT, 'No such file or directory'),
|
IOError(errno.ENOENT, "No such file or directory"),
|
||||||
textwrap.dedent('''\
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
one
|
one
|
||||||
two
|
two
|
||||||
three
|
three
|
||||||
'''),
|
"""
|
||||||
|
),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
fopen_mock = mock_open(read_data=contents)
|
fopen_mock = mock_open(read_data=contents)
|
||||||
with patch('salt.utils.files.fopen', fopen_mock):
|
with patch("salt.utils.files.fopen", fopen_mock):
|
||||||
result = mymod.myfunc()
|
result = mymod.myfunc()
|
||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
@ -423,8 +409,8 @@ so, just add an ``as`` clause to the end of the ``patch`` statement:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n')
|
fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
|
||||||
with patch('salt.utils.files.fopen', fopen_mock) as m_open:
|
with patch("salt.utils.files.fopen", fopen_mock) as m_open:
|
||||||
# do testing here
|
# do testing here
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
|
@ -448,29 +434,25 @@ several useful attributes:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import patch, mock_open, MockCall
|
||||||
patch
|
|
||||||
mock_open,
|
|
||||||
MockCall,
|
|
||||||
)
|
|
||||||
|
|
||||||
import salt.modules.mymod as mymod
|
import salt.modules.mymod as mymod
|
||||||
|
|
||||||
class MyAwesomeTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class MyAwesomeTestCase(TestCase):
|
||||||
def test_something(self):
|
def test_something(self):
|
||||||
|
|
||||||
with patch('salt.utils.files.fopen', mock_open(read_data=b'foo\n')) as m_open:
|
with patch("salt.utils.files.fopen", mock_open(read_data=b"foo\n")) as m_open:
|
||||||
mymod.myfunc()
|
mymod.myfunc()
|
||||||
# Assert that only two opens attempted
|
# Assert that only two opens attempted
|
||||||
assert m_open.call_count == 2
|
assert m_open.call_count == 2
|
||||||
# Assert that only /etc/foo.conf was opened
|
# Assert that only /etc/foo.conf was opened
|
||||||
assert all(call.args[0] == '/etc/foo.conf' for call in m_open.calls)
|
assert all(call.args[0] == "/etc/foo.conf" for call in m_open.calls)
|
||||||
# Asser that the first open was for binary read, and the
|
# Asser that the first open was for binary read, and the
|
||||||
# second was for binary write.
|
# second was for binary write.
|
||||||
assert m_open.calls == [
|
assert m_open.calls == [
|
||||||
MockCall('/etc/foo.conf', 'rb'),
|
MockCall("/etc/foo.conf", "rb"),
|
||||||
MockCall('/etc/foo.conf', 'wb'),
|
MockCall("/etc/foo.conf", "wb"),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that ``MockCall`` is imported from ``tests.support.mock`` in the above
|
Note that ``MockCall`` is imported from ``tests.support.mock`` in the above
|
||||||
|
@ -526,35 +508,35 @@ Examples
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open:
|
with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
|
||||||
# Run the code you are unit testing
|
# Run the code you are unit testing
|
||||||
mymod.myfunc()
|
mymod.myfunc()
|
||||||
# Check that only the expected file was opened, and that it was opened
|
# Check that only the expected file was opened, and that it was opened
|
||||||
# only once.
|
# only once.
|
||||||
assert m_open.call_count == 1
|
assert m_open.call_count == 1
|
||||||
assert list(m_open.filehandles) == ['/etc/foo.conf']
|
assert list(m_open.filehandles) == ["/etc/foo.conf"]
|
||||||
# "opens" will be a list of all the mocked filehandles opened
|
# "opens" will be a list of all the mocked filehandles opened
|
||||||
opens = m_open.filehandles['/etc/foo.conf']
|
opens = m_open.filehandles["/etc/foo.conf"]
|
||||||
# Check that we wrote the expected lines ("expected" here is assumed to
|
# Check that we wrote the expected lines ("expected" here is assumed to
|
||||||
# be a list of strings)
|
# be a list of strings)
|
||||||
assert opens[0].write_calls == expected
|
assert opens[0].write_calls == expected
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open:
|
with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
|
||||||
# Run the code you are unit testing
|
# Run the code you are unit testing
|
||||||
mymod.myfunc()
|
mymod.myfunc()
|
||||||
# Check that .readlines() was called (remember, it's a Mock)
|
# Check that .readlines() was called (remember, it's a Mock)
|
||||||
m_open.filehandles['/etc/foo.conf'][0].readlines.assert_called()
|
m_open.filehandles["/etc/foo.conf"][0].readlines.assert_called()
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open:
|
with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
|
||||||
# Run the code you are unit testing
|
# Run the code you are unit testing
|
||||||
mymod.myfunc()
|
mymod.myfunc()
|
||||||
# Check that we read the file and also wrote to it
|
# Check that we read the file and also wrote to it
|
||||||
m_open.filehandles['/etc/foo.conf'][0].read.assert_called_once()
|
m_open.filehandles["/etc/foo.conf"][0].read.assert_called_once()
|
||||||
m_open.filehandles['/etc/foo.conf'][1].writelines.assert_called_once()
|
m_open.filehandles["/etc/foo.conf"][1].writelines.assert_called_once()
|
||||||
|
|
||||||
.. _`Mock()`: https://github.com/testing-cabal/mock
|
.. _`Mock()`: https://github.com/testing-cabal/mock
|
||||||
|
|
||||||
|
@ -628,11 +610,13 @@ methods, here presented in pseduo-code in an imaginary execution module called
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def create_user(username):
|
def create_user(username):
|
||||||
qry = 'CREATE USER {0}'.format(username)
|
qry = "CREATE USER {0}".format(username)
|
||||||
execute_query(qry)
|
execute_query(qry)
|
||||||
|
|
||||||
|
|
||||||
def execute_query(qry):
|
def execute_query(qry):
|
||||||
# Connect to a database and actually do the query...
|
# Connect to a database and actually do the query...
|
||||||
|
...
|
||||||
|
|
||||||
Here, let's imagine that we want to create a unit test for the `create_user`
|
Here, let's imagine that we want to create a unit test for the `create_user`
|
||||||
function. In doing so, we want to avoid any calls out to an external system and
|
function. In doing so, we want to avoid any calls out to an external system and
|
||||||
|
@ -661,10 +645,10 @@ additional imports for MagicMock:
|
||||||
class DbTestCase(TestCase):
|
class DbTestCase(TestCase):
|
||||||
def test_create_user(self):
|
def test_create_user(self):
|
||||||
# First, we replace 'execute_query' with our own mock function
|
# First, we replace 'execute_query' with our own mock function
|
||||||
with patch.object(db, 'execute_query', MagicMock()) as db_exq:
|
with patch.object(db, "execute_query", MagicMock()) as db_exq:
|
||||||
|
|
||||||
# Now that the exits are blocked, we can run the function under test.
|
# Now that the exits are blocked, we can run the function under test.
|
||||||
db.create_user('testuser')
|
db.create_user("testuser")
|
||||||
|
|
||||||
# We could now query our mock object to see which calls were made
|
# We could now query our mock object to see which calls were made
|
||||||
# to it.
|
# to it.
|
||||||
|
@ -672,7 +656,7 @@ additional imports for MagicMock:
|
||||||
|
|
||||||
# Construct a call object that simulates the way we expected
|
# Construct a call object that simulates the way we expected
|
||||||
# execute_query to have been called.
|
# execute_query to have been called.
|
||||||
expected_call = call('CREATE USER testuser')
|
expected_call = call("CREATE USER testuser")
|
||||||
|
|
||||||
# Compare the expected call with the list of actual calls. The
|
# Compare the expected call with the list of actual calls. The
|
||||||
# test will succeed or fail depending on the output of this
|
# test will succeed or fail depending on the output of this
|
||||||
|
@ -730,14 +714,15 @@ we might write the skeleton for testing ``fib.py``:
|
||||||
|
|
||||||
# Create test case class and inherit from Salt's customized TestCase
|
# Create test case class and inherit from Salt's customized TestCase
|
||||||
class FibTestCase(TestCase):
|
class FibTestCase(TestCase):
|
||||||
'''
|
"""
|
||||||
This class contains a set of functions that test salt.modules.fib.
|
This class contains a set of functions that test salt.modules.fib.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def test_fib(self):
|
def test_fib(self):
|
||||||
'''
|
"""
|
||||||
To create a unit test, we should prefix the name with `test_' so
|
To create a unit test, we should prefix the name with `test_' so
|
||||||
that it's recognized by the test runner.
|
that it's recognized by the test runner.
|
||||||
'''
|
"""
|
||||||
fib_five = (0, 1, 1, 2, 3)
|
fib_five = (0, 1, 1, 2, 3)
|
||||||
self.assertEqual(fib.calculate(5), fib_five)
|
self.assertEqual(fib.calculate(5), fib_five)
|
||||||
|
|
||||||
|
@ -774,7 +759,7 @@ Consider the following function from salt/modules/linux_sysctl.py.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def get(name):
|
def get(name):
|
||||||
'''
|
"""
|
||||||
Return a single sysctl parameter for this minion
|
Return a single sysctl parameter for this minion
|
||||||
|
|
||||||
CLI Example:
|
CLI Example:
|
||||||
|
@ -782,9 +767,9 @@ Consider the following function from salt/modules/linux_sysctl.py.
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
salt '*' sysctl.get net.ipv4.ip_forward
|
salt '*' sysctl.get net.ipv4.ip_forward
|
||||||
'''
|
"""
|
||||||
cmd = 'sysctl -n {0}'.format(name)
|
cmd = "sysctl -n {0}".format(name)
|
||||||
out = __salt__['cmd.run'](cmd)
|
out = __salt__["cmd.run"](cmd)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
This function is very simple, comprising only four source lines of code and
|
This function is very simple, comprising only four source lines of code and
|
||||||
|
@ -805,24 +790,21 @@ will also redefine the ``__salt__`` dictionary such that it only contains
|
||||||
# Import Salt Testing Libs
|
# Import Salt Testing Libs
|
||||||
from tests.support.mixins import LoaderModuleMockMixin
|
from tests.support.mixins import LoaderModuleMockMixin
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import MagicMock, patch
|
||||||
MagicMock,
|
|
||||||
patch,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
||||||
'''
|
"""
|
||||||
TestCase for salt.modules.linux_sysctl module
|
TestCase for salt.modules.linux_sysctl module
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
'''
|
"""
|
||||||
Tests the return of get function
|
Tests the return of get function
|
||||||
'''
|
"""
|
||||||
mock_cmd = MagicMock(return_value=1)
|
mock_cmd = MagicMock(return_value=1)
|
||||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run': mock_cmd}):
|
with patch.dict(linux_sysctl.__salt__, {"cmd.run": mock_cmd}):
|
||||||
self.assertEqual(linux_sysctl.get('net.ipv4.ip_forward'), 1)
|
self.assertEqual(linux_sysctl.get("net.ipv4.ip_forward"), 1)
|
||||||
|
|
||||||
Since ``get()`` has only one raise or return statement and that statement is a
|
Since ``get()`` has only one raise or return statement and that statement is a
|
||||||
success condition, the test function is simply named ``test_get()``. As
|
success condition, the test function is simply named ``test_get()``. As
|
||||||
|
@ -847,7 +829,7 @@ salt/modules/linux_sysctl.py source file.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def assign(name, value):
|
def assign(name, value):
|
||||||
'''
|
"""
|
||||||
Assign a single sysctl parameter for this minion
|
Assign a single sysctl parameter for this minion
|
||||||
|
|
||||||
CLI Example:
|
CLI Example:
|
||||||
|
@ -855,31 +837,30 @@ salt/modules/linux_sysctl.py source file.
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
salt '*' sysctl.assign net.ipv4.ip_forward 1
|
salt '*' sysctl.assign net.ipv4.ip_forward 1
|
||||||
'''
|
"""
|
||||||
value = str(value)
|
value = str(value)
|
||||||
sysctl_file = '/proc/sys/{0}'.format(name.replace('.', '/'))
|
sysctl_file = "/proc/sys/{0}".format(name.replace(".", "/"))
|
||||||
if not os.path.exists(sysctl_file):
|
if not os.path.exists(sysctl_file):
|
||||||
raise CommandExecutionError('sysctl {0} does not exist'.format(name))
|
raise CommandExecutionError("sysctl {0} does not exist".format(name))
|
||||||
|
|
||||||
ret = {}
|
ret = {}
|
||||||
cmd = 'sysctl -w {0}="{1}"'.format(name, value)
|
cmd = 'sysctl -w {0}="{1}"'.format(name, value)
|
||||||
data = __salt__['cmd.run_all'](cmd)
|
data = __salt__["cmd.run_all"](cmd)
|
||||||
out = data['stdout']
|
out = data["stdout"]
|
||||||
err = data['stderr']
|
err = data["stderr"]
|
||||||
|
|
||||||
# Example:
|
# Example:
|
||||||
# # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
|
# # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
|
||||||
# net.ipv4.tcp_rmem = 4096 87380 16777216
|
# net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||||
regex = re.compile(r'^{0}\s+=\s+{1}$'.format(re.escape(name),
|
regex = re.compile(r"^{0}\s+=\s+{1}$".format(re.escape(name), re.escape(value)))
|
||||||
re.escape(value)))
|
|
||||||
|
|
||||||
if not regex.match(out) or 'Invalid argument' in str(err):
|
if not regex.match(out) or "Invalid argument" in str(err):
|
||||||
if data['retcode'] != 0 and err:
|
if data["retcode"] != 0 and err:
|
||||||
error = err
|
error = err
|
||||||
else:
|
else:
|
||||||
error = out
|
error = out
|
||||||
raise CommandExecutionError('sysctl -w failed: {0}'.format(error))
|
raise CommandExecutionError("sysctl -w failed: {0}".format(error))
|
||||||
new_name, new_value = out.split(' = ', 1)
|
new_name, new_value = out.split(" = ", 1)
|
||||||
ret[new_name] = new_value
|
ret[new_name] = new_value
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -904,53 +885,63 @@ with.
|
||||||
# Import Salt Testing Libs
|
# Import Salt Testing Libs
|
||||||
from tests.support.mixins import LoaderModuleMockMixin
|
from tests.support.mixins import LoaderModuleMockMixin
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
from tests.support.mock import (
|
from tests.support.mock import MagicMock, patch
|
||||||
MagicMock,
|
|
||||||
patch,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
||||||
'''
|
"""
|
||||||
TestCase for salt.modules.linux_sysctl module
|
TestCase for salt.modules.linux_sysctl module
|
||||||
'''
|
"""
|
||||||
|
|
||||||
@patch('os.path.exists', MagicMock(return_value=False))
|
@patch("os.path.exists", MagicMock(return_value=False))
|
||||||
def test_assign_proc_sys_failed(self):
|
def test_assign_proc_sys_failed(self):
|
||||||
'''
|
"""
|
||||||
Tests if /proc/sys/<kernel-subsystem> exists or not
|
Tests if /proc/sys/<kernel-subsystem> exists or not
|
||||||
'''
|
"""
|
||||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '',
|
cmd = {
|
||||||
'stdout': 'net.ipv4.ip_forward = 1'}
|
"pid": 1337,
|
||||||
|
"retcode": 0,
|
||||||
|
"stderr": "",
|
||||||
|
"stdout": "net.ipv4.ip_forward = 1",
|
||||||
|
}
|
||||||
mock_cmd = MagicMock(return_value=cmd)
|
mock_cmd = MagicMock(return_value=cmd)
|
||||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||||
self.assertRaises(CommandExecutionError,
|
self.assertRaises(
|
||||||
linux_sysctl.assign,
|
CommandExecutionError, linux_sysctl.assign, "net.ipv4.ip_forward", 1
|
||||||
'net.ipv4.ip_forward', 1)
|
)
|
||||||
|
|
||||||
@patch('os.path.exists', MagicMock(return_value=True))
|
@patch("os.path.exists", MagicMock(return_value=True))
|
||||||
def test_assign_cmd_failed(self):
|
def test_assign_cmd_failed(self):
|
||||||
'''
|
"""
|
||||||
Tests if the assignment was successful or not
|
Tests if the assignment was successful or not
|
||||||
'''
|
"""
|
||||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr':
|
cmd = {
|
||||||
'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
|
"pid": 1337,
|
||||||
'stdout': 'net.ipv4.ip_forward = backward'}
|
"retcode": 0,
|
||||||
|
"stderr": 'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
|
||||||
|
"stdout": "net.ipv4.ip_forward = backward",
|
||||||
|
}
|
||||||
mock_cmd = MagicMock(return_value=cmd)
|
mock_cmd = MagicMock(return_value=cmd)
|
||||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||||
self.assertRaises(CommandExecutionError,
|
self.assertRaises(
|
||||||
linux_sysctl.assign,
|
CommandExecutionError,
|
||||||
'net.ipv4.ip_forward', 'backward')
|
linux_sysctl.assign,
|
||||||
|
"net.ipv4.ip_forward",
|
||||||
|
"backward",
|
||||||
|
)
|
||||||
|
|
||||||
@patch('os.path.exists', MagicMock(return_value=True))
|
@patch("os.path.exists", MagicMock(return_value=True))
|
||||||
def test_assign_success(self):
|
def test_assign_success(self):
|
||||||
'''
|
"""
|
||||||
Tests the return of successful assign function
|
Tests the return of successful assign function
|
||||||
'''
|
"""
|
||||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '',
|
cmd = {
|
||||||
'stdout': 'net.ipv4.ip_forward = 1'}
|
"pid": 1337,
|
||||||
ret = {'net.ipv4.ip_forward': '1'}
|
"retcode": 0,
|
||||||
|
"stderr": "",
|
||||||
|
"stdout": "net.ipv4.ip_forward = 1",
|
||||||
|
}
|
||||||
|
ret = {"net.ipv4.ip_forward": "1"}
|
||||||
mock_cmd = MagicMock(return_value=cmd)
|
mock_cmd = MagicMock(return_value=cmd)
|
||||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||||
self.assertEqual(linux_sysctl.assign(
|
self.assertEqual(linux_sysctl.assign("net.ipv4.ip_forward", 1), ret)
|
||||||
'net.ipv4.ip_forward', 1), ret)
|
|
||||||
|
|
|
@ -84,13 +84,11 @@ The following code will check for a single event:
|
||||||
import salt.config
|
import salt.config
|
||||||
import salt.utils.event
|
import salt.utils.event
|
||||||
|
|
||||||
opts = salt.config.client_config('/etc/salt/master')
|
opts = salt.config.client_config("/etc/salt/master")
|
||||||
|
|
||||||
event = salt.utils.event.get_event(
|
event = salt.utils.event.get_event(
|
||||||
'master',
|
"master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
|
||||||
sock_dir=opts['sock_dir'],
|
)
|
||||||
transport=opts['transport'],
|
|
||||||
opts=opts)
|
|
||||||
|
|
||||||
data = event.get_event()
|
data = event.get_event()
|
||||||
|
|
||||||
|
@ -106,15 +104,15 @@ instead of the default 5.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
data = event.get_event(wait=10, tag='salt/auth')
|
data = event.get_event(wait=10, tag="salt/auth")
|
||||||
|
|
||||||
To retrieve the tag as well as the event data, pass ``full=True``:
|
To retrieve the tag as well as the event data, pass ``full=True``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
evdata = event.get_event(wait=10, tag='salt/job', full=True)
|
evdata = event.get_event(wait=10, tag="salt/job", full=True)
|
||||||
|
|
||||||
tag, data = evdata['tag'], evdata['data']
|
tag, data = evdata["tag"], evdata["data"]
|
||||||
|
|
||||||
|
|
||||||
Instead of looking for a single event, the ``iter_events`` method can be used to
|
Instead of looking for a single event, the ``iter_events`` method can be used to
|
||||||
|
@ -124,7 +122,7 @@ The iter_events method also accepts a tag but not a wait time:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
for data in event.iter_events(tag='salt/auth'):
|
for data in event.iter_events(tag="salt/auth"):
|
||||||
print(data)
|
print(data)
|
||||||
|
|
||||||
And finally event tags can be globbed, such as they can be in the Reactor,
|
And finally event tags can be globbed, such as they can be in the Reactor,
|
||||||
|
@ -137,21 +135,19 @@ using the fnmatch library.
|
||||||
import salt.config
|
import salt.config
|
||||||
import salt.utils.event
|
import salt.utils.event
|
||||||
|
|
||||||
opts = salt.config.client_config('/etc/salt/master')
|
opts = salt.config.client_config("/etc/salt/master")
|
||||||
|
|
||||||
sevent = salt.utils.event.get_event(
|
sevent = salt.utils.event.get_event(
|
||||||
'master',
|
"master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
|
||||||
sock_dir=opts['sock_dir'],
|
)
|
||||||
transport=opts['transport'],
|
|
||||||
opts=opts)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
ret = sevent.get_event(full=True)
|
ret = sevent.get_event(full=True)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if fnmatch.fnmatch(ret['tag'], 'salt/job/*/ret/*'):
|
if fnmatch.fnmatch(ret["tag"], "salt/job/*/ret/*"):
|
||||||
do_something_with_job_return(ret['data'])
|
do_something_with_job_return(ret["data"])
|
||||||
|
|
||||||
Firing Events
|
Firing Events
|
||||||
=============
|
=============
|
||||||
|
@ -184,12 +180,12 @@ a minion on a non-Windows system:
|
||||||
# Job on minion
|
# Job on minion
|
||||||
import salt.utils.event
|
import salt.utils.event
|
||||||
|
|
||||||
opts = salt.config.minion_config('/etc/salt/minion')
|
opts = salt.config.minion_config("/etc/salt/minion")
|
||||||
event = salt.utils.event.MinionEvent(opts)
|
event = salt.utils.event.MinionEvent(opts)
|
||||||
|
|
||||||
for evdata in event.iter_events(match_type = 'regex',
|
for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
|
||||||
tag = 'custom/.*'):
|
|
||||||
# do your processing here...
|
# do your processing here...
|
||||||
|
...
|
||||||
|
|
||||||
And an example of listening local events on a Windows system:
|
And an example of listening local events on a Windows system:
|
||||||
|
|
||||||
|
@ -201,9 +197,9 @@ And an example of listening local events on a Windows system:
|
||||||
opts = salt.config.minion_config(salt.minion.DEFAULT_MINION_OPTS)
|
opts = salt.config.minion_config(salt.minion.DEFAULT_MINION_OPTS)
|
||||||
event = salt.utils.event.MinionEvent(opts)
|
event = salt.utils.event.MinionEvent(opts)
|
||||||
|
|
||||||
for evdata in event.iter_events(match_type = 'regex',
|
for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
|
||||||
tag = 'custom/.*'):
|
|
||||||
# do your processing here...
|
# do your processing here...
|
||||||
|
...
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
@ -224,19 +220,20 @@ easily done using the normal cross-calling syntax:
|
||||||
|
|
||||||
# /srv/salt/_modules/my_custom_module.py
|
# /srv/salt/_modules/my_custom_module.py
|
||||||
|
|
||||||
|
|
||||||
def do_something():
|
def do_something():
|
||||||
'''
|
"""
|
||||||
Do something and fire an event to the master when finished
|
Do something and fire an event to the master when finished
|
||||||
|
|
||||||
CLI Example::
|
CLI Example::
|
||||||
|
|
||||||
salt '*' my_custom_module:do_something
|
salt '*' my_custom_module:do_something
|
||||||
'''
|
"""
|
||||||
# do something!
|
# do something!
|
||||||
__salt__['event.send']('myco/my_custom_module/finished', {
|
__salt__["event.send"](
|
||||||
'finished': True,
|
"myco/my_custom_module/finished",
|
||||||
'message': "The something is finished!",
|
{"finished": True, "message": "The something is finished!",},
|
||||||
})
|
)
|
||||||
|
|
||||||
From Custom Python Scripts
|
From Custom Python Scripts
|
||||||
--------------------------
|
--------------------------
|
||||||
|
@ -257,3 +254,4 @@ done at the CLI:
|
||||||
|
|
||||||
if not ret:
|
if not ret:
|
||||||
# the event could not be sent, process the error here
|
# the event could not be sent, process the error here
|
||||||
|
...
|
||||||
|
|
|
@ -58,9 +58,9 @@ Example Process Class
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.event = SaltEvent('master', self.opts['sock_dir'])
|
self.event = SaltEvent("master", self.opts["sock_dir"])
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.event.fire_event({'iteration': i}, 'ext_processes/test{0}')
|
self.event.fire_event({"iteration": i}, "ext_processes/test{0}")
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
|
|
|
@ -146,12 +146,12 @@ dictionary. For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def yourfunction():
|
def yourfunction():
|
||||||
# initialize a grains dictionary
|
# initialize a grains dictionary
|
||||||
grains = {}
|
grains = {}
|
||||||
# Some code for logic that sets grains like
|
# Some code for logic that sets grains like
|
||||||
grains['yourcustomgrain'] = True
|
grains["yourcustomgrain"] = True
|
||||||
grains['anothergrain'] = 'somevalue'
|
grains["anothergrain"] = "somevalue"
|
||||||
return grains
|
return grains
|
||||||
|
|
||||||
The name of the function does not matter and will not factor into the grains
|
The name of the function does not matter and will not factor into the grains
|
||||||
data at all; only the keys/values returned become part of the grains.
|
data at all; only the keys/values returned become part of the grains.
|
||||||
|
@ -205,14 +205,14 @@ grain data structure. For example, consider this custom grain file:
|
||||||
|
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
def _my_custom_grain():
|
def _my_custom_grain():
|
||||||
my_grain = {'foo': 'bar', 'hello': 'world'}
|
my_grain = {"foo": "bar", "hello": "world"}
|
||||||
return my_grain
|
return my_grain
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# initialize a grains dictionary
|
# initialize a grains dictionary
|
||||||
grains = {}
|
grains = {}
|
||||||
grains['my_grains'] = _my_custom_grain()
|
grains["my_grains"] = _my_custom_grain()
|
||||||
return grains
|
return grains
|
||||||
|
|
||||||
The output of this example renders like so:
|
The output of this example renders like so:
|
||||||
|
|
|
@ -379,7 +379,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
('defabcdef',)
|
("defabcdef",)
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: regex_match
|
.. jinja_ref:: regex_match
|
||||||
|
@ -740,7 +740,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'new': [4], 'old': [3]}
|
{"new": [4], "old": [3]}
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: compare_dicts
|
.. jinja_ref:: compare_dicts
|
||||||
|
@ -762,7 +762,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'a': {'new': 'c', 'old': 'b'}}
|
{"a": {"new": "c", "old": "b"}}
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: is_hex
|
.. jinja_ref:: is_hex
|
||||||
|
@ -1018,7 +1018,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'a': '\xd0\x94'}
|
{"a": "\xd0\x94"}
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: tojson
|
.. jinja_ref:: tojson
|
||||||
|
@ -1381,7 +1381,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'c1': 'foo'}
|
{"c1": "foo"}
|
||||||
|
|
||||||
.. code-block:: jinja
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
@ -1391,7 +1391,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
'default'
|
"default"
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: json_query
|
.. jinja_ref:: json_query
|
||||||
|
@ -1571,7 +1571,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
['192.168.0.1', 'fe80::']
|
["192.168.0.1", "fe80::"]
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: ipv4
|
.. jinja_ref:: ipv4
|
||||||
|
@ -1594,7 +1594,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
['192.168.0.1']
|
["192.168.0.1"]
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: ipv6
|
.. jinja_ref:: ipv6
|
||||||
|
@ -1617,7 +1617,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
['fe80::']
|
["fe80::"]
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: network_hosts
|
.. jinja_ref:: network_hosts
|
||||||
|
@ -1644,7 +1644,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
['192.168.0.1', '192.168.0.2']
|
["192.168.0.1", "192.168.0.2"]
|
||||||
|
|
||||||
|
|
||||||
.. jinja_ref:: network_size
|
.. jinja_ref:: network_size
|
||||||
|
@ -1947,7 +1947,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
[{'value': 3}]
|
[{"value": 3}]
|
||||||
|
|
||||||
.. jinja_ref:: match
|
.. jinja_ref:: match
|
||||||
|
|
||||||
|
@ -1976,7 +1976,7 @@ Returns:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
[{'value': 'b'}, {'value': 'c'}]
|
[{"value": "b"}, {"value": "c"}]
|
||||||
|
|
||||||
|
|
||||||
Test supports additional optional arguments: ``ignorecase``, ``multiline``
|
Test supports additional optional arguments: ``ignorecase``, ``multiline``
|
||||||
|
|
|
@ -61,8 +61,9 @@ bare-bones example:
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Define the module's virtual name
|
# Define the module's virtual name
|
||||||
__virtualname__ = 'customtop'
|
__virtualname__ = "customtop"
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -72,8 +73,8 @@ bare-bones example:
|
||||||
|
|
||||||
|
|
||||||
def top(**kwargs):
|
def top(**kwargs):
|
||||||
log.debug('Calling top in customtop')
|
log.debug("Calling top in customtop")
|
||||||
return {'base': ['test']}
|
return {"base": ["test"]}
|
||||||
|
|
||||||
`salt minion state.show_top` should then display something like:
|
`salt minion state.show_top` should then display something like:
|
||||||
|
|
||||||
|
|
|
@ -36,14 +36,15 @@ As an example, let's modify the ``list`` matcher to have the separator be a
|
||||||
from __future__ import absolute_import, print_function, unicode_literals
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
|
||||||
def match(self, tgt):
|
def match(self, tgt):
|
||||||
'''
|
"""
|
||||||
Determines if this host is on the list
|
Determines if this host is on the list
|
||||||
'''
|
"""
|
||||||
if isinstance(tgt, six.string_types):
|
if isinstance(tgt, six.string_types):
|
||||||
# The stock matcher splits on `,`. Change to `/` below.
|
# The stock matcher splits on `,`. Change to `/` below.
|
||||||
tgt = tgt.split('/')
|
tgt = tgt.split("/")
|
||||||
return bool(self.opts['id'] in tgt)
|
return bool(self.opts["id"] in tgt)
|
||||||
|
|
||||||
|
|
||||||
Place this code in a file called ``list_matcher.py`` in ``_matchers`` in your
|
Place this code in a file called ``list_matcher.py`` in ``_matchers`` in your
|
||||||
|
|
|
@ -278,10 +278,10 @@ in remote execution functions. Here is some example pseudocode:
|
||||||
|
|
||||||
def myrunner():
|
def myrunner():
|
||||||
...
|
...
|
||||||
do stuff
|
# do stuff
|
||||||
...
|
...
|
||||||
if some_error_condition:
|
if some_error_condition:
|
||||||
__context__['retcode'] = 1
|
__context__["retcode"] = 1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
This allows a custom runner/wheel function to report its failure so that
|
This allows a custom runner/wheel function to report its failure so that
|
||||||
|
|
|
@ -85,12 +85,14 @@ they are being loaded for the correct proxytype, example below:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Only work on proxy
|
Only work on proxy
|
||||||
'''
|
"""
|
||||||
try:
|
try:
|
||||||
if salt.utils.platform.is_proxy() and \
|
if (
|
||||||
__opts__['proxy']['proxytype'] == 'ssh_sample':
|
salt.utils.platform.is_proxy()
|
||||||
|
and __opts__["proxy"]["proxytype"] == "ssh_sample"
|
||||||
|
):
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
@ -110,10 +112,10 @@ to ``__proxy__``. This enables patterns like
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def get_ip(proxy):
|
def get_ip(proxy):
|
||||||
'''
|
"""
|
||||||
Ask the remote device what IP it has
|
Ask the remote device what IP it has
|
||||||
'''
|
"""
|
||||||
return {'ip':proxy['proxymodulename.get_ip']()}
|
return {"ip": proxy["proxymodulename.get_ip"]()}
|
||||||
|
|
||||||
|
|
||||||
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function
|
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function
|
||||||
|
@ -397,10 +399,10 @@ and status; "package" installation, and a ping.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
This is a simple proxy-minion designed to connect to and communicate with
|
This is a simple proxy-minion designed to connect to and communicate with
|
||||||
the bottle-based web service contained in https://github.com/saltstack/salt-contrib/tree/master/proxyminion_rest_example
|
the bottle-based web service contained in https://github.com/saltstack/salt-contrib/tree/master/proxyminion_rest_example
|
||||||
'''
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
# Import python libs
|
# Import python libs
|
||||||
|
@ -410,7 +412,7 @@ and status; "package" installation, and a ping.
|
||||||
HAS_REST_EXAMPLE = True
|
HAS_REST_EXAMPLE = True
|
||||||
|
|
||||||
# This must be present or the Salt loader won't load this module
|
# This must be present or the Salt loader won't load this module
|
||||||
__proxyenabled__ = ['rest_sample']
|
__proxyenabled__ = ["rest_sample"]
|
||||||
|
|
||||||
|
|
||||||
# Variables are scoped to this module so we can have persistent data
|
# Variables are scoped to this module so we can have persistent data
|
||||||
|
@ -425,182 +427,207 @@ and status; "package" installation, and a ping.
|
||||||
# This does nothing, it's here just as an example and to provide a log
|
# This does nothing, it's here just as an example and to provide a log
|
||||||
# entry when the module is loaded.
|
# entry when the module is loaded.
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Only return if all the modules are available
|
Only return if all the modules are available
|
||||||
'''
|
"""
|
||||||
log.debug('rest_sample proxy __virtual__() called...')
|
log.debug("rest_sample proxy __virtual__() called...")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _complicated_function_that_determines_if_alive():
|
def _complicated_function_that_determines_if_alive():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# Every proxy module needs an 'init', though you can
|
# Every proxy module needs an 'init', though you can
|
||||||
# just put DETAILS['initialized'] = True here if nothing
|
# just put DETAILS['initialized'] = True here if nothing
|
||||||
# else needs to be done.
|
# else needs to be done.
|
||||||
|
|
||||||
|
|
||||||
def init(opts):
|
def init(opts):
|
||||||
log.debug('rest_sample proxy init() called...')
|
log.debug("rest_sample proxy init() called...")
|
||||||
DETAILS['initialized'] = True
|
DETAILS["initialized"] = True
|
||||||
|
|
||||||
# Save the REST URL
|
# Save the REST URL
|
||||||
DETAILS['url'] = opts['proxy']['url']
|
DETAILS["url"] = opts["proxy"]["url"]
|
||||||
|
|
||||||
# Make sure the REST URL ends with a '/'
|
# Make sure the REST URL ends with a '/'
|
||||||
if not DETAILS['url'].endswith('/'):
|
if not DETAILS["url"].endswith("/"):
|
||||||
DETAILS['url'] += '/'
|
DETAILS["url"] += "/"
|
||||||
|
|
||||||
|
|
||||||
def alive(opts):
|
def alive(opts):
|
||||||
'''
|
"""
|
||||||
This function returns a flag with the connection state.
|
This function returns a flag with the connection state.
|
||||||
It is very useful when the proxy minion establishes the communication
|
It is very useful when the proxy minion establishes the communication
|
||||||
via a channel that requires a more elaborated keep-alive mechanism, e.g.
|
via a channel that requires a more elaborated keep-alive mechanism, e.g.
|
||||||
NETCONF over SSH.
|
NETCONF over SSH.
|
||||||
'''
|
"""
|
||||||
log.debug('rest_sample proxy alive() called...')
|
log.debug("rest_sample proxy alive() called...")
|
||||||
return _complicated_function_that_determines_if_alive()
|
return _complicated_function_that_determines_if_alive()
|
||||||
|
|
||||||
|
|
||||||
def initialized():
|
def initialized():
|
||||||
'''
|
"""
|
||||||
Since grains are loaded in many different places and some of those
|
Since grains are loaded in many different places and some of those
|
||||||
places occur before the proxy can be initialized, return whether
|
places occur before the proxy can be initialized, return whether
|
||||||
our init() function has been called
|
our init() function has been called
|
||||||
'''
|
"""
|
||||||
return DETAILS.get('initialized', False)
|
return DETAILS.get("initialized", False)
|
||||||
|
|
||||||
|
|
||||||
def grains():
|
def grains():
|
||||||
'''
|
"""
|
||||||
Get the grains from the proxied device
|
Get the grains from the proxied device
|
||||||
'''
|
"""
|
||||||
if not DETAILS.get('grains_cache', {}):
|
if not DETAILS.get("grains_cache", {}):
|
||||||
r = salt.utils.http.query(DETAILS['url']+'info', decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
DETAILS['grains_cache'] = r['dict']
|
DETAILS["url"] + "info", decode_type="json", decode=True
|
||||||
return DETAILS['grains_cache']
|
)
|
||||||
|
DETAILS["grains_cache"] = r["dict"]
|
||||||
|
return DETAILS["grains_cache"]
|
||||||
|
|
||||||
|
|
||||||
def grains_refresh():
|
def grains_refresh():
|
||||||
'''
|
"""
|
||||||
Refresh the grains from the proxied device
|
Refresh the grains from the proxied device
|
||||||
'''
|
"""
|
||||||
DETAILS['grains_cache'] = None
|
DETAILS["grains_cache"] = None
|
||||||
return grains()
|
return grains()
|
||||||
|
|
||||||
|
|
||||||
def fns():
|
def fns():
|
||||||
return {'details': 'This key is here because a function in '
|
return {
|
||||||
'grains/rest_sample.py called fns() here in the proxymodule.'}
|
"details": "This key is here because a function in "
|
||||||
|
"grains/rest_sample.py called fns() here in the proxymodule."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def service_start(name):
|
def service_start(name):
|
||||||
'''
|
"""
|
||||||
Start a "service" on the REST server
|
Start a "service" on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'service/start/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "service/start/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def service_stop(name):
|
def service_stop(name):
|
||||||
'''
|
"""
|
||||||
Stop a "service" on the REST server
|
Stop a "service" on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'service/stop/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "service/stop/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def service_restart(name):
|
def service_restart(name):
|
||||||
'''
|
"""
|
||||||
Restart a "service" on the REST server
|
Restart a "service" on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'service/restart/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "service/restart/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def service_list():
|
def service_list():
|
||||||
'''
|
"""
|
||||||
List "services" on the REST server
|
List "services" on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'service/list', decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "service/list", decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def service_status(name):
|
def service_status(name):
|
||||||
'''
|
"""
|
||||||
Check if a service is running on the REST server
|
Check if a service is running on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'service/status/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "service/status/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def package_list():
|
def package_list():
|
||||||
'''
|
"""
|
||||||
List "packages" installed on the REST server
|
List "packages" installed on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'package/list', decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "package/list", decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def package_install(name, **kwargs):
|
def package_install(name, **kwargs):
|
||||||
'''
|
"""
|
||||||
Install a "package" on the REST server
|
Install a "package" on the REST server
|
||||||
'''
|
"""
|
||||||
cmd = DETAILS['url']+'package/install/'+name
|
cmd = DETAILS["url"] + "package/install/" + name
|
||||||
if kwargs.get('version', False):
|
if kwargs.get("version", False):
|
||||||
cmd += '/'+kwargs['version']
|
cmd += "/" + kwargs["version"]
|
||||||
else:
|
else:
|
||||||
cmd += '/1.0'
|
cmd += "/1.0"
|
||||||
r = salt.utils.http.query(cmd, decode_type='json', decode=True)
|
r = salt.utils.http.query(cmd, decode_type="json", decode=True)
|
||||||
return r['dict']
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def fix_outage():
|
def fix_outage():
|
||||||
r = salt.utils.http.query(DETAILS['url']+'fix_outage')
|
r = salt.utils.http.query(DETAILS["url"] + "fix_outage")
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def uptodate(name):
|
def uptodate(name):
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Call the REST endpoint to see if the packages on the "server" are up to date.
|
Call the REST endpoint to see if the packages on the "server" are up to date.
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'package/remove/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def package_remove(name):
|
def package_remove(name):
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Remove a "package" on the REST server
|
Remove a "package" on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'package/remove/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def package_status(name):
|
def package_status(name):
|
||||||
'''
|
"""
|
||||||
Check the installation status of a package on the REST server
|
Check the installation status of a package on the REST server
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'package/status/'+name, decode_type='json', decode=True)
|
r = salt.utils.http.query(
|
||||||
return r['dict']
|
DETAILS["url"] + "package/status/" + name, decode_type="json", decode=True
|
||||||
|
)
|
||||||
|
return r["dict"]
|
||||||
|
|
||||||
|
|
||||||
def ping():
|
def ping():
|
||||||
'''
|
"""
|
||||||
Is the REST server up?
|
Is the REST server up?
|
||||||
'''
|
"""
|
||||||
r = salt.utils.http.query(DETAILS['url']+'ping', decode_type='json', decode=True)
|
r = salt.utils.http.query(DETAILS["url"] + "ping", decode_type="json", decode=True)
|
||||||
try:
|
try:
|
||||||
return r['dict'].get('ret', False)
|
return r["dict"].get("ret", False)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def shutdown(opts):
|
def shutdown(opts):
|
||||||
'''
|
"""
|
||||||
For this proxy shutdown is a no-op
|
For this proxy shutdown is a no-op
|
||||||
'''
|
"""
|
||||||
log.debug('rest_sample proxy shutdown() called...')
|
log.debug("rest_sample proxy shutdown() called...")
|
||||||
|
|
||||||
|
|
||||||
.. _grains support code:
|
.. _grains support code:
|
||||||
|
@ -702,19 +729,23 @@ Example from ``salt/grains/rest_sample.py``:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
Generate baseline proxy minion grains
|
Generate baseline proxy minion grains
|
||||||
'''
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import salt.utils.platform
|
import salt.utils.platform
|
||||||
|
|
||||||
__proxyenabled__ = ['rest_sample']
|
__proxyenabled__ = ["rest_sample"]
|
||||||
|
|
||||||
|
__virtualname__ = "rest_sample"
|
||||||
|
|
||||||
__virtualname__ = 'rest_sample'
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
try:
|
try:
|
||||||
if salt.utils.platform.is_proxy() and __opts__['proxy']['proxytype'] == 'rest_sample':
|
if (
|
||||||
|
salt.utils.platform.is_proxy()
|
||||||
|
and __opts__["proxy"]["proxytype"] == "rest_sample"
|
||||||
|
):
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
@ -746,12 +777,12 @@ This proxymodule enables "package" installation.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
This is a simple proxy-minion designed to connect to and communicate with
|
This is a simple proxy-minion designed to connect to and communicate with
|
||||||
a server that exposes functionality via SSH.
|
a server that exposes functionality via SSH.
|
||||||
This can be used as an option when the device does not provide
|
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.
|
an api over HTTP and doesn't have the python stack to run a minion.
|
||||||
'''
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
# Import python libs
|
# Import python libs
|
||||||
|
@ -763,7 +794,7 @@ This proxymodule enables "package" installation.
|
||||||
from salt.utils.vt import TerminalException
|
from salt.utils.vt import TerminalException
|
||||||
|
|
||||||
# This must be present or the Salt loader won't load this module
|
# This must be present or the Salt loader won't load this module
|
||||||
__proxyenabled__ = ['ssh_sample']
|
__proxyenabled__ = ["ssh_sample"]
|
||||||
|
|
||||||
DETAILS = {}
|
DETAILS = {}
|
||||||
|
|
||||||
|
@ -774,25 +805,27 @@ This proxymodule enables "package" installation.
|
||||||
# This does nothing, it's here just as an example and to provide a log
|
# This does nothing, it's here just as an example and to provide a log
|
||||||
# entry when the module is loaded.
|
# entry when the module is loaded.
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Only return if all the modules are available
|
Only return if all the modules are available
|
||||||
'''
|
"""
|
||||||
log.info('ssh_sample proxy __virtual__() called...')
|
log.info("ssh_sample proxy __virtual__() called...")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def init(opts):
|
def init(opts):
|
||||||
'''
|
"""
|
||||||
Required.
|
Required.
|
||||||
Can be used to initialize the server connection.
|
Can be used to initialize the server connection.
|
||||||
'''
|
"""
|
||||||
try:
|
try:
|
||||||
DETAILS['server'] = SSHConnection(host=__opts__['proxy']['host'],
|
DETAILS["server"] = SSHConnection(
|
||||||
username=__opts__['proxy']['username'],
|
host=__opts__["proxy"]["host"],
|
||||||
password=__opts__['proxy']['password'])
|
username=__opts__["proxy"]["username"],
|
||||||
|
password=__opts__["proxy"]["password"],
|
||||||
|
)
|
||||||
# connected to the SSH server
|
# connected to the SSH server
|
||||||
out, err = DETAILS['server'].sendline('help')
|
out, err = DETAILS["server"].sendline("help")
|
||||||
|
|
||||||
except TerminalException as e:
|
except TerminalException as e:
|
||||||
log.error(e)
|
log.error(e)
|
||||||
|
@ -800,73 +833,73 @@ This proxymodule enables "package" installation.
|
||||||
|
|
||||||
|
|
||||||
def shutdown(opts):
|
def shutdown(opts):
|
||||||
'''
|
"""
|
||||||
Disconnect
|
Disconnect
|
||||||
'''
|
"""
|
||||||
DETAILS['server'].close_connection()
|
DETAILS["server"].close_connection()
|
||||||
|
|
||||||
|
|
||||||
def parse(out):
|
def parse(out):
|
||||||
'''
|
"""
|
||||||
Extract json from out.
|
Extract json from out.
|
||||||
|
|
||||||
Parameter
|
Parameter
|
||||||
out: Type string. The data returned by the
|
out: Type string. The data returned by the
|
||||||
ssh command.
|
ssh command.
|
||||||
'''
|
"""
|
||||||
jsonret = []
|
jsonret = []
|
||||||
in_json = False
|
in_json = False
|
||||||
for ln_ in out.split('\n'):
|
for ln_ in out.split("\n"):
|
||||||
if '{' in ln_:
|
if "{" in ln_:
|
||||||
in_json = True
|
in_json = True
|
||||||
if in_json:
|
if in_json:
|
||||||
jsonret.append(ln_)
|
jsonret.append(ln_)
|
||||||
if '}' in ln_:
|
if "}" in ln_:
|
||||||
in_json = False
|
in_json = False
|
||||||
return salt.utils.json.loads('\n'.join(jsonret))
|
return salt.utils.json.loads("\n".join(jsonret))
|
||||||
|
|
||||||
|
|
||||||
def package_list():
|
def package_list():
|
||||||
'''
|
"""
|
||||||
List "packages" by executing a command via ssh
|
List "packages" by executing a command via ssh
|
||||||
This function is called in response to the salt command
|
This function is called in response to the salt command
|
||||||
|
|
||||||
..code-block::bash
|
..code-block::bash
|
||||||
salt target_minion pkg.list_pkgs
|
salt target_minion pkg.list_pkgs
|
||||||
|
|
||||||
'''
|
"""
|
||||||
# Send the command to execute
|
# Send the command to execute
|
||||||
out, err = DETAILS['server'].sendline('pkg_list')
|
out, err = DETAILS["server"].sendline("pkg_list")
|
||||||
|
|
||||||
# "scrape" the output and return the right fields as a dict
|
# "scrape" the output and return the right fields as a dict
|
||||||
return parse(out)
|
return parse(out)
|
||||||
|
|
||||||
|
|
||||||
def package_install(name, **kwargs):
|
def package_install(name, **kwargs):
|
||||||
'''
|
"""
|
||||||
Install a "package" on the REST server
|
Install a "package" on the REST server
|
||||||
'''
|
"""
|
||||||
cmd = 'pkg_install ' + name
|
cmd = "pkg_install " + name
|
||||||
if 'version' in kwargs:
|
if "version" in kwargs:
|
||||||
cmd += '/'+kwargs['version']
|
cmd += "/" + kwargs["version"]
|
||||||
else:
|
else:
|
||||||
cmd += '/1.0'
|
cmd += "/1.0"
|
||||||
|
|
||||||
# Send the command to execute
|
# Send the command to execute
|
||||||
out, err = DETAILS['server'].sendline(cmd)
|
out, err = DETAILS["server"].sendline(cmd)
|
||||||
|
|
||||||
# "scrape" the output and return the right fields as a dict
|
# "scrape" the output and return the right fields as a dict
|
||||||
return parse(out)
|
return parse(out)
|
||||||
|
|
||||||
|
|
||||||
def package_remove(name):
|
def package_remove(name):
|
||||||
'''
|
"""
|
||||||
Remove a "package" on the REST server
|
Remove a "package" on the REST server
|
||||||
'''
|
"""
|
||||||
cmd = 'pkg_remove ' + name
|
cmd = "pkg_remove " + name
|
||||||
|
|
||||||
# Send the command to execute
|
# Send the command to execute
|
||||||
out, err = DETAILS['server'].sendline(cmd)
|
out, err = DETAILS["server"].sendline(cmd)
|
||||||
|
|
||||||
# "scrape" the output and return the right fields as a dict
|
# "scrape" the output and return the right fields as a dict
|
||||||
return parse(out)
|
return parse(out)
|
||||||
|
|
|
@ -110,12 +110,12 @@ write Salt States in pure Python:
|
||||||
|
|
||||||
#!py
|
#!py
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
'''
|
"""
|
||||||
Install the python-mako package
|
Install the python-mako package
|
||||||
'''
|
"""
|
||||||
return {'include': ['python'],
|
return {"include": ["python"], "python-mako": {"pkg": ["installed"]}}
|
||||||
'python-mako': {'pkg': ['installed']}}
|
|
||||||
|
|
||||||
This renderer is used by making a run function that returns the Highstate data
|
This renderer is used by making a run function that returns the Highstate data
|
||||||
structure. Any capabilities of Python can be used in pure Python sls modules.
|
structure. Any capabilities of Python can be used in pure Python sls modules.
|
||||||
|
|
|
@ -162,8 +162,8 @@ This addition to the LocalClient api can be used like so:
|
||||||
|
|
||||||
import salt.client
|
import salt.client
|
||||||
|
|
||||||
client = salt.client.LocalClient('/etc/salt/master')
|
client = salt.client.LocalClient("/etc/salt/master")
|
||||||
ret = client.cmd('*', 'cmd.run', ['ls -l'], kwarg={'cwd': '/etc'})
|
ret = client.cmd("*", "cmd.run", ["ls -l"], kwarg={"cwd": "/etc"})
|
||||||
|
|
||||||
This update has been added to all cmd methods in the LocalClient class.
|
This update has been added to all cmd methods in the LocalClient class.
|
||||||
|
|
||||||
|
|
|
@ -378,10 +378,7 @@ eAuth Changes
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
[{'web*': ['test.*',
|
[{"web*": ["test.*", "network.*"]}, "@wheel", "@runner"]
|
||||||
'network.*']},
|
|
||||||
'@wheel',
|
|
||||||
'@runner']
|
|
||||||
|
|
||||||
- External auth is supported by :ref:`salt-run <salt-run>` and
|
- External auth is supported by :ref:`salt-run <salt-run>` and
|
||||||
:ref:`salt-key <salt-key>` now. Note that master must be started to
|
:ref:`salt-key <salt-key>` now. Note that master must be started to
|
||||||
|
@ -554,13 +551,15 @@ General Deprecations
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def fcn(msg='', env='base', refresh=True, saltenv='base', **kwargs):
|
def fcn(msg="", env="base", refresh=True, saltenv="base", **kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
has been changed to
|
has been changed to
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def fcn(msg='', refresh=True, saltenv='base', **kwargs):
|
def fcn(msg="", refresh=True, saltenv="base", **kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
|
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
|
||||||
that also accepts arbitrary keyword arguments, then a new warning informs the
|
that also accepts arbitrary keyword arguments, then a new warning informs the
|
||||||
|
@ -569,12 +568,13 @@ General Deprecations
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def fcn(msg='', refresh=True, saltenv='base', **kwargs):
|
def fcn(msg="", refresh=True, saltenv="base", **kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# will result in a warning log message
|
# will result in a warning log message
|
||||||
fcn(msg='add more salt', env='prod', refresh=False)
|
fcn(msg="add more salt", env="prod", refresh=False)
|
||||||
|
|
||||||
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
|
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
|
||||||
that does not accept arbitrary keyword arguments, then python will issue an
|
that does not accept arbitrary keyword arguments, then python will issue an
|
||||||
|
@ -582,12 +582,13 @@ General Deprecations
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def fcn(msg='', refresh=True, saltenv='base'):
|
def fcn(msg="", refresh=True, saltenv="base"):
|
||||||
|
...
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# will result in a python TypeError
|
# will result in a python TypeError
|
||||||
fcn(msg='add more salt', env='prod', refresh=False)
|
fcn(msg="add more salt", env="prod", refresh=False)
|
||||||
|
|
||||||
- If ``env`` (or ``__env__``) is supplied as a positional argument to a
|
- If ``env`` (or ``__env__``) is supplied as a positional argument to a
|
||||||
function, then undefined behavior will occur, as the removal of ``env`` and
|
function, then undefined behavior will occur, as the removal of ``env`` and
|
||||||
|
@ -596,12 +597,13 @@ General Deprecations
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def fcn(msg='', refresh=True, saltenv='base'):
|
def fcn(msg="", refresh=True, saltenv="base"):
|
||||||
|
...
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# will result in refresh evaluating to True and saltenv likely not being a string at all
|
# will result in refresh evaluating to True and saltenv likely not being a string at all
|
||||||
fcn('add more salt', 'prod', False)
|
fcn("add more salt", "prod", False)
|
||||||
|
|
||||||
- Deprecations in ``minion.py``:
|
- Deprecations in ``minion.py``:
|
||||||
|
|
||||||
|
|
|
@ -156,12 +156,11 @@ they are being loaded for the correct proxytype, example below:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Only work on proxy
|
Only work on proxy
|
||||||
'''
|
"""
|
||||||
try:
|
try:
|
||||||
if salt.utils.is_proxy() and \
|
if salt.utils.is_proxy() and __opts__["proxy"]["proxytype"] == "ssh_sample":
|
||||||
__opts__['proxy']['proxytype'] == 'ssh_sample':
|
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
@ -185,10 +184,10 @@ to ``__proxy__``. This enables patterns like
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def get_ip(proxy):
|
def get_ip(proxy):
|
||||||
'''
|
"""
|
||||||
Ask the remote device what IP it has
|
Ask the remote device what IP it has
|
||||||
'''
|
"""
|
||||||
return {'ip':proxy['proxymodulename.get_ip']()}
|
return {"ip": proxy["proxymodulename.get_ip"]()}
|
||||||
|
|
||||||
|
|
||||||
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function
|
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function
|
||||||
|
|
|
@ -675,7 +675,7 @@ Additional Features
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ret1 = __salt__['salt.execute']('*', 'mod.fun')
|
ret1 = __salt__["salt.execute"]("*", "mod.fun")
|
||||||
|
|
||||||
New Modules
|
New Modules
|
||||||
===========
|
===========
|
||||||
|
|
|
@ -216,10 +216,10 @@ they failed. Here's some example pseudocode:
|
||||||
|
|
||||||
def myrunner():
|
def myrunner():
|
||||||
...
|
...
|
||||||
do stuff
|
# do stuff
|
||||||
...
|
...
|
||||||
if some_error_condition:
|
if some_error_condition:
|
||||||
__context__['retcode'] = 1
|
__context__["retcode"] = 1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
Variable Update Intervals for Fileserver Backends
|
Variable Update Intervals for Fileserver Backends
|
||||||
|
|
|
@ -29,7 +29,7 @@ occurred:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if something_went_wrong:
|
if something_went_wrong:
|
||||||
__context__['retcode'] = 42
|
__context__["retcode"] = 42
|
||||||
|
|
||||||
This is actually how states signal that they have failed. Different cases
|
This is actually how states signal that they have failed. Different cases
|
||||||
result in different codes being set in the :ref:`__context__ <dunder-context>`
|
result in different codes being set in the :ref:`__context__ <dunder-context>`
|
||||||
|
|
|
@ -140,7 +140,7 @@ something like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
mykey = __salt__['config.get']('mykey')
|
mykey = __salt__["config.get"]("mykey")
|
||||||
|
|
||||||
Templating renderers use a similar construct. To get the ``mykey`` value from
|
Templating renderers use a similar construct. To get the ``mykey`` value from
|
||||||
above in Jinja, you would use:
|
above in Jinja, you would use:
|
||||||
|
@ -175,7 +175,7 @@ in the module as well:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
__func_alias__ = {
|
__func_alias__ = {
|
||||||
'set_': 'set',
|
"set_": "set",
|
||||||
}
|
}
|
||||||
|
|
||||||
This is because ``set`` is a Python built-in, and therefore functions should not
|
This is because ``set`` is a Python built-in, and therefore functions should not
|
||||||
|
|
|
@ -41,7 +41,7 @@ connection object, then that connection object is returned. For instance, the
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
conn = sqlite3.connect(__opts__['spm_db'], isolation_level=None)
|
conn = sqlite3.connect(__opts__["spm_db"], isolation_level=None)
|
||||||
...
|
...
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ This function will not generally be more complex than:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def hash_file(path, hashobj, conn=None):
|
def hash_file(path, hashobj, conn=None):
|
||||||
with salt.utils.files.fopen(path, 'r') as f:
|
with salt.utils.files.fopen(path, "r") as f:
|
||||||
hashobj.update(f.read())
|
hashobj.update(f.read())
|
||||||
return hashobj.hexdigest()
|
return hashobj.hexdigest()
|
||||||
|
|
||||||
|
|
|
@ -384,4 +384,4 @@ longer that 1024 characters. PyYAML enforces these limitations (see here__),
|
||||||
and therefore anything parsed as YAML in Salt is subject to them.
|
and therefore anything parsed as YAML in Salt is subject to them.
|
||||||
|
|
||||||
.. _`YAML Spec`: https://yaml.org/spec/1.2/spec.html#id2792424
|
.. _`YAML Spec`: https://yaml.org/spec/1.2/spec.html#id2792424
|
||||||
.. __: https://github.com/yaml/pyyaml/blob/eb459f8/lib/yaml/scanner.py#L279-L293
|
.. __: https://github.com/yaml/pyyaml/blob/eb459f8/lib/yaml/scanner.py#L279-L293
|
||||||
|
|
|
@ -64,14 +64,14 @@ single URL:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query('http://example.com')
|
salt.utils.http.query("http://example.com")
|
||||||
|
|
||||||
By default the query will be performed with a ``GET`` method. The method can
|
By default the query will be performed with a ``GET`` method. The method can
|
||||||
be overridden with the ``method`` argument:
|
be overridden with the ``method`` argument:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query('http://example.com/delete/url', 'DELETE')
|
salt.utils.http.query("http://example.com/delete/url", "DELETE")
|
||||||
|
|
||||||
When using the ``POST`` method (and others, such as ``PUT``), extra data is usually
|
When using the ``POST`` method (and others, such as ``PUT``), extra data is usually
|
||||||
sent as well. This data can be sent directly (would be URL encoded when necessary),
|
sent as well. This data can be sent directly (would be URL encoded when necessary),
|
||||||
|
@ -80,9 +80,7 @@ or in whatever format is required by the remote server (XML, JSON, plain text, e
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url", method="POST", data=json.dumps(mydict)
|
||||||
method='POST',
|
|
||||||
data=json.dumps(mydict)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Data Formatting and Templating
|
Data Formatting and Templating
|
||||||
|
@ -96,9 +94,7 @@ the file (untemplated):
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url", method="POST", data_file="/srv/salt/somefile.xml"
|
||||||
method='POST',
|
|
||||||
data_file='/srv/salt/somefile.xml'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
To pass through a file that contains jinja + yaml templating (the default):
|
To pass through a file that contains jinja + yaml templating (the default):
|
||||||
|
@ -106,11 +102,11 @@ To pass through a file that contains jinja + yaml templating (the default):
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url",
|
||||||
method='POST',
|
method="POST",
|
||||||
data_file='/srv/salt/somefile.jinja',
|
data_file="/srv/salt/somefile.jinja",
|
||||||
data_render=True,
|
data_render=True,
|
||||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
template_dict={"key1": "value1", "key2": "value2"},
|
||||||
)
|
)
|
||||||
|
|
||||||
To pass through a file that contains mako templating:
|
To pass through a file that contains mako templating:
|
||||||
|
@ -118,12 +114,12 @@ To pass through a file that contains mako templating:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url",
|
||||||
method='POST',
|
method="POST",
|
||||||
data_file='/srv/salt/somefile.mako',
|
data_file="/srv/salt/somefile.mako",
|
||||||
data_render=True,
|
data_render=True,
|
||||||
data_renderer='mako',
|
data_renderer="mako",
|
||||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
template_dict={"key1": "value1", "key2": "value2"},
|
||||||
)
|
)
|
||||||
|
|
||||||
Because this function uses Salt's own rendering system, any Salt renderer can
|
Because this function uses Salt's own rendering system, any Salt renderer can
|
||||||
|
@ -136,21 +132,21 @@ However, this can be changed to ``master`` if necessary.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url",
|
||||||
method='POST',
|
method="POST",
|
||||||
data_file='/srv/salt/somefile.jinja',
|
data_file="/srv/salt/somefile.jinja",
|
||||||
data_render=True,
|
data_render=True,
|
||||||
template_dict={'key1': 'value1', 'key2': 'value2'},
|
template_dict={"key1": "value1", "key2": "value2"},
|
||||||
opts=__opts__
|
opts=__opts__,
|
||||||
)
|
)
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/post/url',
|
"http://example.com/post/url",
|
||||||
method='POST',
|
method="POST",
|
||||||
data_file='/srv/salt/somefile.jinja',
|
data_file="/srv/salt/somefile.jinja",
|
||||||
data_render=True,
|
data_render=True,
|
||||||
template_dict={'key1': 'value1', 'key2': 'value2'},
|
template_dict={"key1": "value1", "key2": "value2"},
|
||||||
node='master'
|
node="master",
|
||||||
)
|
)
|
||||||
|
|
||||||
Headers
|
Headers
|
||||||
|
@ -165,12 +161,12 @@ a Python dict.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com/delete/url',
|
"http://example.com/delete/url",
|
||||||
method='POST',
|
method="POST",
|
||||||
header_file='/srv/salt/headers.jinja',
|
header_file="/srv/salt/headers.jinja",
|
||||||
header_render=True,
|
header_render=True,
|
||||||
header_renderer='jinja',
|
header_renderer="jinja",
|
||||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
template_dict={"key1": "value1", "key2": "value2"},
|
||||||
)
|
)
|
||||||
|
|
||||||
Because much of the data that would be templated between headers and data may be
|
Because much of the data that would be templated between headers and data may be
|
||||||
|
@ -186,9 +182,7 @@ password may be passed in as ``username`` and ``password``, respectively.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com',
|
"http://example.com", username="larry", password="5700g3543v4r",
|
||||||
username='larry',
|
|
||||||
password='5700g3543v4r',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Cookies and Sessions
|
Cookies and Sessions
|
||||||
|
@ -199,10 +193,7 @@ are turned off by default. To turn cookies on, set ``cookies`` to True.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query("http://example.com", cookies=True)
|
||||||
'http://example.com',
|
|
||||||
cookies=True
|
|
||||||
)
|
|
||||||
|
|
||||||
By default cookies are stored in Salt's cache directory, normally
|
By default cookies are stored in Salt's cache directory, normally
|
||||||
``/var/cache/salt``, as a file called ``cookies.txt``. However, this location
|
``/var/cache/salt``, as a file called ``cookies.txt``. However, this location
|
||||||
|
@ -211,9 +202,7 @@ may be changed with the ``cookie_jar`` argument:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com',
|
"http://example.com", cookies=True, cookie_jar="/path/to/cookie_jar.txt"
|
||||||
cookies=True,
|
|
||||||
cookie_jar='/path/to/cookie_jar.txt'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
By default, the format of the cookie jar is LWP (aka, lib-www-perl). This
|
By default, the format of the cookie jar is LWP (aka, lib-www-perl). This
|
||||||
|
@ -223,10 +212,10 @@ format of the cookie jar can be set to Mozilla:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com',
|
"http://example.com",
|
||||||
cookies=True,
|
cookies=True,
|
||||||
cookie_jar='/path/to/cookie_jar.txt',
|
cookie_jar="/path/to/cookie_jar.txt",
|
||||||
cookie_format='mozilla'
|
cookie_format="mozilla",
|
||||||
)
|
)
|
||||||
|
|
||||||
Because Salt commands are normally one-off commands that are piped together,
|
Because Salt commands are normally one-off commands that are piped together,
|
||||||
|
@ -238,9 +227,7 @@ Salt's cache directory, is ``cookies.session.p``. This can also be changed.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com',
|
"http://example.com", persist_session=True, session_cookie_jar="/path/to/jar.p"
|
||||||
persist_session=True,
|
|
||||||
session_cookie_jar='/path/to/jar.p'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
The format of this file is msgpack, which is consistent with much of the rest
|
The format of this file is msgpack, which is consistent with much of the rest
|
||||||
|
@ -265,11 +252,7 @@ these are set in the minion configuration file.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query("http://example.com", opts=__opts__, backend="tornado")
|
||||||
'http://example.com',
|
|
||||||
opts=__opts__,
|
|
||||||
backend='tornado'
|
|
||||||
)
|
|
||||||
|
|
||||||
Return Data
|
Return Data
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
@ -294,10 +277,7 @@ force either JSON or XML decoding, the ``decode_type`` may be set:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query("http://example.com", decode_type="xml")
|
||||||
'http://example.com',
|
|
||||||
decode_type='xml'
|
|
||||||
)
|
|
||||||
|
|
||||||
Once translated, the return dict from ``query()`` will include a dict called
|
Once translated, the return dict from ``query()`` will include a dict called
|
||||||
``dict``.
|
``dict``.
|
||||||
|
@ -307,10 +287,7 @@ turned off.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query("http://example.com", decode=False)
|
||||||
'http://example.com',
|
|
||||||
decode=False
|
|
||||||
)
|
|
||||||
|
|
||||||
If decoding is turned on, and references to JSON or XML cannot be found, then
|
If decoding is turned on, and references to JSON or XML cannot be found, then
|
||||||
this module will default to plain text, and return the undecoded data as
|
this module will default to plain text, and return the undecoded data as
|
||||||
|
@ -321,12 +298,7 @@ as required. However, each must individually be turned on.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query("http://example.com", status=True, headers=True, text=True)
|
||||||
'http://example.com',
|
|
||||||
status=True,
|
|
||||||
headers=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
The return from these will be found in the return dict as ``status``,
|
The return from these will be found in the return dict as ``status``,
|
||||||
``headers`` and ``text``, respectively.
|
``headers`` and ``text``, respectively.
|
||||||
|
@ -341,11 +313,11 @@ to be returned to the user in order to do this.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'http://example.com',
|
"http://example.com",
|
||||||
text=False,
|
text=False,
|
||||||
headers=False,
|
headers=False,
|
||||||
text_out='/path/to/url_download.txt',
|
text_out="/path/to/url_download.txt",
|
||||||
headers_out='/path/to/headers_download.txt',
|
headers_out="/path/to/headers_download.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
SSL Verification
|
SSL Verification
|
||||||
|
@ -356,8 +328,7 @@ debugging purposes, SSL verification can be turned off.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'https://example.com',
|
"https://example.com", verify_ssl=False,
|
||||||
verify_ssl=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
CA Bundles
|
CA Bundles
|
||||||
|
@ -373,8 +344,7 @@ using the ``ca_bundle`` variable.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.query(
|
salt.utils.http.query(
|
||||||
'https://example.com',
|
"https://example.com", ca_bundle="/path/to/ca_bundle.pem",
|
||||||
ca_bundle='/path/to/ca_bundle.pem',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Updating CA Bundles
|
Updating CA Bundles
|
||||||
|
@ -395,8 +365,8 @@ download which is hazardous or does not meet the needs of the user.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt.utils.http.update_ca_bundle(
|
salt.utils.http.update_ca_bundle(
|
||||||
target='/path/to/ca-bundle.crt',
|
target="/path/to/ca-bundle.crt",
|
||||||
source='https://example.com/path/to/ca-bundle.crt',
|
source="https://example.com/path/to/ca-bundle.crt",
|
||||||
opts=__opts__,
|
opts=__opts__,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -423,10 +393,10 @@ otherwise reasonable to add to the bundle file.
|
||||||
salt.utils.http.update_ca_bundle(
|
salt.utils.http.update_ca_bundle(
|
||||||
opts=__opts__,
|
opts=__opts__,
|
||||||
merge_files=[
|
merge_files=[
|
||||||
'/etc/ssl/private_cert_1.pem',
|
"/etc/ssl/private_cert_1.pem",
|
||||||
'/etc/ssl/private_cert_2.pem',
|
"/etc/ssl/private_cert_2.pem",
|
||||||
'/etc/ssl/private_cert_3.pem',
|
"/etc/ssl/private_cert_3.pem",
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -100,31 +100,33 @@ Note how the module encapsulates all of the logic around finding the storage ser
|
||||||
# _modules/storage.py
|
# _modules/storage.py
|
||||||
#!python
|
#!python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Functions related to storage servers.
|
Functions related to storage servers.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
def ips():
|
def ips():
|
||||||
'''
|
"""
|
||||||
Provide a list of all local storage server IPs.
|
Provide a list of all local storage server IPs.
|
||||||
|
|
||||||
CLI Example::
|
CLI Example::
|
||||||
|
|
||||||
salt \* storage.ips
|
salt \* storage.ips
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if __grains__.get('virtual', None) in ['VirtualBox', 'oracle']:
|
if __grains__.get("virtual", None) in ["VirtualBox", "oracle"]:
|
||||||
return ['192.168.33.51', ]
|
return [
|
||||||
|
"192.168.33.51",
|
||||||
|
]
|
||||||
|
|
||||||
colo = __pillar__.get('inventory', {}).get('colo', 'Unknown')
|
colo = __pillar__.get("inventory", {}).get("colo", "Unknown")
|
||||||
return __pillar__.get('storage_servers', {}).get(colo, ['unknown', ])
|
return __pillar__.get("storage_servers", {}).get(colo, ["unknown",])
|
||||||
|
|
||||||
|
|
||||||
def ip():
|
def ip():
|
||||||
'''
|
"""
|
||||||
Select and return a local storage server IP.
|
Select and return a local storage server IP.
|
||||||
|
|
||||||
This loadbalances across storage servers by using the modulus of the client's id number.
|
This loadbalances across storage servers by using the modulus of the client's id number.
|
||||||
|
@ -138,12 +140,12 @@ Note how the module encapsulates all of the logic around finding the storage ser
|
||||||
|
|
||||||
salt \* storage.ip
|
salt \* storage.ip
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
numerical_suffix = re.compile(r'^.*(\d+)$')
|
numerical_suffix = re.compile(r"^.*(\d+)$")
|
||||||
servers_list = ips()
|
servers_list = ips()
|
||||||
|
|
||||||
m = numerical_suffix.match(__grains__['id'])
|
m = numerical_suffix.match(__grains__["id"])
|
||||||
if m:
|
if m:
|
||||||
modulus = len(servers_list)
|
modulus = len(servers_list)
|
||||||
server_number = int(m.group(1))
|
server_number = int(m.group(1))
|
||||||
|
|
|
@ -11,7 +11,7 @@ The salt loader was enhanced to look for external modules by looking at the
|
||||||
`salt.loader` entry-point:
|
`salt.loader` entry-point:
|
||||||
|
|
||||||
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
|
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
|
||||||
|
|
||||||
`pkg_resources` should be installed, which is normally included in setuptools.
|
`pkg_resources` should be installed, which is normally included in setuptools.
|
||||||
|
|
||||||
https://setuptools.readthedocs.io/en/latest/pkg_resources.html
|
https://setuptools.readthedocs.io/en/latest/pkg_resources.html
|
||||||
|
@ -24,21 +24,23 @@ function:
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(name=<NAME>,
|
setup(
|
||||||
version=<VERSION>,
|
name=<NAME>,
|
||||||
description=<DESC>,
|
version=<VERSION>,
|
||||||
author=<AUTHOR>,
|
description=<DESC>,
|
||||||
author_email=<AUTHOR-EMAIL>,
|
author=<AUTHOR>,
|
||||||
url=' ... ',
|
author_email=<AUTHOR-EMAIL>,
|
||||||
packages=find_packages(),
|
url=" ... ",
|
||||||
entry_points='''
|
packages=find_packages(),
|
||||||
[salt.loader]
|
entry_points="""
|
||||||
engines_dirs = <package>.<loader-module>:engines_dirs
|
[salt.loader]
|
||||||
fileserver_dirs = <package>.<loader-module>:fileserver_dirs
|
engines_dirs = <package>.<loader-module>:engines_dirs
|
||||||
pillar_dirs = <package>.<loader-module>:pillar_dirs
|
fileserver_dirs = <package>.<loader-module>:fileserver_dirs
|
||||||
returner_dirs = <package>.<loader-module>:returner_dirs
|
pillar_dirs = <package>.<loader-module>:pillar_dirs
|
||||||
roster_dirs = <package>.<loader-module>:roster_dirs
|
returner_dirs = <package>.<loader-module>:returner_dirs
|
||||||
''')
|
roster_dirs = <package>.<loader-module>:roster_dirs
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
The above setup script example mentions a loader module. here's an example of
|
The above setup script example mentions a loader module. here's an example of
|
||||||
|
|
|
@ -37,22 +37,24 @@ Edit it to include needed variables and your new paths
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# you need to edit this
|
# you need to edit this
|
||||||
ROOT_DIR = *your current dir* + '/salt/root'
|
_your_current_dir_ = ...
|
||||||
|
ROOT_DIR = _your_current_dir_ + "/salt/root"
|
||||||
|
|
||||||
# you need to edit this
|
# you need to edit this
|
||||||
INSTALL_DIR = *location of source code*
|
_location_of_source_code_ = ...
|
||||||
|
INSTALL_DIR = _location_of_source_code_
|
||||||
|
|
||||||
CONFIG_DIR = ROOT_DIR + '/etc/salt'
|
CONFIG_DIR = ROOT_DIR + "/etc/salt"
|
||||||
CACHE_DIR = ROOT_DIR + '/var/cache/salt'
|
CACHE_DIR = ROOT_DIR + "/var/cache/salt"
|
||||||
SOCK_DIR = ROOT_DIR + '/var/run/salt'
|
SOCK_DIR = ROOT_DIR + "/var/run/salt"
|
||||||
SRV_ROOT_DIR= ROOT_DIR + '/srv'
|
SRV_ROOT_DIR = ROOT_DIR + "/srv"
|
||||||
BASE_FILE_ROOTS_DIR = ROOT_DIR + '/srv/salt'
|
BASE_FILE_ROOTS_DIR = ROOT_DIR + "/srv/salt"
|
||||||
BASE_PILLAR_ROOTS_DIR = ROOT_DIR + '/srv/pillar'
|
BASE_PILLAR_ROOTS_DIR = ROOT_DIR + "/srv/pillar"
|
||||||
BASE_MASTER_ROOTS_DIR = ROOT_DIR + '/srv/salt-master'
|
BASE_MASTER_ROOTS_DIR = ROOT_DIR + "/srv/salt-master"
|
||||||
LOGS_DIR = ROOT_DIR + '/var/log/salt'
|
LOGS_DIR = ROOT_DIR + "/var/log/salt"
|
||||||
PIDFILE_DIR = ROOT_DIR + '/var/run'
|
PIDFILE_DIR = ROOT_DIR + "/var/run"
|
||||||
CLOUD_DIR = INSTALL_DIR + '/cloud'
|
CLOUD_DIR = INSTALL_DIR + "/cloud"
|
||||||
BOOTSTRAP = CLOUD_DIR + '/deploy/bootstrap-salt.sh'
|
BOOTSTRAP = CLOUD_DIR + "/deploy/bootstrap-salt.sh"
|
||||||
|
|
||||||
|
|
||||||
Create the directory structure
|
Create the directory structure
|
||||||
|
|
|
@ -484,12 +484,12 @@ This example shows a very basic Python SLS file:
|
||||||
|
|
||||||
#!py
|
#!py
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
'''
|
"""
|
||||||
Install the django package
|
Install the django package
|
||||||
'''
|
"""
|
||||||
return {'include': ['python'],
|
return {"include": ["python"], "django": {"pkg": ["installed"]}}
|
||||||
'django': {'pkg': ['installed']}}
|
|
||||||
|
|
||||||
This is a very simple example; the first line has an SLS shebang that
|
This is a very simple example; the first line has an SLS shebang that
|
||||||
tells Salt to not use the default renderer, but to use the ``py`` renderer.
|
tells Salt to not use the default renderer, but to use the ``py`` renderer.
|
||||||
|
@ -504,8 +504,8 @@ renderer, the above example can be written more succinctly as:
|
||||||
|
|
||||||
#!pydsl
|
#!pydsl
|
||||||
|
|
||||||
include('python', delayed=True)
|
include("python", delayed=True)
|
||||||
state('django').pkg.installed()
|
state("django").pkg.installed()
|
||||||
|
|
||||||
The :mod:`pyobjects<salt.renderers.pyobjects>` renderer
|
The :mod:`pyobjects<salt.renderers.pyobjects>` renderer
|
||||||
provides an `"Pythonic"`_ object based approach for building the state data.
|
provides an `"Pythonic"`_ object based approach for building the state data.
|
||||||
|
@ -515,7 +515,7 @@ The above example could be written as:
|
||||||
|
|
||||||
#!pyobjects
|
#!pyobjects
|
||||||
|
|
||||||
include('python')
|
include("python")
|
||||||
Pkg.installed("django")
|
Pkg.installed("django")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,8 @@ a value equivalent to the following python pseudo-code:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import salt.modules.file
|
import salt.modules.file
|
||||||
file.group_to_gid('some_group_that_exists')
|
|
||||||
|
file.group_to_gid("some_group_that_exists")
|
||||||
|
|
||||||
Note that for the above example to work, ``some_group_that_exists`` must exist
|
Note that for the above example to work, ``some_group_that_exists`` must exist
|
||||||
before the state file is processed by the templating engine.
|
before the state file is processed by the templating engine.
|
||||||
|
@ -156,7 +157,7 @@ MAC address for eth0:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
salt['network.hw_addr']('eth0')
|
salt["network.hw_addr"]("eth0")
|
||||||
|
|
||||||
To examine the possible arguments to each execution module function,
|
To examine the possible arguments to each execution module function,
|
||||||
one can examine the `module reference documentation </ref/modules/all>`_:
|
one can examine the `module reference documentation </ref/modules/all>`_:
|
||||||
|
|
|
@ -139,10 +139,10 @@ file:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_ping(self):
|
def test_ping(self):
|
||||||
'''
|
"""
|
||||||
test.ping
|
test.ping
|
||||||
'''
|
"""
|
||||||
self.assertTrue(self.run_function('test.ping'))
|
self.assertTrue(self.run_function("test.ping"))
|
||||||
|
|
||||||
The test above is a very simple example where the ``test.ping`` function is
|
The test above is a very simple example where the ``test.ping`` function is
|
||||||
executed by Salt's test suite runner and is asserting that the minion returned
|
executed by Salt's test suite runner and is asserting that the minion returned
|
||||||
|
@ -254,20 +254,20 @@ minion's return is expected.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_ping(self):
|
def test_ping(self):
|
||||||
'''
|
"""
|
||||||
test.ping
|
test.ping
|
||||||
'''
|
"""
|
||||||
self.assertTrue(self.run_function('test.ping'))
|
self.assertTrue(self.run_function("test.ping"))
|
||||||
|
|
||||||
Args can be passed in to the ``run_function`` method as well:
|
Args can be passed in to the ``run_function`` method as well:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_echo(self):
|
def test_echo(self):
|
||||||
'''
|
"""
|
||||||
test.echo
|
test.echo
|
||||||
'''
|
"""
|
||||||
self.assertEqual(self.run_function('test.echo', ['text']), 'text')
|
self.assertEqual(self.run_function("test.echo", ["text"]), "text")
|
||||||
|
|
||||||
The next example is taken from the
|
The next example is taken from the
|
||||||
``tests/integration/modules/test_aliases.py`` file and
|
``tests/integration/modules/test_aliases.py`` file and
|
||||||
|
@ -279,18 +279,13 @@ call should return.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_set_target(self):
|
def test_set_target(self):
|
||||||
'''
|
"""
|
||||||
aliases.set_target and aliases.get_target
|
aliases.set_target and aliases.get_target
|
||||||
'''
|
"""
|
||||||
set_ret = self.run_function(
|
set_ret = self.run_function("aliases.set_target", alias="fred", target="bob")
|
||||||
'aliases.set_target',
|
|
||||||
alias='fred',
|
|
||||||
target='bob')
|
|
||||||
self.assertTrue(set_ret)
|
self.assertTrue(set_ret)
|
||||||
tgt_ret = self.run_function(
|
tgt_ret = self.run_function("aliases.get_target", alias="fred")
|
||||||
'aliases.get_target',
|
self.assertEqual(tgt_ret, "bob")
|
||||||
alias='fred')
|
|
||||||
self.assertEqual(tgt_ret, 'bob')
|
|
||||||
|
|
||||||
Using multiple Salt commands in this manner provides two useful benefits. The first is
|
Using multiple Salt commands in this manner provides two useful benefits. The first is
|
||||||
that it provides some additional coverage for the ``aliases.set_target`` function.
|
that it provides some additional coverage for the ``aliases.set_target`` function.
|
||||||
|
@ -335,12 +330,13 @@ the test method:
|
||||||
import integration
|
import integration
|
||||||
from tests.support.helpers import destructiveTest
|
from tests.support.helpers import destructiveTest
|
||||||
|
|
||||||
|
|
||||||
class PkgTest(integration.ModuleCase):
|
class PkgTest(integration.ModuleCase):
|
||||||
@destructiveTest
|
@destructiveTest
|
||||||
def test_pkg_install(self):
|
def test_pkg_install(self):
|
||||||
ret = self.run_function('pkg.install', name='finch')
|
ret = self.run_function("pkg.install", name="finch")
|
||||||
self.assertSaltTrueReturn(ret)
|
self.assertSaltTrueReturn(ret)
|
||||||
ret = self.run_function('pkg.purge', name='finch')
|
ret = self.run_function("pkg.purge", name="finch")
|
||||||
self.assertSaltTrueReturn(ret)
|
self.assertSaltTrueReturn(ret)
|
||||||
|
|
||||||
|
|
||||||
|
@ -372,13 +368,13 @@ testing the call to ``cp.hash_file``, which is used in ``cp.get_file``.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_get_file_not_found(self):
|
def test_get_file_not_found(self):
|
||||||
'''
|
"""
|
||||||
Test if get_file can't find the file.
|
Test if get_file can't find the file.
|
||||||
'''
|
"""
|
||||||
with patch('salt.modules.cp.hash_file', MagicMock(return_value=False)):
|
with patch("salt.modules.cp.hash_file", MagicMock(return_value=False)):
|
||||||
path = 'salt://saltines'
|
path = "salt://saltines"
|
||||||
dest = '/srv/salt/cheese'
|
dest = "/srv/salt/cheese"
|
||||||
ret = ''
|
ret = ""
|
||||||
assert cp.get_file(path, dest) == ret
|
assert cp.get_file(path, dest) == ret
|
||||||
|
|
||||||
Note that Salt's ``cp`` module is imported at the top of the file, along with all
|
Note that Salt's ``cp`` module is imported at the top of the file, along with all
|
||||||
|
@ -446,10 +442,10 @@ can be used
|
||||||
# .. inside test
|
# .. inside test
|
||||||
with TstSuiteLoggingHandler() as handler:
|
with TstSuiteLoggingHandler() as handler:
|
||||||
for message in handler.messages:
|
for message in handler.messages:
|
||||||
if message.startswith('ERROR: This is the error message we seek'):
|
if message.startswith("ERROR: This is the error message we seek"):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise AssertionError('Did not find error message')
|
raise AssertionError("Did not find error message")
|
||||||
|
|
||||||
|
|
||||||
Automated Test Runs
|
Automated Test Runs
|
||||||
|
|
|
@ -23,15 +23,16 @@ For example, assuming the following simple utility module, saved to
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
My utils module
|
My utils module
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
This module contains common functions for use in my other custom types.
|
This module contains common functions for use in my other custom types.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
def bar():
|
||||||
return 'baz'
|
return "baz"
|
||||||
|
|
||||||
Once synced to a minion, this function would be available to other custom Salt
|
Once synced to a minion, this function would be available to other custom Salt
|
||||||
types like so:
|
types like so:
|
||||||
|
@ -39,13 +40,14 @@ types like so:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
My awesome execution module
|
My awesome execution module
|
||||||
---------------------------
|
---------------------------
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def observe_the_awesomeness():
|
def observe_the_awesomeness():
|
||||||
'''
|
"""
|
||||||
Prints information from my utility module
|
Prints information from my utility module
|
||||||
|
|
||||||
CLI Example:
|
CLI Example:
|
||||||
|
@ -53,8 +55,8 @@ types like so:
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
salt '*' mymodule.observe_the_awesomeness
|
salt '*' mymodule.observe_the_awesomeness
|
||||||
'''
|
"""
|
||||||
return __utils__['foo.bar']()
|
return __utils__["foo.bar"]()
|
||||||
|
|
||||||
Utility modules, like any other kind of Salt extension, support using a
|
Utility modules, like any other kind of Salt extension, support using a
|
||||||
:ref:`__virtual__ function <modules-virtual-name>` to conditionally load them,
|
:ref:`__virtual__ function <modules-virtual-name>` to conditionally load them,
|
||||||
|
@ -65,21 +67,23 @@ the ``foo`` utility module with a ``__virtual__`` function.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
My utils module
|
My utils module
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
This module contains common functions for use in my other custom types.
|
This module contains common functions for use in my other custom types.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
'''
|
"""
|
||||||
Load as a different name
|
Load as a different name
|
||||||
'''
|
"""
|
||||||
return 'foo'
|
return "foo"
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
def bar():
|
||||||
return 'baz'
|
return "baz"
|
||||||
|
|
||||||
.. versionadded:: 2018.3.0
|
.. versionadded:: 2018.3.0
|
||||||
Instantiating objects from classes declared in util modules works with
|
Instantiating objects from classes declared in util modules works with
|
||||||
|
@ -90,35 +94,36 @@ Also you could even write your utility modules in object oriented fashion:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
My OOP-style utils module
|
My OOP-style utils module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
This module contains common functions for use in my other custom types.
|
This module contains common functions for use in my other custom types.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Foo(object):
|
class Foo(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bar(self):
|
def bar(self):
|
||||||
return 'baz'
|
return "baz"
|
||||||
|
|
||||||
And import them into other custom modules:
|
And import them into other custom modules:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
"""
|
||||||
My awesome execution module
|
My awesome execution module
|
||||||
---------------------------
|
---------------------------
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import mymodule
|
import mymodule
|
||||||
|
|
||||||
|
|
||||||
def observe_the_awesomeness():
|
def observe_the_awesomeness():
|
||||||
'''
|
"""
|
||||||
Prints information from my utility module
|
Prints information from my utility module
|
||||||
|
|
||||||
CLI Example:
|
CLI Example:
|
||||||
|
@ -126,7 +131,7 @@ And import them into other custom modules:
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
salt '*' mymodule.observe_the_awesomeness
|
salt '*' mymodule.observe_the_awesomeness
|
||||||
'''
|
"""
|
||||||
foo = mymodule.Foo()
|
foo = mymodule.Foo()
|
||||||
return foo.bar()
|
return foo.bar()
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ In Python, the above maps to:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'my_key': 'my_value'}
|
{"my_key": "my_value"}
|
||||||
|
|
||||||
Alternatively, a value can be associated with a key through indentation.
|
Alternatively, a value can be associated with a key through indentation.
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ In Python, the above maps to:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'my_key': 'my_value'}
|
{"my_key": "my_value"}
|
||||||
|
|
||||||
Dictionaries can be nested:
|
Dictionaries can be nested:
|
||||||
|
|
||||||
|
@ -71,11 +71,7 @@ And in Python:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{
|
{"first_level_dict_key": {"second_level_dict_key": "value_in_second_level_dict"}}
|
||||||
'first_level_dict_key': {
|
|
||||||
'second_level_dict_key': 'value_in_second_level_dict'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rule Three: Dashes
|
Rule Three: Dashes
|
||||||
------------------
|
------------------
|
||||||
|
@ -102,7 +98,7 @@ In Python, the above maps to:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
{'my_dictionary': ['list_value_one', 'list_value_two', 'list_value_three']}
|
{"my_dictionary": ["list_value_one", "list_value_two", "list_value_three"]}
|
||||||
|
|
||||||
Learning More
|
Learning More
|
||||||
-------------
|
-------------
|
||||||
|
|
Loading…
Add table
Reference in a new issue