mirror of
https://github.com/saltstack/salt.git
synced 2025-04-15 17:20:19 +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
|
||||
rev: stable
|
||||
rev: 19.10b0
|
||||
hooks:
|
||||
- id: black
|
||||
# This tells pre-commit not to pass files to black.
|
||||
|
@ -717,6 +717,13 @@ repos:
|
|||
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
|
||||
rev: master
|
||||
|
|
|
@ -183,10 +183,10 @@ custom LogRecord attributes to colorize console log output:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'%(colorlevel)s' # log level name colorized by level
|
||||
'%(colorname)s' # colorized module name
|
||||
'%(colorprocess)s' # colorized process number
|
||||
'%(colormsg)s' # log message colorized by level
|
||||
"%(colorlevel)s" # log level name colorized by level
|
||||
"%(colorname)s" # colorized module name
|
||||
"%(colorprocess)s" # colorized process number
|
||||
"%(colormsg)s" # log message colorized by level
|
||||
|
||||
.. note::
|
||||
The ``%(colorlevel)s``, ``%(colorname)s``, and ``%(colorprocess)``
|
||||
|
@ -212,9 +212,9 @@ these custom LogRecord attributes that include padding and enclosing brackets
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'%(bracketlevel)s' # equivalent to [%(levelname)-8s]
|
||||
'%(bracketname)s' # equivalent to [%(name)-17s]
|
||||
'%(bracketprocess)s' # equivalent to [%(process)5s]
|
||||
"%(bracketlevel)s" # equivalent to [%(levelname)-8s]
|
||||
"%(bracketname)s" # equivalent to [%(name)-17s]
|
||||
"%(bracketprocess)s" # equivalent to [%(process)5s]
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
|
|
@ -876,10 +876,11 @@ For example, with these custom grains functions:
|
|||
.. code-block:: python
|
||||
|
||||
def custom1_k1():
|
||||
return {'custom1': {'k1': 'v1'}}
|
||||
return {"custom1": {"k1": "v1"}}
|
||||
|
||||
|
||||
def custom1_k2():
|
||||
return {'custom1': {'k2': 'v2'}}
|
||||
return {"custom1": {"k2": "v2"}}
|
||||
|
||||
Without ``grains_deep_merge``, the result would be:
|
||||
|
||||
|
@ -2710,8 +2711,7 @@ the ``extra_minion_data`` parameter will be
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'opt1': 'value1',
|
||||
'opt2': {'subopt1': 'value2'}}
|
||||
{"opt1": "value1", "opt2": {"subopt1": "value2"}}
|
||||
|
||||
Security Settings
|
||||
=================
|
||||
|
|
|
@ -59,7 +59,8 @@ the ``execute`` function with the following signature:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def execute(opts, data, func, args, kwargs)
|
||||
def execute(opts, data, func, args, kwargs):
|
||||
...
|
||||
|
||||
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.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
|
||||
|
||||
CLI Example:
|
||||
salt '*' cp.get_file salt://vimrc /etc/vimrc
|
||||
'''
|
||||
"""
|
||||
# Get the fileclient object
|
||||
client = salt.fileclient.get_file_client(__opts__)
|
||||
# Call get_file
|
||||
|
@ -153,12 +154,13 @@ data is not available, it needs to be generated:
|
|||
import salt.fileclient
|
||||
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
|
||||
'''
|
||||
"""
|
||||
# Get the configuration data
|
||||
opts = salt.config.minion_config('/etc/salt/minion')
|
||||
opts = salt.config.minion_config("/etc/salt/minion")
|
||||
# Get the fileclient object
|
||||
client = salt.fileclient.get_file_client(opts)
|
||||
# Call get_file
|
||||
|
|
|
@ -25,7 +25,8 @@ for a minion instance:
|
|||
.. code-block:: python
|
||||
|
||||
import salt.config
|
||||
opts = salt.config.minion_config('/etc/salt/minion')
|
||||
|
||||
opts = salt.config.minion_config("/etc/salt/minion")
|
||||
print(opts)
|
||||
|
||||
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
|
||||
|
||||
import salt.config
|
||||
opts = salt.config.master_config('/etc/salt/master')
|
||||
|
||||
opts = salt.config.master_config("/etc/salt/master")
|
||||
print(opts)
|
||||
|
|
|
@ -92,7 +92,7 @@ included libraries.
|
|||
|
||||
|
||||
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)
|
||||
|
||||
Then, create the zip:
|
||||
|
@ -154,7 +154,7 @@ dict:
|
|||
.. code-block:: python
|
||||
|
||||
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>`
|
||||
module and pass the argument ``bar`` to it.
|
||||
|
@ -229,14 +229,16 @@ as also available in the ``__opts__`` dict.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'''
|
||||
"""
|
||||
Cheese module initialization example
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def __init__(opts):
|
||||
'''
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
|
@ -266,9 +268,7 @@ names to Salt :ref:`outputters <all-salt.output>`.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
__outputter__ = {
|
||||
'run': 'txt'
|
||||
}
|
||||
__outputter__ = {"run": "txt"}
|
||||
|
||||
This will ensure that the ``txt`` outputter is used to display output from the
|
||||
``run`` function.
|
||||
|
@ -326,44 +326,50 @@ the case when the dependency is unavailable.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'''
|
||||
"""
|
||||
Cheese execution (or returner/beacon/etc.) module
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
import enzymes
|
||||
|
||||
HAS_ENZYMES = True
|
||||
except ImportError:
|
||||
HAS_ENZYMES = False
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
only load cheese if enzymes are available
|
||||
'''
|
||||
"""
|
||||
if HAS_ENZYMES:
|
||||
return 'cheese'
|
||||
return "cheese"
|
||||
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():
|
||||
pass
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
'''
|
||||
"""
|
||||
Cheese state module. Note that this works in state modules because it is
|
||||
guaranteed that execution modules are loaded first
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
only load cheese if enzymes are available
|
||||
'''
|
||||
"""
|
||||
# predicate loading of the cheese state on the corresponding execution module
|
||||
if 'cheese.slice' in __salt__:
|
||||
return 'cheese'
|
||||
if "cheese.slice" in __salt__:
|
||||
return "cheese"
|
||||
else:
|
||||
return False, 'The cheese state module cannot be loaded: enzymes unavailable.'
|
||||
return False, "The cheese state module cannot be loaded: enzymes unavailable."
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -445,15 +451,15 @@ similar to the following:
|
|||
.. code-block:: python
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'pkg'
|
||||
__virtualname__ = "pkg"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
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 False
|
||||
|
||||
|
@ -472,12 +478,11 @@ For example:
|
|||
.. code-block:: python
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Only load if git exists on the system
|
||||
'''
|
||||
if salt.utils.path.which('git') is None:
|
||||
return (False,
|
||||
'The git execution module cannot be loaded: git unavailable.')
|
||||
"""
|
||||
if salt.utils.path.which("git") is None:
|
||||
return (False, "The git execution module cannot be loaded: git unavailable.")
|
||||
else:
|
||||
return True
|
||||
|
||||
|
@ -505,13 +510,13 @@ To add documentation add a `Python docstring`_ to the function.
|
|||
.. code-block:: python
|
||||
|
||||
def spam(eggs):
|
||||
'''
|
||||
"""
|
||||
A function to make some spam with eggs!
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' test.spam eggs
|
||||
'''
|
||||
"""
|
||||
return eggs
|
||||
|
||||
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.info('Here is Some Information')
|
||||
log.warning('You Should Not Do That')
|
||||
log.error('It Is Busted')
|
||||
log.info("Here is Some Information")
|
||||
log.warning("You Should Not Do That")
|
||||
log.error("It Is Busted")
|
||||
|
||||
Aliasing Functions
|
||||
==================
|
||||
|
@ -580,8 +585,8 @@ module, or from the cli, the alias name should be used.
|
|||
.. code-block:: python
|
||||
|
||||
__func_alias__ = {
|
||||
'set_': 'set',
|
||||
'list_': 'list',
|
||||
"set_": "set",
|
||||
"list_": "list",
|
||||
}
|
||||
|
||||
Private Functions
|
||||
|
@ -604,10 +609,11 @@ Objects NOT Loaded into the Salt Minion
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def _foobar(baz): # Preceded with an _
|
||||
def _foobar(baz): # Preceded with an _
|
||||
return baz
|
||||
|
||||
cheese = {} # Not a callable Python object
|
||||
|
||||
cheese = {} # Not a callable Python object
|
||||
|
||||
Useful Decorators for Modules
|
||||
=============================
|
||||
|
@ -640,29 +646,32 @@ removing it
|
|||
try:
|
||||
import dependency_that_sometimes_exists
|
||||
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():
|
||||
'''
|
||||
"""
|
||||
Function with a dependency on the "dependency_that_sometimes_exists" module,
|
||||
if the "dependency_that_sometimes_exists" is missing this function will not exist
|
||||
'''
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
def _fallback():
|
||||
'''
|
||||
"""
|
||||
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'
|
||||
|
||||
@depends('dependency_that_sometimes_exists', fallback_function=_fallback)
|
||||
|
||||
@depends("dependency_that_sometimes_exists", fallback_function=_fallback)
|
||||
def foo():
|
||||
'''
|
||||
"""
|
||||
Function with a dependency on the "dependency_that_sometimes_exists" module.
|
||||
If the "dependency_that_sometimes_exists" is missing this function will be
|
||||
replaced with "_fallback"
|
||||
'''
|
||||
"""
|
||||
return True
|
||||
|
||||
In addition to global dependencies the depends decorator also supports raw
|
||||
|
@ -675,10 +684,12 @@ booleans.
|
|||
HAS_DEP = False
|
||||
try:
|
||||
import dependency_that_sometimes_exists
|
||||
|
||||
HAS_DEP = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@depends(HAS_DEP)
|
||||
def foo():
|
||||
return True
|
||||
|
|
|
@ -40,7 +40,7 @@ In Python, the above maps to:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'my_key': 'my_value'}
|
||||
{"my_key": "my_value"}
|
||||
|
||||
Dictionaries can be nested:
|
||||
|
||||
|
@ -53,7 +53,7 @@ And in 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
|
||||
------------------
|
||||
|
|
|
@ -73,17 +73,14 @@ to install a package:
|
|||
|
||||
#!py
|
||||
|
||||
|
||||
def run():
|
||||
'''
|
||||
"""
|
||||
Install version 1.5-1.el7 of package "python-foo"
|
||||
'''
|
||||
"""
|
||||
return {
|
||||
'include': ['python'],
|
||||
'python-foo': {
|
||||
'pkg.installed': [
|
||||
{'version': '1.5-1.el7'},
|
||||
]
|
||||
}
|
||||
"include": ["python"],
|
||||
"python-foo": {"pkg.installed": [{"version": "1.5-1.el7"},]},
|
||||
}
|
||||
|
||||
This would be equivalent to the following:
|
||||
|
@ -185,7 +182,8 @@ strings or file-like objects as input. For example:
|
|||
import mycoolmodule
|
||||
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):
|
||||
# Read from file-like object
|
||||
data = data.read()
|
||||
|
@ -227,7 +225,8 @@ Here is a simple YAML renderer example:
|
|||
from salt.utils.yamlloader import SaltYamlSafeLoader
|
||||
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):
|
||||
yaml_data = yaml_data.read()
|
||||
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 salt.utils.json
|
||||
|
||||
|
||||
def returner(ret):
|
||||
'''
|
||||
"""
|
||||
Return information to a redis server
|
||||
'''
|
||||
"""
|
||||
# Get a redis connection
|
||||
serv = redis.Redis(
|
||||
host='redis-serv.example.com',
|
||||
port=6379,
|
||||
db='0')
|
||||
serv.sadd("%(id)s:jobs" % ret, ret['jid'])
|
||||
serv.set("%(jid)s:%(id)s" % ret, salt.utils.json.dumps(ret['return']))
|
||||
serv.sadd('jobs', ret['jid'])
|
||||
serv.sadd(ret['jid'], ret['id'])
|
||||
serv = redis.Redis(host="redis-serv.example.com", port=6379, db="0")
|
||||
serv.sadd("%(id)s:jobs" % ret, ret["jid"])
|
||||
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
|
||||
serializes the data as JSON and sets it in redis.
|
||||
|
@ -120,11 +118,13 @@ loaded as simply ``redis``:
|
|||
|
||||
try:
|
||||
import redis
|
||||
|
||||
HAS_REDIS = True
|
||||
except ImportError:
|
||||
HAS_REDIS = False
|
||||
|
||||
__virtualname__ = 'redis'
|
||||
__virtualname__ = "redis"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
if not HAS_REDIS:
|
||||
|
@ -154,9 +154,9 @@ must implement the following functions:
|
|||
.. code-block:: python
|
||||
|
||||
def prep_jid(nocache, passed_jid=None): # pylint: disable=unused-argument
|
||||
'''
|
||||
"""
|
||||
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()
|
||||
|
||||
``save_load``
|
||||
|
@ -171,26 +171,27 @@ must implement the following functions:
|
|||
|
||||
import salt.utils.json
|
||||
|
||||
|
||||
def save_load(jid, load, minions=None):
|
||||
'''
|
||||
"""
|
||||
Save the load to the specified jid id
|
||||
'''
|
||||
query = '''INSERT INTO salt.jids (
|
||||
"""
|
||||
query = """INSERT INTO salt.jids (
|
||||
jid, load
|
||||
) VALUES (
|
||||
'{0}', '{1}'
|
||||
);'''.format(jid, salt.utils.json.dumps(load))
|
||||
);""".format(
|
||||
jid, salt.utils.json.dumps(load)
|
||||
)
|
||||
|
||||
# cassandra_cql.cql_query may raise a CommandExecutionError
|
||||
try:
|
||||
__salt__['cassandra_cql.cql_query'](query)
|
||||
__salt__["cassandra_cql.cql_query"](query)
|
||||
except CommandExecutionError:
|
||||
log.critical('Could not save load in jids table.')
|
||||
log.critical("Could not save load in jids table.")
|
||||
raise
|
||||
except Exception as e:
|
||||
log.critical(
|
||||
'Unexpected error while inserting into jids: {0}'.format(e)
|
||||
)
|
||||
log.critical("Unexpected error while inserting into jids: {0}".format(e))
|
||||
raise
|
||||
|
||||
|
||||
|
@ -201,26 +202,30 @@ must implement the following functions:
|
|||
.. code-block:: python
|
||||
|
||||
def get_load(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 = {}
|
||||
|
||||
# cassandra_cql.cql_query may raise a CommandExecutionError
|
||||
try:
|
||||
data = __salt__['cassandra_cql.cql_query'](query)
|
||||
data = __salt__["cassandra_cql.cql_query"](query)
|
||||
if data:
|
||||
load = data[0].get('load')
|
||||
load = data[0].get("load")
|
||||
if load:
|
||||
ret = json.loads(load)
|
||||
except CommandExecutionError:
|
||||
log.critical('Could not get load from jids table.')
|
||||
log.critical("Could not get load from jids table.")
|
||||
raise
|
||||
except Exception as e:
|
||||
log.critical('''Unexpected error while getting load from
|
||||
jids: {0}'''.format(str(e)))
|
||||
log.critical(
|
||||
"""Unexpected error while getting load from
|
||||
jids: {0}""".format(
|
||||
str(e)
|
||||
)
|
||||
)
|
||||
raise
|
||||
|
||||
return ret
|
||||
|
@ -322,20 +327,21 @@ contains the jid and therefore is guaranteed to be unique.
|
|||
|
||||
import salt.utils.json
|
||||
|
||||
def event_return(events):
|
||||
'''
|
||||
Return event to mysql server
|
||||
|
||||
Requires that configuration be enabled via 'event_return'
|
||||
option in master config.
|
||||
'''
|
||||
with _get_serv(events, commit=True) as cur:
|
||||
for event in events:
|
||||
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']))
|
||||
def event_return(events):
|
||||
"""
|
||||
Return event to mysql server
|
||||
|
||||
Requires that configuration be enabled via 'event_return'
|
||||
option in master config.
|
||||
"""
|
||||
with _get_serv(events, commit=True) as cur:
|
||||
for event in events:
|
||||
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
|
||||
|
|
|
@ -36,7 +36,7 @@ fired onto the master event bus where. For example:
|
|||
.. code-block:: python
|
||||
|
||||
def a_runner(outputter=None, display_progress=False):
|
||||
print('Hello world')
|
||||
print("Hello world")
|
||||
...
|
||||
|
||||
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
|
||||
|
||||
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``
|
||||
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.client
|
||||
|
||||
|
||||
def up():
|
||||
'''
|
||||
"""
|
||||
Print a list of all of the minions that are up
|
||||
'''
|
||||
client = salt.client.LocalClient(__opts__['conf_file'])
|
||||
minions = client.cmd('*', 'test.version', timeout=1)
|
||||
"""
|
||||
client = salt.client.LocalClient(__opts__["conf_file"])
|
||||
minions = client.cmd("*", "test.version", timeout=1)
|
||||
for minion in sorted(minions):
|
||||
print minion
|
||||
|
|
|
@ -102,48 +102,48 @@ This example, simplified from the pkg state, shows how to create mod_aggregate f
|
|||
.. code-block:: python
|
||||
|
||||
def mod_aggregate(low, chunks, running):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
'''
|
||||
"""
|
||||
pkgs = []
|
||||
# What functions should we aggregate?
|
||||
agg_enabled = [
|
||||
'installed',
|
||||
'latest',
|
||||
'removed',
|
||||
'purged',
|
||||
]
|
||||
"installed",
|
||||
"latest",
|
||||
"removed",
|
||||
"purged",
|
||||
]
|
||||
# The `low` data is just a dict with the state, function (fun) and
|
||||
# arguments passed in from the sls
|
||||
if low.get('fun') not in agg_enabled:
|
||||
if low.get("fun") not in agg_enabled:
|
||||
return low
|
||||
# Now look into what other things are set to execute
|
||||
for chunk in chunks:
|
||||
# The state runtime uses "tags" to track completed jobs, it may
|
||||
# look familiar with the _|-
|
||||
tag = __utils__['state.gen_tag'](chunk)
|
||||
tag = __utils__["state.gen_tag"](chunk)
|
||||
if tag in running:
|
||||
# Already ran the pkg state, skip aggregation
|
||||
continue
|
||||
if chunk.get('state') == 'pkg':
|
||||
if '__agg__' in chunk:
|
||||
if chunk.get("state") == "pkg":
|
||||
if "__agg__" in chunk:
|
||||
continue
|
||||
# Check for the same function
|
||||
if chunk.get('fun') != low.get('fun'):
|
||||
if chunk.get("fun") != low.get("fun"):
|
||||
continue
|
||||
# Pull out the pkg names!
|
||||
if 'pkgs' in chunk:
|
||||
pkgs.extend(chunk['pkgs'])
|
||||
chunk['__agg__'] = True
|
||||
elif 'name' in chunk:
|
||||
pkgs.append(chunk['name'])
|
||||
chunk['__agg__'] = True
|
||||
if "pkgs" in chunk:
|
||||
pkgs.extend(chunk["pkgs"])
|
||||
chunk["__agg__"] = True
|
||||
elif "name" in chunk:
|
||||
pkgs.append(chunk["name"])
|
||||
chunk["__agg__"] = True
|
||||
if pkgs:
|
||||
if 'pkgs' in low:
|
||||
low['pkgs'].extend(pkgs)
|
||||
if "pkgs" in low:
|
||||
low["pkgs"].extend(pkgs)
|
||||
else:
|
||||
low['pkgs'] = pkgs
|
||||
low["pkgs"] = pkgs
|
||||
# The low has been modified and needs to be returned to the state
|
||||
# runtime for execution
|
||||
return low
|
||||
|
|
|
@ -66,13 +66,10 @@ A well-written state function will follow these steps:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
ret = {'name': name,
|
||||
'result': False,
|
||||
'changes': {},
|
||||
'comment': ''}
|
||||
ret = {"name": name, "result": False, "changes": {}, "comment": ""}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
if result:
|
||||
ret['result'] = True
|
||||
ret['comment'] = '{0} is already installed'.format(name)
|
||||
ret["result"] = True
|
||||
ret["comment"] = "{0} is already installed".format(name)
|
||||
return ret
|
||||
|
||||
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
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = '{0} would be installed'.format(name)
|
||||
ret['changes'] = result
|
||||
if __opts__["test"]:
|
||||
ret["result"] = None
|
||||
ret["comment"] = "{0} would be installed".format(name)
|
||||
ret["changes"] = result
|
||||
return ret
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
if ret['changes']:
|
||||
ret['comment'] = '{0} failed to install'.format(name)
|
||||
if ret["changes"]:
|
||||
ret["comment"] = "{0} failed to install".format(name)
|
||||
else:
|
||||
ret['result'] = True
|
||||
ret['comment'] = '{0} was installed'.format(name)
|
||||
ret["result"] = True
|
||||
ret["comment"] = "{0} was installed".format(name)
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -202,7 +199,7 @@ Salt state modules can be cross-called by accessing the value in the
|
|||
|
||||
.. 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
|
||||
<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
|
||||
|
||||
ret['changes'].update({'my_pkg_name': {'old': '',
|
||||
'new': 'my_pkg_name-1.0'}})
|
||||
ret["changes"].update({"my_pkg_name": {"old": "", "new": "my_pkg_name-1.0"}})
|
||||
|
||||
|
||||
- **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
|
||||
|
||||
# Return comment of changes if test.
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'State Foo will execute with param {0}'.format(bar)
|
||||
if __opts__["test"]:
|
||||
ret["result"] = None
|
||||
ret["comment"] = "State Foo will execute with param {0}".format(bar)
|
||||
return ret
|
||||
|
||||
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
|
||||
|
||||
def mod_init(low):
|
||||
'''
|
||||
"""
|
||||
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()
|
||||
if not os.path.exists(rtag):
|
||||
open(rtag, 'w+').write('')
|
||||
open(rtag, "w+").write("")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -368,9 +364,9 @@ logs. The following code snippet demonstrates writing log messages:
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.info('Here is Some Information')
|
||||
log.warning('You Should Not Do That')
|
||||
log.error('It Is Busted')
|
||||
log.info("Here is Some Information")
|
||||
log.warning("You Should Not Do That")
|
||||
log.error("It Is Busted")
|
||||
|
||||
|
||||
Strings and Unicode
|
||||
|
@ -424,8 +420,9 @@ Example state module
|
|||
|
||||
import salt.exceptions
|
||||
|
||||
|
||||
def enforce_custom_thing(name, foo, bar=True):
|
||||
'''
|
||||
"""
|
||||
Enforce the state of a custom thing
|
||||
|
||||
This state module does a custom thing. It calls out to the execution module
|
||||
|
@ -438,52 +435,53 @@ Example state module
|
|||
A required argument
|
||||
bar : True
|
||||
An argument with a default value
|
||||
'''
|
||||
"""
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': '',
|
||||
}
|
||||
"name": name,
|
||||
"changes": {},
|
||||
"result": False,
|
||||
"comment": "",
|
||||
}
|
||||
|
||||
# Start with basic error-checking. Do all the passed parameters make sense
|
||||
# and agree with each-other?
|
||||
if bar == True and foo.startswith('Foo'):
|
||||
if bar == True and foo.startswith("Foo"):
|
||||
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?
|
||||
current_state = __salt__['my_custom_module.current_state'](name)
|
||||
current_state = __salt__["my_custom_module.current_state"](name)
|
||||
|
||||
if current_state == foo:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'System already in the correct state'
|
||||
ret["result"] = True
|
||||
ret["comment"] = "System already in the correct state"
|
||||
return ret
|
||||
|
||||
# The state of the system does need to be changed. Check if we're running
|
||||
# in ``test=true`` mode.
|
||||
if __opts__['test'] == True:
|
||||
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
|
||||
ret['changes'] = {
|
||||
'old': current_state,
|
||||
'new': 'Description, diff, whatever of the new state',
|
||||
if __opts__["test"] == True:
|
||||
ret["comment"] = 'The state of "{0}" will be changed.'.format(name)
|
||||
ret["changes"] = {
|
||||
"old": current_state,
|
||||
"new": "Description, diff, whatever of the new state",
|
||||
}
|
||||
|
||||
# Return ``None`` when running with ``test=true``.
|
||||
ret['result'] = None
|
||||
ret["result"] = None
|
||||
|
||||
return ret
|
||||
|
||||
# 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'] = {
|
||||
'old': current_state,
|
||||
'new': new_state,
|
||||
ret["changes"] = {
|
||||
"old": current_state,
|
||||
"new": new_state,
|
||||
}
|
||||
|
||||
ret['result'] = True
|
||||
ret["result"] = True
|
||||
|
||||
return ret
|
||||
|
|
|
@ -360,8 +360,7 @@ The return data structure would look something like this:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
[{'changes': ['/foo/bar'], 'tag': 'foo'},
|
||||
{'changes': ['/foo/baz'], 'tag': 'bar'}]
|
||||
[{"changes": ["/foo/bar"], "tag": "foo"}, {"changes": ["/foo/baz"], "tag": "bar"}]
|
||||
|
||||
Calling Execution Modules
|
||||
-------------------------
|
||||
|
|
|
@ -145,7 +145,7 @@ library. The following two lines set up the imports:
|
|||
|
||||
.. 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
|
||||
|
||||
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())
|
||||
list_nodes = salt.utils.functools.namespaced_function(list_nodes, 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())
|
||||
|
||||
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
|
||||
|
||||
def list_nodes_select(call=None):
|
||||
'''
|
||||
"""
|
||||
Return a list of the VMs that are on the provider, with select fields
|
||||
'''
|
||||
"""
|
||||
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.
|
||||
|
@ -315,16 +317,14 @@ appropriately:
|
|||
.. code-block:: python
|
||||
|
||||
def list_nodes_select(conn=None, call=None):
|
||||
'''
|
||||
"""
|
||||
Return a list of the VMs that are on the provider, with select fields
|
||||
'''
|
||||
"""
|
||||
if not conn:
|
||||
conn = get_conn() # pylint: disable=E0602
|
||||
conn = get_conn() # pylint: disable=E0602
|
||||
|
||||
return salt.utils.cloud.list_nodes_select(
|
||||
list_nodes_full(conn, 'function'),
|
||||
__opts__['query.selection'],
|
||||
call,
|
||||
list_nodes_full(conn, "function"), __opts__["query.selection"], call,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
def show_instance(name, call=None):
|
||||
'''
|
||||
Show the details from EC2 concerning an AMI
|
||||
'''
|
||||
if call != 'action':
|
||||
raise SaltCloudSystemExit(
|
||||
'The show_instance action must be called with -a or --action.'
|
||||
)
|
||||
"""
|
||||
Show the details from EC2 concerning an AMI
|
||||
"""
|
||||
if call != "action":
|
||||
raise SaltCloudSystemExit(
|
||||
"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
|
||||
``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
|
||||
|
||||
def show_image(kwargs, call=None):
|
||||
'''
|
||||
"""
|
||||
Show the details from EC2 concerning an AMI
|
||||
'''
|
||||
if call != 'function':
|
||||
"""
|
||||
if call != "function":
|
||||
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'],
|
||||
'Action': 'DescribeImages'}
|
||||
params = {"ImageId.1": kwargs["image"], "Action": "DescribeImages"}
|
||||
result = query(params)
|
||||
log.info(result)
|
||||
|
||||
|
|
|
@ -572,33 +572,43 @@ data:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
[{'deploy': False,
|
||||
'image': 'ami-08d97e61',
|
||||
'profile': 'Fedora-17',
|
||||
'provider': 'my-ec2-config',
|
||||
'securitygroup': ['default'],
|
||||
'size': 't1.micro',
|
||||
'ssh_username': 'ec2_user'},
|
||||
{'deploy': False,
|
||||
'image': 'ami-09b61d60',
|
||||
'profile': 'CentOS-5',
|
||||
'provider': 'my-aws-config',
|
||||
'securitygroup': ['default'],
|
||||
'size': 't1.micro',
|
||||
'ssh_username': 'ec2_user'},
|
||||
{'deploy': False,
|
||||
'image': 'ami-54cf5c3d',
|
||||
'profile': 'Amazon-Linux-AMI-2012.09-64bit',
|
||||
'provider': 'my-ec2-config',
|
||||
'securitygroup': ['default'],
|
||||
'size': 't1.micro',
|
||||
'ssh_username': 'ec2_user'},
|
||||
{'deploy': False,
|
||||
'profile': 'development-instances',
|
||||
'provider': 'my-ec2-config',
|
||||
'securitygroup': ['default'],
|
||||
'size': 't1.micro',
|
||||
'ssh_username': 'ec2_user'}]
|
||||
[
|
||||
{
|
||||
"deploy": False,
|
||||
"image": "ami-08d97e61",
|
||||
"profile": "Fedora-17",
|
||||
"provider": "my-ec2-config",
|
||||
"securitygroup": ["default"],
|
||||
"size": "t1.micro",
|
||||
"ssh_username": "ec2_user",
|
||||
},
|
||||
{
|
||||
"deploy": False,
|
||||
"image": "ami-09b61d60",
|
||||
"profile": "CentOS-5",
|
||||
"provider": "my-aws-config",
|
||||
"securitygroup": ["default"],
|
||||
"size": "t1.micro",
|
||||
"ssh_username": "ec2_user",
|
||||
},
|
||||
{
|
||||
"deploy": False,
|
||||
"image": "ami-54cf5c3d",
|
||||
"profile": "Amazon-Linux-AMI-2012.09-64bit",
|
||||
"provider": "my-ec2-config",
|
||||
"securitygroup": ["default"],
|
||||
"size": "t1.micro",
|
||||
"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?
|
||||
|
||||
|
@ -642,33 +652,36 @@ data:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'providers': {
|
||||
'my-develop-envs': [
|
||||
{'availability_zone': 'ap-southeast-1b',
|
||||
'id': 'HJGRYCILJLKJYG',
|
||||
'key': 'kdjgfsgm;woormgl/aserigjksjdhasdfgn',
|
||||
'keyname': 'test',
|
||||
'location': 'ap-southeast-1',
|
||||
'private_key': '/root/test.pem',
|
||||
'driver': 'aws',
|
||||
'securitygroup': 'quick-start'
|
||||
"providers": {
|
||||
"my-develop-envs": [
|
||||
{
|
||||
"availability_zone": "ap-southeast-1b",
|
||||
"id": "HJGRYCILJLKJYG",
|
||||
"key": "kdjgfsgm;woormgl/aserigjksjdhasdfgn",
|
||||
"keyname": "test",
|
||||
"location": "ap-southeast-1",
|
||||
"private_key": "/root/test.pem",
|
||||
"driver": "aws",
|
||||
"securitygroup": "quick-start",
|
||||
},
|
||||
{'location': 'Raleigh',
|
||||
'password': 'mypass',
|
||||
'driver': 'ibmsce',
|
||||
'ssh_key_file': '/etc/salt/ibm/mykey.pem',
|
||||
'ssh_key_name': 'mykey',
|
||||
'user': 'myuser@mycorp.com'
|
||||
{
|
||||
"location": "Raleigh",
|
||||
"password": "mypass",
|
||||
"driver": "ibmsce",
|
||||
"ssh_key_file": "/etc/salt/ibm/mykey.pem",
|
||||
"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
|
||||
|
||||
{'name': 'web1',
|
||||
'profile': 'ec2-centos',
|
||||
'provider': 'ec2-config:ec2'}
|
||||
{"name": "web1", "profile": "ec2-centos", "provider": "ec2-config:ec2"}
|
||||
|
||||
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 pprint
|
||||
client = salt.cloud.CloudClient('/etc/salt/cloud')
|
||||
|
||||
client = salt.cloud.CloudClient("/etc/salt/cloud")
|
||||
nodes = client.query()
|
||||
pprint.pprint(nodes)
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ code and can contain special formatting. For example:
|
|||
.. code-block:: python
|
||||
|
||||
def my_function(value):
|
||||
'''
|
||||
"""
|
||||
Upper-case the given value
|
||||
|
||||
Usage:
|
||||
|
@ -110,7 +110,7 @@ code and can contain special formatting. For example:
|
|||
|
||||
:param value: a string
|
||||
:return: a copy of ``value`` that has been upper-cased
|
||||
'''
|
||||
"""
|
||||
return value.upper()
|
||||
|
||||
Specify a release for additions or changes
|
||||
|
@ -122,13 +122,13 @@ denotes what Salt release will be affected. For example:
|
|||
.. code-block:: python
|
||||
|
||||
def my_function(value):
|
||||
'''
|
||||
"""
|
||||
Upper-case the given value
|
||||
|
||||
.. versionadded:: 2014.7.0
|
||||
|
||||
<...snip...>
|
||||
'''
|
||||
"""
|
||||
return value.upper()
|
||||
|
||||
For changes to a function:
|
||||
|
@ -136,14 +136,14 @@ For changes to a function:
|
|||
.. code-block:: python
|
||||
|
||||
def my_function(value, strip=False):
|
||||
'''
|
||||
"""
|
||||
Upper-case the given value
|
||||
|
||||
.. versionchanged:: 2016.3.0
|
||||
Added a flag to also strip whitespace from the string.
|
||||
|
||||
<...snip...>
|
||||
'''
|
||||
"""
|
||||
if strip:
|
||||
return value.upper().strip()
|
||||
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'.
|
||||
# Easily access the grains dictionary:
|
||||
try:
|
||||
app, instance_number, environment = __grains__['id'].split('-')
|
||||
app, instance_number, environment = __grains__["id"].split("-")
|
||||
instance_number = int(instance_number)
|
||||
except ValueError:
|
||||
app, instance_number, environment = ['Unknown', 0, 'dev']
|
||||
app, instance_number, environment = ["Unknown", 0, "dev"]
|
||||
|
||||
list_of_roles.add(app)
|
||||
|
||||
if app == 'web' and environment == 'dev':
|
||||
list_of_roles.add('primary')
|
||||
list_of_roles.add('secondary')
|
||||
elif app == 'web' and environment == 'staging':
|
||||
if app == "web" and environment == "dev":
|
||||
list_of_roles.add("primary")
|
||||
list_of_roles.add("secondary")
|
||||
elif app == "web" and environment == "staging":
|
||||
if instance_number == 0:
|
||||
list_of_roles.add('primary')
|
||||
list_of_roles.add("primary")
|
||||
else:
|
||||
list_of_roles.add('secondary')
|
||||
list_of_roles.add("secondary")
|
||||
|
||||
# Easily cross-call Salt execution modules:
|
||||
if __salt__['myutils.query_valid_ec2_instance']():
|
||||
list_of_roles.add('is_ec2_instance')
|
||||
if __salt__["myutils.query_valid_ec2_instance"]():
|
||||
list_of_roles.add("is_ec2_instance")
|
||||
|
||||
return {
|
||||
'set_roles_grains': {
|
||||
'grains.present': [
|
||||
{'name': 'roles'},
|
||||
{'value': list(list_of_roles)},
|
||||
],
|
||||
"set_roles_grains": {
|
||||
"grains.present": [{"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
|
||||
|
||||
data = 'some text'
|
||||
more = '{0} and then some'.format(data)
|
||||
data = "some text"
|
||||
more = "{0} and then some".format(data)
|
||||
|
||||
Make sure to use indices or identifiers in the format brackets, since empty
|
||||
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
|
||||
|
||||
def new_func(msg=''):
|
||||
'''
|
||||
def new_func(msg=""):
|
||||
"""
|
||||
.. versionadded:: 0.16.0
|
||||
|
||||
Prints what was passed to the function.
|
||||
|
||||
msg : None
|
||||
The string to be printed.
|
||||
'''
|
||||
"""
|
||||
print(msg)
|
||||
|
||||
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
|
||||
|
||||
def new_func(msg='', signature=''):
|
||||
'''
|
||||
def new_func(msg="", signature=""):
|
||||
"""
|
||||
.. versionadded:: 0.16.0
|
||||
|
||||
Prints what was passed to the function.
|
||||
|
@ -124,8 +124,8 @@ significantly, the ``versionchanged`` directive can be used to clarify this:
|
|||
An optional signature.
|
||||
|
||||
.. versionadded 0.17.0
|
||||
'''
|
||||
print('Greetings! {0}\n\n{1}'.format(msg, signature))
|
||||
"""
|
||||
print("Greetings! {0}\n\n{1}".format(msg, signature))
|
||||
|
||||
|
||||
Dictionaries
|
||||
|
@ -154,8 +154,9 @@ To say this more directly with an example, this is `GOOD`:
|
|||
|
||||
import os
|
||||
|
||||
|
||||
def minion_path():
|
||||
path = os.path.join(self.opts['cachedir'], 'minions')
|
||||
path = os.path.join(self.opts["cachedir"], "minions")
|
||||
return path
|
||||
|
||||
This on the other hand is `DISCOURAGED`:
|
||||
|
@ -164,8 +165,9 @@ This on the other hand is `DISCOURAGED`:
|
|||
|
||||
from os.path import join
|
||||
|
||||
|
||||
def minion_path():
|
||||
path = join(self.opts['cachedir'], 'minions')
|
||||
path = join(self.opts["cachedir"], "minions")
|
||||
return path
|
||||
|
||||
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):
|
||||
if foo is not None:
|
||||
salt.utils.versions.warn_until(
|
||||
'Aluminum',
|
||||
'The \'foo\' argument has been deprecated and its '
|
||||
'functionality removed, as such, its usage is no longer '
|
||||
'required.'
|
||||
"Aluminum",
|
||||
"The 'foo' argument has been deprecated and its "
|
||||
"functionality removed, as such, its usage is no longer "
|
||||
"required.",
|
||||
)
|
||||
|
||||
Development begins on the ``Aluminum`` release when the ``Magnesium`` branch is
|
||||
|
|
|
@ -79,8 +79,8 @@ Example file in the template directory
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
print('Hello {{module_name}}')
|
||||
__virtual__ = '{{__virtual_name__}}'
|
||||
print("Hello {{module_name}}")
|
||||
__virtual__ = "{{__virtual_name__}}"
|
||||
|
||||
Default context properties
|
||||
**************************
|
||||
|
|
|
@ -30,8 +30,10 @@ the debugger should be started:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
test = 'test123'
|
||||
import IPython; IPython.embed_kernel()
|
||||
test = "test123"
|
||||
import IPython
|
||||
|
||||
IPython.embed_kernel()
|
||||
|
||||
After running a Salt command that hits that line, the following will show up in
|
||||
the log file:
|
||||
|
@ -146,8 +148,8 @@ functions to be called as they have been set up by the salt loader.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
__salt__['cmd.run']('fdisk -l')
|
||||
__salt__['network.ip_addrs']()
|
||||
__salt__["cmd.run"]("fdisk -l")
|
||||
__salt__["network.ip_addrs"]()
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -205,8 +207,8 @@ each file. Here is an example from salt/modules/cp.py:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
if not 'cp.fileclient' in __context__:
|
||||
__context__['cp.fileclient'] = salt.fileclient.get_file_client(__opts__)
|
||||
if not "cp.fileclient" in __context__:
|
||||
__context__["cp.fileclient"] = salt.fileclient.get_file_client(__opts__)
|
||||
|
||||
|
||||
.. 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:
|
||||
import weird_thing
|
||||
|
||||
EXAMPLE_A_LOADED = True
|
||||
except ImportError:
|
||||
EXAMPLE_A_LOADED = False
|
||||
|
@ -80,7 +81,7 @@ things ``modulename.option``.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
__opts__ = { 'example_a.someconfig': 137 }
|
||||
__opts__ = {"example_a.someconfig": 137}
|
||||
|
||||
|
||||
Initialization
|
||||
|
@ -91,8 +92,9 @@ signature:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def __init__( __opts__ ):
|
||||
def __init__(__opts__):
|
||||
# Do init work here
|
||||
...
|
||||
|
||||
|
||||
**Note**: The ``__init__`` function is ran every time a particular minion causes
|
||||
|
@ -127,7 +129,8 @@ installed.
|
|||
.. code-block:: python
|
||||
|
||||
# This external pillar will be known as `something_else`
|
||||
__virtualname__ = 'something_else'
|
||||
__virtualname__ = "something_else"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
if EXAMPLE_A_LOADED:
|
||||
|
@ -151,9 +154,9 @@ Using our example above:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
ext_pillar( id, pillar, 'some argument' ) # example_a
|
||||
ext_pillar( id, pillar, 'argumentA', 'argumentB' ) # example_b
|
||||
ext_pillar( id, pillar, keyA='valueA', keyB='valueB' ) # example_c
|
||||
ext_pillar(id, pillar, "some argument") # example_a
|
||||
ext_pillar(id, pillar, "argumentA", "argumentB") # example_b
|
||||
ext_pillar(id, pillar, keyA="valueA", keyB="valueB") # example_c
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -101,11 +101,7 @@ This is done via setuptools entry points:
|
|||
|
||||
setup(
|
||||
# ...
|
||||
entry_points={
|
||||
'salt.loader': [
|
||||
'module_dirs=spirofs.loader:module',
|
||||
],
|
||||
},
|
||||
entry_points={"salt.loader": ["module_dirs=spirofs.loader:module"]},
|
||||
# ...
|
||||
)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ so:
|
|||
|
||||
.. 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
|
||||
:mod:`pkg_resource.sort_pkglist <salt.modules.pkg_resource.sort_pkglist>`. This
|
||||
|
@ -32,7 +32,7 @@ future versions of Salt.
|
|||
|
||||
.. 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
|
||||
|
@ -41,8 +41,7 @@ data:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'foo': '1.2.3-4',
|
||||
'bar': '5.6.7-8'}
|
||||
{"foo": "1.2.3-4", "bar": "5.6.7-8"}
|
||||
|
||||
|
||||
latest_version
|
||||
|
@ -83,7 +82,7 @@ Deprecated and destined to be removed. For now, should just do the following:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
return __salt__['pkg.latest_version'](name) != ''
|
||||
return __salt__["pkg.latest_version"](name) != ""
|
||||
|
||||
|
||||
install
|
||||
|
@ -106,9 +105,7 @@ system.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](name,
|
||||
pkgs,
|
||||
sources)
|
||||
pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](name, pkgs, sources)
|
||||
|
||||
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,
|
||||
|
@ -164,8 +161,8 @@ changed.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
basedir = '/etc/yum.repos.d'
|
||||
__salt__['pkg.list_repos'](basedir)
|
||||
basedir = "/etc/yum.repos.d"
|
||||
__salt__["pkg.list_repos"](basedir)
|
||||
|
||||
list_repos
|
||||
^^^^^^^^^^
|
||||
|
@ -173,7 +170,7 @@ Lists the repositories that are currently configured on this system.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
__salt__['pkg.list_repos']()
|
||||
__salt__["pkg.list_repos"]()
|
||||
|
||||
Returns a dictionary, in the following format:
|
||||
|
||||
|
@ -190,7 +187,7 @@ Displays all local configuration for a specific repository.
|
|||
|
||||
.. 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
|
||||
specific to only one repo.
|
||||
|
@ -211,7 +208,7 @@ success.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
__salt__['pkg.del_repo'](repo='myrepo')
|
||||
__salt__["pkg.del_repo"](repo="myrepo")
|
||||
|
||||
mod_repo
|
||||
^^^^^^^^
|
||||
|
@ -224,7 +221,7 @@ refer to the documentation for your specific repo manager for specifics.
|
|||
|
||||
.. 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
|
||||
|
@ -251,14 +248,13 @@ packages will be listed.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
installed = __salt__['lowpkg.list_pkgs']('foo', 'bar')
|
||||
installed = __salt__["lowpkg.list_pkgs"]("foo", "bar")
|
||||
|
||||
Example output:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'foo': '1.2.3-4',
|
||||
'bar': '5.6.7-8'}
|
||||
{"foo": "1.2.3-4", "bar": "5.6.7-8"}
|
||||
|
||||
verify
|
||||
^^^^^^
|
||||
|
@ -269,14 +265,18 @@ included.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
installed = __salt__['lowpkg.verify']('httpd')
|
||||
installed = __salt__["lowpkg.verify"]("httpd")
|
||||
|
||||
Example output:
|
||||
|
||||
.. 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
|
||||
^^^^^^^^^
|
||||
|
@ -285,7 +285,7 @@ specified, then all files for all known packages are returned.
|
|||
|
||||
.. 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
|
||||
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
|
||||
|
||||
{'errors': ['package apache is not installed'],
|
||||
'files': ['/etc/httpd',
|
||||
'/etc/httpd/conf',
|
||||
'/etc/httpd/conf.d',
|
||||
'...SNIP...']}
|
||||
{
|
||||
"errors": ["package apache is not installed"],
|
||||
"files": ["/etc/httpd", "/etc/httpd/conf", "/etc/httpd/conf.d", "...SNIP..."],
|
||||
}
|
||||
|
||||
file_dict
|
||||
^^^^^^^^^
|
||||
|
@ -307,17 +306,21 @@ specified, then all files for all known packages are returned.
|
|||
|
||||
.. 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
|
||||
packages. It will also return errors in the same manner as `file_list`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'errors': ['package apache is not installed'],
|
||||
'packages': {'httpd': ['/etc/httpd',
|
||||
'/etc/httpd/conf',
|
||||
'...SNIP...'],
|
||||
'kernel': ['/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac',
|
||||
'/boot/System.map-2.6.32-279.el6.x86_64',
|
||||
'...SNIP...']}}
|
||||
{
|
||||
"errors": ["package apache is not installed"],
|
||||
"packages": {
|
||||
"httpd": ["/etc/httpd", "/etc/httpd/conf", "...SNIP..."],
|
||||
"kernel": [
|
||||
"/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac",
|
||||
"/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
|
||||
|
||||
def test_user_present(self):
|
||||
...
|
||||
|
||||
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.
|
||||
|
|
|
@ -89,16 +89,12 @@ kernel is not present, then the test should be skipped.
|
|||
.. code-block:: python
|
||||
|
||||
def setUp(self):
|
||||
'''
|
||||
"""
|
||||
Sets up test requirements
|
||||
'''
|
||||
os_grain = self.run_function('grains.item', ['kernel'])
|
||||
if os_grain['kernel'] not in 'Darwin':
|
||||
self.skipTest(
|
||||
'Test not applicable to \'{kernel}\' kernel'.format(
|
||||
**os_grain
|
||||
)
|
||||
)
|
||||
"""
|
||||
os_grain = self.run_function("grains.item", ["kernel"])
|
||||
if os_grain["kernel"] not in "Darwin":
|
||||
self.skipTest("Test not applicable to '{kernel}' kernel".format(**os_grain))
|
||||
|
||||
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
|
||||
|
@ -272,24 +268,25 @@ Now the workhorse method ``run_function`` can be used to test a module:
|
|||
.. code-block:: python
|
||||
|
||||
import os
|
||||
import tests.integration as integration
|
||||
from tests.support.case import ModuleCase
|
||||
|
||||
|
||||
class TestModuleTest(integration.ModuleCase):
|
||||
'''
|
||||
class TestModuleTest(ModuleCase):
|
||||
"""
|
||||
Validate the test module
|
||||
'''
|
||||
"""
|
||||
|
||||
def test_ping(self):
|
||||
'''
|
||||
"""
|
||||
test.ping
|
||||
'''
|
||||
self.assertTrue(self.run_function('test.ping'))
|
||||
"""
|
||||
self.assertTrue(self.run_function("test.ping"))
|
||||
|
||||
def test_echo(self):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
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 tempfile
|
||||
|
||||
import tests.integration as integration
|
||||
from tests.support.case import ShellCase
|
||||
|
||||
class KeyTest(integration.ShellCase):
|
||||
'''
|
||||
|
||||
class KeyTest(ShellCase):
|
||||
"""
|
||||
Test salt-key script
|
||||
'''
|
||||
"""
|
||||
|
||||
_call_binary_ = 'salt-key'
|
||||
_call_binary_ = "salt-key"
|
||||
|
||||
def test_list(self):
|
||||
'''
|
||||
"""
|
||||
test salt-key -L
|
||||
'''
|
||||
data = self.run_key('-L')
|
||||
"""
|
||||
data = self.run_key("-L")
|
||||
expect = [
|
||||
'Unaccepted Keys:',
|
||||
'Accepted Keys:',
|
||||
'minion',
|
||||
'sub_minion',
|
||||
'Rejected:', '']
|
||||
"Unaccepted Keys:",
|
||||
"Accepted Keys:",
|
||||
"minion",
|
||||
"sub_minion",
|
||||
"Rejected:",
|
||||
"",
|
||||
]
|
||||
self.assertEqual(data, expect)
|
||||
|
||||
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
|
||||
|
||||
import tests.integration as integration
|
||||
from tests.support.case import SSHCase
|
||||
|
||||
class SSHGrainsTest(integration.SSHCase):
|
||||
class SSHGrainsTest(SSHCase):
|
||||
'''
|
||||
Test salt-ssh grains functionality
|
||||
Depend on proper environment set by integration.SSHCase class
|
||||
|
@ -370,20 +370,23 @@ on a minion event bus.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
import tests.integration as integration
|
||||
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
|
||||
'''
|
||||
"""
|
||||
|
||||
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
|
||||
|
@ -393,17 +396,19 @@ Testing Salt's Syndic can be done via the SyndicCase test class:
|
|||
|
||||
.. 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
|
||||
'''
|
||||
"""
|
||||
|
||||
def test_ping(self):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
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.utils.files
|
||||
|
||||
HFILE = os.path.join(TMP, 'hosts')
|
||||
HFILE = os.path.join(TMP, "hosts")
|
||||
|
||||
|
||||
class HostTest(ModuleCase, SaltReturnAssertsMixin):
|
||||
'''
|
||||
"""
|
||||
Validate the host state
|
||||
'''
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
shutil.copyfile(os.path.join(FILES, 'hosts'), HFILE)
|
||||
shutil.copyfile(os.path.join(FILES, "hosts"), HFILE)
|
||||
super(HostTest, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -464,18 +469,18 @@ to test states:
|
|||
super(HostTest, self).tearDown()
|
||||
|
||||
def test_present(self):
|
||||
'''
|
||||
"""
|
||||
host.present
|
||||
'''
|
||||
name = 'spam.bacon'
|
||||
ip = '10.10.10.10'
|
||||
ret = self.run_state('host.present', name=name, ip=ip)
|
||||
"""
|
||||
name = "spam.bacon"
|
||||
ip = "10.10.10.10"
|
||||
ret = self.run_state("host.present", name=name, ip=ip)
|
||||
self.assertSaltTrueReturn(ret)
|
||||
with salt.utils.files.fopen(HFILE) as fp_:
|
||||
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
|
||||
``host.present`` sls file resides.
|
||||
|
||||
|
@ -507,24 +512,25 @@ the test method:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
import tests.integration as integration
|
||||
from tests.support.case import ModuleCase
|
||||
from tests.support.helpers import destructiveTest, skip_if_not_root
|
||||
|
||||
class DestructiveExampleModuleTest(integration.ModuleCase):
|
||||
'''
|
||||
|
||||
class DestructiveExampleModuleTest(ModuleCase):
|
||||
"""
|
||||
Demonstrate a destructive test
|
||||
'''
|
||||
"""
|
||||
|
||||
@destructiveTest
|
||||
@skip_if_not_root
|
||||
def test_user_not_present(self):
|
||||
'''
|
||||
"""
|
||||
This is a DESTRUCTIVE TEST it creates a new user on the minion.
|
||||
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)
|
||||
ret = self.run_state('user.absent', name='salt_test')
|
||||
ret = self.run_state("user.absent", name="salt_test")
|
||||
self.assertSaltTrueReturn(ret)
|
||||
|
||||
|
||||
|
@ -634,27 +640,28 @@ the test function:
|
|||
|
||||
from tests.support.helpers import expensiveTest
|
||||
|
||||
|
||||
@expensiveTest
|
||||
def test_instance(self):
|
||||
'''
|
||||
"""
|
||||
Test creating an instance on Linode
|
||||
'''
|
||||
name = 'linode-testing'
|
||||
"""
|
||||
name = "linode-testing"
|
||||
|
||||
# create the instance
|
||||
instance = self.run_cloud('-p linode-test {0}'.format(name))
|
||||
str = ' {0}'.format(name)
|
||||
instance = self.run_cloud("-p linode-test {0}".format(name))
|
||||
str = " {0}".format(name)
|
||||
|
||||
# check if instance with salt installed returned as expected
|
||||
try:
|
||||
self.assertIn(str, instance)
|
||||
except AssertionError:
|
||||
self.run_cloud('-d {0} --assume-yes'.format(name))
|
||||
self.run_cloud("-d {0} --assume-yes".format(name))
|
||||
raise
|
||||
|
||||
# delete the instance
|
||||
delete = self.run_cloud('-d {0} --assume-yes'.format(name))
|
||||
str = ' True'
|
||||
delete = self.run_cloud("-d {0} --assume-yes".format(name))
|
||||
str = " True"
|
||||
try:
|
||||
self.assertIn(str, delete)
|
||||
except AssertionError:
|
||||
|
|
|
@ -113,14 +113,10 @@ to the module being tested one should do:
|
|||
|
||||
import salt.modules.somemodule as somemodule
|
||||
|
||||
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
|
||||
|
||||
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
|
||||
def setup_loader_modules(self):
|
||||
return {
|
||||
somemodule: {
|
||||
'__opts__': {'test': True}
|
||||
}
|
||||
}
|
||||
return {somemodule: {"__opts__": {"test": True}}}
|
||||
|
||||
Consider this more extensive example from
|
||||
``tests/unit/modules/test_libcloud_dns.py``:
|
||||
|
@ -133,10 +129,7 @@ Consider this more extensive example from
|
|||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch,
|
||||
MagicMock,
|
||||
)
|
||||
from tests.support.mock import patch, MagicMock
|
||||
import salt.modules.libcloud_dns as libcloud_dns
|
||||
|
||||
|
||||
|
@ -149,23 +142,18 @@ Consider this more extensive example from
|
|||
return MockDNSDriver()
|
||||
|
||||
|
||||
@patch('salt.modules.libcloud_dns._get_driver',
|
||||
MagicMock(return_value=MockDNSDriver()))
|
||||
@patch("salt.modules.libcloud_dns._get_driver", MagicMock(return_value=MockDNSDriver()))
|
||||
class LibcloudDnsModuleTestCase(TestCase, LoaderModuleMockMixin):
|
||||
|
||||
def setup_loader_modules(self):
|
||||
module_globals = {
|
||||
'__salt__': {
|
||||
'config.option': MagicMock(return_value={
|
||||
'test': {
|
||||
'driver': 'test',
|
||||
'key': '2orgk34kgk34g'
|
||||
}
|
||||
})
|
||||
"__salt__": {
|
||||
"config.option": MagicMock(
|
||||
return_value={"test": {"driver": "test", "key": "2orgk34kgk34g"}}
|
||||
)
|
||||
}
|
||||
}
|
||||
if libcloud_dns.HAS_LIBCLOUD is False:
|
||||
module_globals['sys.modules'] = {'libcloud': MagicMock()}
|
||||
module_globals["sys.modules"] = {"libcloud": MagicMock()}
|
||||
|
||||
return {libcloud_dns: module_globals}
|
||||
|
||||
|
@ -193,18 +181,15 @@ a separate implementation which has additional functionality.
|
|||
.. code-block:: python
|
||||
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch
|
||||
mock_open,
|
||||
)
|
||||
from tests.support.mock import patch, mock_open
|
||||
|
||||
import salt.modules.mymod as mymod
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
def test_something(self):
|
||||
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n')
|
||||
with patch('salt.utils.files.fopen', fopen_mock):
|
||||
fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
|
||||
with patch("salt.utils.files.fopen", fopen_mock):
|
||||
result = mymod.myfunc()
|
||||
assert result is True
|
||||
|
||||
|
@ -245,30 +230,31 @@ those cases, you can pass ``read_data`` as a dictionary:
|
|||
import textwrap
|
||||
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch
|
||||
mock_open,
|
||||
)
|
||||
from tests.support.mock import patch, mock_open
|
||||
|
||||
import salt.modules.mymod as mymod
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
def test_something(self):
|
||||
contents = {
|
||||
'/etc/foo.conf': textwrap.dedent('''\
|
||||
"/etc/foo.conf": textwrap.dedent(
|
||||
"""\
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'''),
|
||||
'/etc/b*.conf': textwrap.dedent('''\
|
||||
"""
|
||||
),
|
||||
"/etc/b*.conf": textwrap.dedent(
|
||||
"""\
|
||||
one
|
||||
two
|
||||
three
|
||||
'''),
|
||||
"""
|
||||
),
|
||||
}
|
||||
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()
|
||||
assert result is True
|
||||
|
||||
|
@ -284,8 +270,8 @@ below two ``mock_open`` calls would produce identical results:
|
|||
|
||||
.. 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::
|
||||
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
|
||||
|
||||
contents = OrderedDict()
|
||||
contents['/etc/bar.conf'] = 'foo\nbar\nbaz\n'
|
||||
contents['/etc/b*.conf'] = IOError(errno.EACCES, 'Permission denied')
|
||||
contents['*'] = 'This is a fallback for files not beginning with "/etc/b"\n'
|
||||
contents["/etc/bar.conf"] = "foo\nbar\nbaz\n"
|
||||
contents["/etc/b*.conf"] = IOError(errno.EACCES, "Permission denied")
|
||||
contents["*"] = 'This is a fallback for files not beginning with "/etc/b"\n'
|
||||
fopen_mock = mock_open(read_data=contents)
|
||||
|
||||
Raising Exceptions
|
||||
|
@ -315,19 +301,16 @@ Instead of a string, an exception can also be used as the ``read_data``:
|
|||
import errno
|
||||
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch
|
||||
mock_open,
|
||||
)
|
||||
from tests.support.mock import patch, mock_open
|
||||
|
||||
import salt.modules.mymod as mymod
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
def test_something(self):
|
||||
exc = IOError(errno.EACCES, 'Permission denied')
|
||||
exc = IOError(errno.EACCES, "Permission denied")
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch
|
||||
mock_open,
|
||||
)
|
||||
from tests.support.mock import patch, mock_open
|
||||
|
||||
import salt.modules.mymod as mymod
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
def test_something(self):
|
||||
contents = {
|
||||
'/etc/foo.conf': [
|
||||
textwrap.dedent('''\
|
||||
"/etc/foo.conf": [
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
foo
|
||||
bar
|
||||
'''),
|
||||
textwrap.dedent('''\
|
||||
"""
|
||||
),
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'''),
|
||||
"""
|
||||
),
|
||||
],
|
||||
'/etc/b*.conf': [
|
||||
IOError(errno.ENOENT, 'No such file or directory'),
|
||||
textwrap.dedent('''\
|
||||
"/etc/b*.conf": [
|
||||
IOError(errno.ENOENT, "No such file or directory"),
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
one
|
||||
two
|
||||
three
|
||||
'''),
|
||||
"""
|
||||
),
|
||||
],
|
||||
}
|
||||
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()
|
||||
assert result is True
|
||||
|
||||
|
@ -423,8 +409,8 @@ so, just add an ``as`` clause to the end of the ``patch`` statement:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n')
|
||||
with patch('salt.utils.files.fopen', fopen_mock) as m_open:
|
||||
fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
|
||||
with patch("salt.utils.files.fopen", fopen_mock) as m_open:
|
||||
# do testing here
|
||||
...
|
||||
...
|
||||
|
@ -448,29 +434,25 @@ several useful attributes:
|
|||
.. code-block:: python
|
||||
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
patch
|
||||
mock_open,
|
||||
MockCall,
|
||||
)
|
||||
from tests.support.mock import patch, mock_open, MockCall
|
||||
|
||||
import salt.modules.mymod as mymod
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
|
||||
class MyAwesomeTestCase(TestCase):
|
||||
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()
|
||||
# Assert that only two opens attempted
|
||||
assert m_open.call_count == 2
|
||||
# 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
|
||||
# second was for binary write.
|
||||
assert m_open.calls == [
|
||||
MockCall('/etc/foo.conf', 'rb'),
|
||||
MockCall('/etc/foo.conf', 'wb'),
|
||||
MockCall("/etc/foo.conf", "rb"),
|
||||
MockCall("/etc/foo.conf", "wb"),
|
||||
]
|
||||
|
||||
Note that ``MockCall`` is imported from ``tests.support.mock`` in the above
|
||||
|
@ -526,35 +508,35 @@ Examples
|
|||
|
||||
.. 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
|
||||
mymod.myfunc()
|
||||
# Check that only the expected file was opened, and that it was opened
|
||||
# only once.
|
||||
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 = 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
|
||||
# be a list of strings)
|
||||
assert opens[0].write_calls == expected
|
||||
|
||||
.. 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
|
||||
mymod.myfunc()
|
||||
# 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
|
||||
|
||||
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
|
||||
mymod.myfunc()
|
||||
# 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'][1].writelines.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()
|
||||
|
||||
.. _`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
|
||||
|
||||
def create_user(username):
|
||||
qry = 'CREATE USER {0}'.format(username)
|
||||
qry = "CREATE USER {0}".format(username)
|
||||
execute_query(qry)
|
||||
|
||||
|
||||
def execute_query(qry):
|
||||
# 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`
|
||||
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):
|
||||
def test_create_user(self):
|
||||
# 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.
|
||||
db.create_user('testuser')
|
||||
db.create_user("testuser")
|
||||
|
||||
# We could now query our mock object to see which calls were made
|
||||
# to it.
|
||||
|
@ -672,7 +656,7 @@ additional imports for MagicMock:
|
|||
|
||||
# Construct a call object that simulates the way we expected
|
||||
# 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
|
||||
# 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
|
||||
class FibTestCase(TestCase):
|
||||
'''
|
||||
"""
|
||||
This class contains a set of functions that test salt.modules.fib.
|
||||
'''
|
||||
"""
|
||||
|
||||
def test_fib(self):
|
||||
'''
|
||||
"""
|
||||
To create a unit test, we should prefix the name with `test_' so
|
||||
that it's recognized by the test runner.
|
||||
'''
|
||||
"""
|
||||
fib_five = (0, 1, 1, 2, 3)
|
||||
self.assertEqual(fib.calculate(5), fib_five)
|
||||
|
||||
|
@ -774,7 +759,7 @@ Consider the following function from salt/modules/linux_sysctl.py.
|
|||
.. code-block:: python
|
||||
|
||||
def get(name):
|
||||
'''
|
||||
"""
|
||||
Return a single sysctl parameter for this minion
|
||||
|
||||
CLI Example:
|
||||
|
@ -782,9 +767,9 @@ Consider the following function from salt/modules/linux_sysctl.py.
|
|||
.. code-block:: bash
|
||||
|
||||
salt '*' sysctl.get net.ipv4.ip_forward
|
||||
'''
|
||||
cmd = 'sysctl -n {0}'.format(name)
|
||||
out = __salt__['cmd.run'](cmd)
|
||||
"""
|
||||
cmd = "sysctl -n {0}".format(name)
|
||||
out = __salt__["cmd.run"](cmd)
|
||||
return out
|
||||
|
||||
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
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
MagicMock,
|
||||
patch,
|
||||
)
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
"""
|
||||
TestCase for salt.modules.linux_sysctl module
|
||||
'''
|
||||
"""
|
||||
|
||||
def test_get(self):
|
||||
'''
|
||||
"""
|
||||
Tests the return of get function
|
||||
'''
|
||||
"""
|
||||
mock_cmd = MagicMock(return_value=1)
|
||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run': mock_cmd}):
|
||||
self.assertEqual(linux_sysctl.get('net.ipv4.ip_forward'), 1)
|
||||
with patch.dict(linux_sysctl.__salt__, {"cmd.run": mock_cmd}):
|
||||
self.assertEqual(linux_sysctl.get("net.ipv4.ip_forward"), 1)
|
||||
|
||||
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
|
||||
|
@ -847,7 +829,7 @@ salt/modules/linux_sysctl.py source file.
|
|||
.. code-block:: python
|
||||
|
||||
def assign(name, value):
|
||||
'''
|
||||
"""
|
||||
Assign a single sysctl parameter for this minion
|
||||
|
||||
CLI Example:
|
||||
|
@ -855,31 +837,30 @@ salt/modules/linux_sysctl.py source file.
|
|||
.. code-block:: bash
|
||||
|
||||
salt '*' sysctl.assign net.ipv4.ip_forward 1
|
||||
'''
|
||||
"""
|
||||
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):
|
||||
raise CommandExecutionError('sysctl {0} does not exist'.format(name))
|
||||
raise CommandExecutionError("sysctl {0} does not exist".format(name))
|
||||
|
||||
ret = {}
|
||||
cmd = 'sysctl -w {0}="{1}"'.format(name, value)
|
||||
data = __salt__['cmd.run_all'](cmd)
|
||||
out = data['stdout']
|
||||
err = data['stderr']
|
||||
data = __salt__["cmd.run_all"](cmd)
|
||||
out = data["stdout"]
|
||||
err = data["stderr"]
|
||||
|
||||
# Example:
|
||||
# # sysctl -w 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),
|
||||
re.escape(value)))
|
||||
regex = re.compile(r"^{0}\s+=\s+{1}$".format(re.escape(name), re.escape(value)))
|
||||
|
||||
if not regex.match(out) or 'Invalid argument' in str(err):
|
||||
if data['retcode'] != 0 and err:
|
||||
if not regex.match(out) or "Invalid argument" in str(err):
|
||||
if data["retcode"] != 0 and err:
|
||||
error = err
|
||||
else:
|
||||
error = out
|
||||
raise CommandExecutionError('sysctl -w failed: {0}'.format(error))
|
||||
new_name, new_value = out.split(' = ', 1)
|
||||
raise CommandExecutionError("sysctl -w failed: {0}".format(error))
|
||||
new_name, new_value = out.split(" = ", 1)
|
||||
ret[new_name] = new_value
|
||||
return ret
|
||||
|
||||
|
@ -904,53 +885,63 @@ with.
|
|||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import (
|
||||
MagicMock,
|
||||
patch,
|
||||
)
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
"""
|
||||
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):
|
||||
'''
|
||||
"""
|
||||
Tests if /proc/sys/<kernel-subsystem> exists or not
|
||||
'''
|
||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '',
|
||||
'stdout': 'net.ipv4.ip_forward = 1'}
|
||||
"""
|
||||
cmd = {
|
||||
"pid": 1337,
|
||||
"retcode": 0,
|
||||
"stderr": "",
|
||||
"stdout": "net.ipv4.ip_forward = 1",
|
||||
}
|
||||
mock_cmd = MagicMock(return_value=cmd)
|
||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
||||
self.assertRaises(CommandExecutionError,
|
||||
linux_sysctl.assign,
|
||||
'net.ipv4.ip_forward', 1)
|
||||
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||
self.assertRaises(
|
||||
CommandExecutionError, linux_sysctl.assign, "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):
|
||||
'''
|
||||
"""
|
||||
Tests if the assignment was successful or not
|
||||
'''
|
||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr':
|
||||
'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
|
||||
'stdout': 'net.ipv4.ip_forward = backward'}
|
||||
"""
|
||||
cmd = {
|
||||
"pid": 1337,
|
||||
"retcode": 0,
|
||||
"stderr": 'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
|
||||
"stdout": "net.ipv4.ip_forward = backward",
|
||||
}
|
||||
mock_cmd = MagicMock(return_value=cmd)
|
||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
||||
self.assertRaises(CommandExecutionError,
|
||||
linux_sysctl.assign,
|
||||
'net.ipv4.ip_forward', 'backward')
|
||||
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||
self.assertRaises(
|
||||
CommandExecutionError,
|
||||
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):
|
||||
'''
|
||||
"""
|
||||
Tests the return of successful assign function
|
||||
'''
|
||||
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '',
|
||||
'stdout': 'net.ipv4.ip_forward = 1'}
|
||||
ret = {'net.ipv4.ip_forward': '1'}
|
||||
"""
|
||||
cmd = {
|
||||
"pid": 1337,
|
||||
"retcode": 0,
|
||||
"stderr": "",
|
||||
"stdout": "net.ipv4.ip_forward = 1",
|
||||
}
|
||||
ret = {"net.ipv4.ip_forward": "1"}
|
||||
mock_cmd = MagicMock(return_value=cmd)
|
||||
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}):
|
||||
self.assertEqual(linux_sysctl.assign(
|
||||
'net.ipv4.ip_forward', 1), ret)
|
||||
with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
||||
self.assertEqual(linux_sysctl.assign("net.ipv4.ip_forward", 1), ret)
|
||||
|
|
|
@ -84,13 +84,11 @@ The following code will check for a single event:
|
|||
import salt.config
|
||||
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(
|
||||
'master',
|
||||
sock_dir=opts['sock_dir'],
|
||||
transport=opts['transport'],
|
||||
opts=opts)
|
||||
"master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
|
||||
)
|
||||
|
||||
data = event.get_event()
|
||||
|
||||
|
@ -106,15 +104,15 @@ instead of the default 5.
|
|||
|
||||
.. 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``:
|
||||
|
||||
.. 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
|
||||
|
@ -124,7 +122,7 @@ The iter_events method also accepts a tag but not a wait time:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
for data in event.iter_events(tag='salt/auth'):
|
||||
for data in event.iter_events(tag="salt/auth"):
|
||||
print(data)
|
||||
|
||||
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.utils.event
|
||||
|
||||
opts = salt.config.client_config('/etc/salt/master')
|
||||
opts = salt.config.client_config("/etc/salt/master")
|
||||
|
||||
sevent = salt.utils.event.get_event(
|
||||
'master',
|
||||
sock_dir=opts['sock_dir'],
|
||||
transport=opts['transport'],
|
||||
opts=opts)
|
||||
"master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
|
||||
)
|
||||
|
||||
while True:
|
||||
ret = sevent.get_event(full=True)
|
||||
if ret is None:
|
||||
continue
|
||||
|
||||
if fnmatch.fnmatch(ret['tag'], 'salt/job/*/ret/*'):
|
||||
do_something_with_job_return(ret['data'])
|
||||
if fnmatch.fnmatch(ret["tag"], "salt/job/*/ret/*"):
|
||||
do_something_with_job_return(ret["data"])
|
||||
|
||||
Firing Events
|
||||
=============
|
||||
|
@ -184,12 +180,12 @@ a minion on a non-Windows system:
|
|||
# Job on minion
|
||||
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)
|
||||
|
||||
for evdata in event.iter_events(match_type = 'regex',
|
||||
tag = 'custom/.*'):
|
||||
for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
|
||||
# do your processing here...
|
||||
...
|
||||
|
||||
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)
|
||||
event = salt.utils.event.MinionEvent(opts)
|
||||
|
||||
for evdata in event.iter_events(match_type = 'regex',
|
||||
tag = 'custom/.*'):
|
||||
for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
|
||||
# do your processing here...
|
||||
...
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -224,19 +220,20 @@ easily done using the normal cross-calling syntax:
|
|||
|
||||
# /srv/salt/_modules/my_custom_module.py
|
||||
|
||||
|
||||
def do_something():
|
||||
'''
|
||||
"""
|
||||
Do something and fire an event to the master when finished
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' my_custom_module:do_something
|
||||
'''
|
||||
"""
|
||||
# do something!
|
||||
__salt__['event.send']('myco/my_custom_module/finished', {
|
||||
'finished': True,
|
||||
'message': "The something is finished!",
|
||||
})
|
||||
__salt__["event.send"](
|
||||
"myco/my_custom_module/finished",
|
||||
{"finished": True, "message": "The something is finished!",},
|
||||
)
|
||||
|
||||
From Custom Python Scripts
|
||||
--------------------------
|
||||
|
@ -257,3 +254,4 @@ done at the CLI:
|
|||
|
||||
if not ret:
|
||||
# the event could not be sent, process the error here
|
||||
...
|
||||
|
|
|
@ -58,9 +58,9 @@ Example Process Class
|
|||
self.opts = opts
|
||||
|
||||
def run(self):
|
||||
self.event = SaltEvent('master', self.opts['sock_dir'])
|
||||
self.event = SaltEvent("master", self.opts["sock_dir"])
|
||||
i = 0
|
||||
|
||||
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)
|
||||
|
|
|
@ -146,12 +146,12 @@ dictionary. For example:
|
|||
.. code-block:: python
|
||||
|
||||
def yourfunction():
|
||||
# initialize a grains dictionary
|
||||
grains = {}
|
||||
# Some code for logic that sets grains like
|
||||
grains['yourcustomgrain'] = True
|
||||
grains['anothergrain'] = 'somevalue'
|
||||
return grains
|
||||
# initialize a grains dictionary
|
||||
grains = {}
|
||||
# Some code for logic that sets grains like
|
||||
grains["yourcustomgrain"] = True
|
||||
grains["anothergrain"] = "somevalue"
|
||||
return 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.
|
||||
|
@ -205,14 +205,14 @@ grain data structure. For example, consider this custom grain file:
|
|||
|
||||
#!/usr/bin/env python
|
||||
def _my_custom_grain():
|
||||
my_grain = {'foo': 'bar', 'hello': 'world'}
|
||||
my_grain = {"foo": "bar", "hello": "world"}
|
||||
return my_grain
|
||||
|
||||
|
||||
def main():
|
||||
# initialize a grains dictionary
|
||||
grains = {}
|
||||
grains['my_grains'] = _my_custom_grain()
|
||||
grains["my_grains"] = _my_custom_grain()
|
||||
return grains
|
||||
|
||||
The output of this example renders like so:
|
||||
|
|
|
@ -379,7 +379,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
('defabcdef',)
|
||||
("defabcdef",)
|
||||
|
||||
|
||||
.. jinja_ref:: regex_match
|
||||
|
@ -740,7 +740,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'new': [4], 'old': [3]}
|
||||
{"new": [4], "old": [3]}
|
||||
|
||||
|
||||
.. jinja_ref:: compare_dicts
|
||||
|
@ -762,7 +762,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'a': {'new': 'c', 'old': 'b'}}
|
||||
{"a": {"new": "c", "old": "b"}}
|
||||
|
||||
|
||||
.. jinja_ref:: is_hex
|
||||
|
@ -1018,7 +1018,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'a': '\xd0\x94'}
|
||||
{"a": "\xd0\x94"}
|
||||
|
||||
|
||||
.. jinja_ref:: tojson
|
||||
|
@ -1381,7 +1381,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'c1': 'foo'}
|
||||
{"c1": "foo"}
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
|
@ -1391,7 +1391,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
'default'
|
||||
"default"
|
||||
|
||||
|
||||
.. jinja_ref:: json_query
|
||||
|
@ -1571,7 +1571,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
['192.168.0.1', 'fe80::']
|
||||
["192.168.0.1", "fe80::"]
|
||||
|
||||
|
||||
.. jinja_ref:: ipv4
|
||||
|
@ -1594,7 +1594,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
['192.168.0.1']
|
||||
["192.168.0.1"]
|
||||
|
||||
|
||||
.. jinja_ref:: ipv6
|
||||
|
@ -1617,7 +1617,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
['fe80::']
|
||||
["fe80::"]
|
||||
|
||||
|
||||
.. jinja_ref:: network_hosts
|
||||
|
@ -1644,7 +1644,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
['192.168.0.1', '192.168.0.2']
|
||||
["192.168.0.1", "192.168.0.2"]
|
||||
|
||||
|
||||
.. jinja_ref:: network_size
|
||||
|
@ -1947,7 +1947,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
[{'value': 3}]
|
||||
[{"value": 3}]
|
||||
|
||||
.. jinja_ref:: match
|
||||
|
||||
|
@ -1976,7 +1976,7 @@ Returns:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
[{'value': 'b'}, {'value': 'c'}]
|
||||
[{"value": "b"}, {"value": "c"}]
|
||||
|
||||
|
||||
Test supports additional optional arguments: ``ignorecase``, ``multiline``
|
||||
|
|
|
@ -61,8 +61,9 @@ bare-bones example:
|
|||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'customtop'
|
||||
__virtualname__ = "customtop"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -72,8 +73,8 @@ bare-bones example:
|
|||
|
||||
|
||||
def top(**kwargs):
|
||||
log.debug('Calling top in customtop')
|
||||
return {'base': ['test']}
|
||||
log.debug("Calling top in customtop")
|
||||
return {"base": ["test"]}
|
||||
|
||||
`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 salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||
|
||||
|
||||
def match(self, tgt):
|
||||
'''
|
||||
"""
|
||||
Determines if this host is on the list
|
||||
'''
|
||||
"""
|
||||
if isinstance(tgt, six.string_types):
|
||||
# The stock matcher splits on `,`. Change to `/` below.
|
||||
tgt = tgt.split('/')
|
||||
return bool(self.opts['id'] in tgt)
|
||||
tgt = tgt.split("/")
|
||||
return bool(self.opts["id"] in tgt)
|
||||
|
||||
|
||||
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():
|
||||
...
|
||||
do stuff
|
||||
# do stuff
|
||||
...
|
||||
if some_error_condition:
|
||||
__context__['retcode'] = 1
|
||||
__context__["retcode"] = 1
|
||||
return result
|
||||
|
||||
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
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Only work on proxy
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
if salt.utils.platform.is_proxy() and \
|
||||
__opts__['proxy']['proxytype'] == 'ssh_sample':
|
||||
if (
|
||||
salt.utils.platform.is_proxy()
|
||||
and __opts__["proxy"]["proxytype"] == "ssh_sample"
|
||||
):
|
||||
return __virtualname__
|
||||
except KeyError:
|
||||
pass
|
||||
|
@ -110,10 +112,10 @@ to ``__proxy__``. This enables patterns like
|
|||
.. code-block:: python
|
||||
|
||||
def get_ip(proxy):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
|
@ -397,10 +399,10 @@ and status; "package" installation, and a ping.
|
|||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
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
|
||||
'''
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import python libs
|
||||
|
@ -410,7 +412,7 @@ and status; "package" installation, and a ping.
|
|||
HAS_REST_EXAMPLE = True
|
||||
|
||||
# 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
|
||||
|
@ -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
|
||||
# entry when the module is loaded.
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Only return if all the modules are available
|
||||
'''
|
||||
log.debug('rest_sample proxy __virtual__() called...')
|
||||
"""
|
||||
log.debug("rest_sample proxy __virtual__() called...")
|
||||
return True
|
||||
|
||||
|
||||
def _complicated_function_that_determines_if_alive():
|
||||
return True
|
||||
|
||||
|
||||
# Every proxy module needs an 'init', though you can
|
||||
# just put DETAILS['initialized'] = True here if nothing
|
||||
# else needs to be done.
|
||||
|
||||
|
||||
def init(opts):
|
||||
log.debug('rest_sample proxy init() called...')
|
||||
DETAILS['initialized'] = True
|
||||
log.debug("rest_sample proxy init() called...")
|
||||
DETAILS["initialized"] = True
|
||||
|
||||
# Save the REST URL
|
||||
DETAILS['url'] = opts['proxy']['url']
|
||||
DETAILS["url"] = opts["proxy"]["url"]
|
||||
|
||||
# Make sure the REST URL ends with a '/'
|
||||
if not DETAILS['url'].endswith('/'):
|
||||
DETAILS['url'] += '/'
|
||||
if not DETAILS["url"].endswith("/"):
|
||||
DETAILS["url"] += "/"
|
||||
|
||||
|
||||
def alive(opts):
|
||||
'''
|
||||
"""
|
||||
This function returns a flag with the connection state.
|
||||
It is very useful when the proxy minion establishes the communication
|
||||
via a channel that requires a more elaborated keep-alive mechanism, e.g.
|
||||
NETCONF over SSH.
|
||||
'''
|
||||
log.debug('rest_sample proxy alive() called...')
|
||||
"""
|
||||
log.debug("rest_sample proxy alive() called...")
|
||||
return _complicated_function_that_determines_if_alive()
|
||||
|
||||
|
||||
def initialized():
|
||||
'''
|
||||
"""
|
||||
Since grains are loaded in many different places and some of those
|
||||
places occur before the proxy can be initialized, return whether
|
||||
our init() function has been called
|
||||
'''
|
||||
return DETAILS.get('initialized', False)
|
||||
"""
|
||||
return DETAILS.get("initialized", False)
|
||||
|
||||
|
||||
def grains():
|
||||
'''
|
||||
"""
|
||||
Get the grains from the proxied device
|
||||
'''
|
||||
if not DETAILS.get('grains_cache', {}):
|
||||
r = salt.utils.http.query(DETAILS['url']+'info', decode_type='json', decode=True)
|
||||
DETAILS['grains_cache'] = r['dict']
|
||||
return DETAILS['grains_cache']
|
||||
"""
|
||||
if not DETAILS.get("grains_cache", {}):
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "info", decode_type="json", decode=True
|
||||
)
|
||||
DETAILS["grains_cache"] = r["dict"]
|
||||
return DETAILS["grains_cache"]
|
||||
|
||||
|
||||
def grains_refresh():
|
||||
'''
|
||||
"""
|
||||
Refresh the grains from the proxied device
|
||||
'''
|
||||
DETAILS['grains_cache'] = None
|
||||
"""
|
||||
DETAILS["grains_cache"] = None
|
||||
return grains()
|
||||
|
||||
|
||||
def fns():
|
||||
return {'details': 'This key is here because a function in '
|
||||
'grains/rest_sample.py called fns() here in the proxymodule.'}
|
||||
return {
|
||||
"details": "This key is here because a function in "
|
||||
"grains/rest_sample.py called fns() here in the proxymodule."
|
||||
}
|
||||
|
||||
|
||||
def service_start(name):
|
||||
'''
|
||||
"""
|
||||
Start a "service" on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'service/start/'+name, decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "service/start/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def service_stop(name):
|
||||
'''
|
||||
"""
|
||||
Stop a "service" on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'service/stop/'+name, decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "service/stop/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def service_restart(name):
|
||||
'''
|
||||
"""
|
||||
Restart a "service" on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'service/restart/'+name, decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "service/restart/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def service_list():
|
||||
'''
|
||||
"""
|
||||
List "services" on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'service/list', decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "service/list", decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def service_status(name):
|
||||
'''
|
||||
"""
|
||||
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)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "service/status/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def package_list():
|
||||
'''
|
||||
"""
|
||||
List "packages" installed on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'package/list', decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "package/list", decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def package_install(name, **kwargs):
|
||||
'''
|
||||
"""
|
||||
Install a "package" on the REST server
|
||||
'''
|
||||
cmd = DETAILS['url']+'package/install/'+name
|
||||
if kwargs.get('version', False):
|
||||
cmd += '/'+kwargs['version']
|
||||
"""
|
||||
cmd = DETAILS["url"] + "package/install/" + name
|
||||
if kwargs.get("version", False):
|
||||
cmd += "/" + kwargs["version"]
|
||||
else:
|
||||
cmd += '/1.0'
|
||||
r = salt.utils.http.query(cmd, decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
cmd += "/1.0"
|
||||
r = salt.utils.http.query(cmd, decode_type="json", decode=True)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def fix_outage():
|
||||
r = salt.utils.http.query(DETAILS['url']+'fix_outage')
|
||||
r = salt.utils.http.query(DETAILS["url"] + "fix_outage")
|
||||
return r
|
||||
|
||||
|
||||
def uptodate(name):
|
||||
|
||||
'''
|
||||
"""
|
||||
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)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def package_remove(name):
|
||||
|
||||
'''
|
||||
"""
|
||||
Remove a "package" on the REST server
|
||||
'''
|
||||
r = salt.utils.http.query(DETAILS['url']+'package/remove/'+name, decode_type='json', decode=True)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def package_status(name):
|
||||
'''
|
||||
"""
|
||||
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)
|
||||
return r['dict']
|
||||
"""
|
||||
r = salt.utils.http.query(
|
||||
DETAILS["url"] + "package/status/" + name, decode_type="json", decode=True
|
||||
)
|
||||
return r["dict"]
|
||||
|
||||
|
||||
def ping():
|
||||
'''
|
||||
"""
|
||||
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:
|
||||
return r['dict'].get('ret', False)
|
||||
return r["dict"].get("ret", False)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def shutdown(opts):
|
||||
'''
|
||||
"""
|
||||
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:
|
||||
|
@ -702,19 +729,23 @@ Example from ``salt/grains/rest_sample.py``:
|
|||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
Generate baseline proxy minion grains
|
||||
'''
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import salt.utils.platform
|
||||
|
||||
__proxyenabled__ = ['rest_sample']
|
||||
__proxyenabled__ = ["rest_sample"]
|
||||
|
||||
__virtualname__ = "rest_sample"
|
||||
|
||||
__virtualname__ = 'rest_sample'
|
||||
|
||||
def __virtual__():
|
||||
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__
|
||||
except KeyError:
|
||||
pass
|
||||
|
@ -746,12 +777,12 @@ This proxymodule enables "package" installation.
|
|||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
This is a simple proxy-minion designed to connect to and communicate with
|
||||
a server that exposes functionality via SSH.
|
||||
This can be used as an option when the device does not provide
|
||||
an api over HTTP and doesn't have the python stack to run a minion.
|
||||
'''
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import python libs
|
||||
|
@ -763,7 +794,7 @@ This proxymodule enables "package" installation.
|
|||
from salt.utils.vt import TerminalException
|
||||
|
||||
# This must be present or the Salt loader won't load this module
|
||||
__proxyenabled__ = ['ssh_sample']
|
||||
__proxyenabled__ = ["ssh_sample"]
|
||||
|
||||
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
|
||||
# entry when the module is loaded.
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Only return if all the modules are available
|
||||
'''
|
||||
log.info('ssh_sample proxy __virtual__() called...')
|
||||
"""
|
||||
log.info("ssh_sample proxy __virtual__() called...")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def init(opts):
|
||||
'''
|
||||
"""
|
||||
Required.
|
||||
Can be used to initialize the server connection.
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
DETAILS['server'] = SSHConnection(host=__opts__['proxy']['host'],
|
||||
username=__opts__['proxy']['username'],
|
||||
password=__opts__['proxy']['password'])
|
||||
DETAILS["server"] = SSHConnection(
|
||||
host=__opts__["proxy"]["host"],
|
||||
username=__opts__["proxy"]["username"],
|
||||
password=__opts__["proxy"]["password"],
|
||||
)
|
||||
# connected to the SSH server
|
||||
out, err = DETAILS['server'].sendline('help')
|
||||
out, err = DETAILS["server"].sendline("help")
|
||||
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
|
@ -800,73 +833,73 @@ This proxymodule enables "package" installation.
|
|||
|
||||
|
||||
def shutdown(opts):
|
||||
'''
|
||||
"""
|
||||
Disconnect
|
||||
'''
|
||||
DETAILS['server'].close_connection()
|
||||
"""
|
||||
DETAILS["server"].close_connection()
|
||||
|
||||
|
||||
def parse(out):
|
||||
'''
|
||||
"""
|
||||
Extract json from out.
|
||||
|
||||
Parameter
|
||||
out: Type string. The data returned by the
|
||||
ssh command.
|
||||
'''
|
||||
"""
|
||||
jsonret = []
|
||||
in_json = False
|
||||
for ln_ in out.split('\n'):
|
||||
if '{' in ln_:
|
||||
for ln_ in out.split("\n"):
|
||||
if "{" in ln_:
|
||||
in_json = True
|
||||
if in_json:
|
||||
jsonret.append(ln_)
|
||||
if '}' in ln_:
|
||||
if "}" in ln_:
|
||||
in_json = False
|
||||
return salt.utils.json.loads('\n'.join(jsonret))
|
||||
return salt.utils.json.loads("\n".join(jsonret))
|
||||
|
||||
|
||||
def package_list():
|
||||
'''
|
||||
"""
|
||||
List "packages" by executing a command via ssh
|
||||
This function is called in response to the salt command
|
||||
|
||||
..code-block::bash
|
||||
salt target_minion pkg.list_pkgs
|
||||
|
||||
'''
|
||||
"""
|
||||
# Send the command to execute
|
||||
out, err = DETAILS['server'].sendline('pkg_list')
|
||||
out, err = DETAILS["server"].sendline("pkg_list")
|
||||
|
||||
# "scrape" the output and return the right fields as a dict
|
||||
return parse(out)
|
||||
|
||||
|
||||
def package_install(name, **kwargs):
|
||||
'''
|
||||
"""
|
||||
Install a "package" on the REST server
|
||||
'''
|
||||
cmd = 'pkg_install ' + name
|
||||
if 'version' in kwargs:
|
||||
cmd += '/'+kwargs['version']
|
||||
"""
|
||||
cmd = "pkg_install " + name
|
||||
if "version" in kwargs:
|
||||
cmd += "/" + kwargs["version"]
|
||||
else:
|
||||
cmd += '/1.0'
|
||||
cmd += "/1.0"
|
||||
|
||||
# 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
|
||||
return parse(out)
|
||||
|
||||
|
||||
def package_remove(name):
|
||||
'''
|
||||
"""
|
||||
Remove a "package" on the REST server
|
||||
'''
|
||||
cmd = 'pkg_remove ' + name
|
||||
"""
|
||||
cmd = "pkg_remove " + name
|
||||
|
||||
# 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
|
||||
return parse(out)
|
||||
|
|
|
@ -110,12 +110,12 @@ write Salt States in pure Python:
|
|||
|
||||
#!py
|
||||
|
||||
|
||||
def run():
|
||||
'''
|
||||
"""
|
||||
Install the python-mako package
|
||||
'''
|
||||
return {'include': ['python'],
|
||||
'python-mako': {'pkg': ['installed']}}
|
||||
"""
|
||||
return {"include": ["python"], "python-mako": {"pkg": ["installed"]}}
|
||||
|
||||
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.
|
||||
|
|
|
@ -162,8 +162,8 @@ This addition to the LocalClient api can be used like so:
|
|||
|
||||
import salt.client
|
||||
|
||||
client = salt.client.LocalClient('/etc/salt/master')
|
||||
ret = client.cmd('*', 'cmd.run', ['ls -l'], kwarg={'cwd': '/etc'})
|
||||
client = salt.client.LocalClient("/etc/salt/master")
|
||||
ret = client.cmd("*", "cmd.run", ["ls -l"], kwarg={"cwd": "/etc"})
|
||||
|
||||
This update has been added to all cmd methods in the LocalClient class.
|
||||
|
||||
|
|
|
@ -378,10 +378,7 @@ eAuth Changes
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
[{'web*': ['test.*',
|
||||
'network.*']},
|
||||
'@wheel',
|
||||
'@runner']
|
||||
[{"web*": ["test.*", "network.*"]}, "@wheel", "@runner"]
|
||||
|
||||
- External auth is supported by :ref:`salt-run <salt-run>` and
|
||||
:ref:`salt-key <salt-key>` now. Note that master must be started to
|
||||
|
@ -554,13 +551,15 @@ General Deprecations
|
|||
|
||||
.. 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
|
||||
|
||||
.. 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
|
||||
that also accepts arbitrary keyword arguments, then a new warning informs the
|
||||
|
@ -569,12 +568,13 @@ General Deprecations
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def fcn(msg='', refresh=True, saltenv='base', **kwargs):
|
||||
def fcn(msg="", refresh=True, saltenv="base", **kwargs):
|
||||
...
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 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
|
||||
that does not accept arbitrary keyword arguments, then python will issue an
|
||||
|
@ -582,12 +582,13 @@ General Deprecations
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def fcn(msg='', refresh=True, saltenv='base'):
|
||||
def fcn(msg="", refresh=True, saltenv="base"):
|
||||
...
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 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
|
||||
function, then undefined behavior will occur, as the removal of ``env`` and
|
||||
|
@ -596,12 +597,13 @@ General Deprecations
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def fcn(msg='', refresh=True, saltenv='base'):
|
||||
def fcn(msg="", refresh=True, saltenv="base"):
|
||||
...
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 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``:
|
||||
|
||||
|
|
|
@ -156,12 +156,11 @@ they are being loaded for the correct proxytype, example below:
|
|||
.. code-block:: python
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Only work on proxy
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
if salt.utils.is_proxy() and \
|
||||
__opts__['proxy']['proxytype'] == 'ssh_sample':
|
||||
if salt.utils.is_proxy() and __opts__["proxy"]["proxytype"] == "ssh_sample":
|
||||
return __virtualname__
|
||||
except KeyError:
|
||||
pass
|
||||
|
@ -185,10 +184,10 @@ to ``__proxy__``. This enables patterns like
|
|||
.. code-block:: python
|
||||
|
||||
def get_ip(proxy):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
|
|
|
@ -675,7 +675,7 @@ Additional Features
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
ret1 = __salt__['salt.execute']('*', 'mod.fun')
|
||||
ret1 = __salt__["salt.execute"]("*", "mod.fun")
|
||||
|
||||
New Modules
|
||||
===========
|
||||
|
|
|
@ -216,10 +216,10 @@ they failed. Here's some example pseudocode:
|
|||
|
||||
def myrunner():
|
||||
...
|
||||
do stuff
|
||||
# do stuff
|
||||
...
|
||||
if some_error_condition:
|
||||
__context__['retcode'] = 1
|
||||
__context__["retcode"] = 1
|
||||
return result
|
||||
|
||||
Variable Update Intervals for Fileserver Backends
|
||||
|
|
|
@ -29,7 +29,7 @@ occurred:
|
|||
.. code-block:: python
|
||||
|
||||
if something_went_wrong:
|
||||
__context__['retcode'] = 42
|
||||
__context__["retcode"] = 42
|
||||
|
||||
This is actually how states signal that they have failed. Different cases
|
||||
result in different codes being set in the :ref:`__context__ <dunder-context>`
|
||||
|
|
|
@ -140,7 +140,7 @@ something like:
|
|||
|
||||
.. 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
|
||||
above in Jinja, you would use:
|
||||
|
@ -175,7 +175,7 @@ in the module as well:
|
|||
.. code-block:: python
|
||||
|
||||
__func_alias__ = {
|
||||
'set_': 'set',
|
||||
"set_": "set",
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
conn = sqlite3.connect(__opts__['spm_db'], isolation_level=None)
|
||||
conn = sqlite3.connect(__opts__["spm_db"], isolation_level=None)
|
||||
...
|
||||
return conn
|
||||
|
||||
|
@ -259,7 +259,7 @@ This function will not generally be more complex than:
|
|||
.. code-block:: python
|
||||
|
||||
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())
|
||||
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.
|
||||
|
||||
.. _`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
|
||||
|
||||
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
|
||||
be overridden with the ``method`` argument:
|
||||
|
||||
.. 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
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data=json.dumps(mydict)
|
||||
"http://example.com/post/url", method="POST", data=json.dumps(mydict)
|
||||
)
|
||||
|
||||
Data Formatting and Templating
|
||||
|
@ -96,9 +94,7 @@ the file (untemplated):
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data_file='/srv/salt/somefile.xml'
|
||||
"http://example.com/post/url", method="POST", data_file="/srv/salt/somefile.xml"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data_file='/srv/salt/somefile.jinja',
|
||||
"http://example.com/post/url",
|
||||
method="POST",
|
||||
data_file="/srv/salt/somefile.jinja",
|
||||
data_render=True,
|
||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
||||
template_dict={"key1": "value1", "key2": "value2"},
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data_file='/srv/salt/somefile.mako',
|
||||
"http://example.com/post/url",
|
||||
method="POST",
|
||||
data_file="/srv/salt/somefile.mako",
|
||||
data_render=True,
|
||||
data_renderer='mako',
|
||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
||||
data_renderer="mako",
|
||||
template_dict={"key1": "value1", "key2": "value2"},
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data_file='/srv/salt/somefile.jinja',
|
||||
"http://example.com/post/url",
|
||||
method="POST",
|
||||
data_file="/srv/salt/somefile.jinja",
|
||||
data_render=True,
|
||||
template_dict={'key1': 'value1', 'key2': 'value2'},
|
||||
opts=__opts__
|
||||
template_dict={"key1": "value1", "key2": "value2"},
|
||||
opts=__opts__,
|
||||
)
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/post/url',
|
||||
method='POST',
|
||||
data_file='/srv/salt/somefile.jinja',
|
||||
"http://example.com/post/url",
|
||||
method="POST",
|
||||
data_file="/srv/salt/somefile.jinja",
|
||||
data_render=True,
|
||||
template_dict={'key1': 'value1', 'key2': 'value2'},
|
||||
node='master'
|
||||
template_dict={"key1": "value1", "key2": "value2"},
|
||||
node="master",
|
||||
)
|
||||
|
||||
Headers
|
||||
|
@ -165,12 +161,12 @@ a Python dict.
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com/delete/url',
|
||||
method='POST',
|
||||
header_file='/srv/salt/headers.jinja',
|
||||
"http://example.com/delete/url",
|
||||
method="POST",
|
||||
header_file="/srv/salt/headers.jinja",
|
||||
header_render=True,
|
||||
header_renderer='jinja',
|
||||
template_dict={'key1': 'value1', 'key2': 'value2'}
|
||||
header_renderer="jinja",
|
||||
template_dict={"key1": "value1", "key2": "value2"},
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
username='larry',
|
||||
password='5700g3543v4r',
|
||||
"http://example.com", username="larry", password="5700g3543v4r",
|
||||
)
|
||||
|
||||
Cookies and Sessions
|
||||
|
@ -199,10 +193,7 @@ are turned off by default. To turn cookies on, set ``cookies`` to True.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
cookies=True
|
||||
)
|
||||
salt.utils.http.query("http://example.com", cookies=True)
|
||||
|
||||
By default cookies are stored in Salt's cache directory, normally
|
||||
``/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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
cookies=True,
|
||||
cookie_jar='/path/to/cookie_jar.txt'
|
||||
"http://example.com", cookies=True, cookie_jar="/path/to/cookie_jar.txt"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
"http://example.com",
|
||||
cookies=True,
|
||||
cookie_jar='/path/to/cookie_jar.txt',
|
||||
cookie_format='mozilla'
|
||||
cookie_jar="/path/to/cookie_jar.txt",
|
||||
cookie_format="mozilla",
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
persist_session=True,
|
||||
session_cookie_jar='/path/to/jar.p'
|
||||
"http://example.com", 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
|
||||
|
@ -265,11 +252,7 @@ these are set in the minion configuration file.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
opts=__opts__,
|
||||
backend='tornado'
|
||||
)
|
||||
salt.utils.http.query("http://example.com", opts=__opts__, backend="tornado")
|
||||
|
||||
Return Data
|
||||
~~~~~~~~~~~
|
||||
|
@ -294,10 +277,7 @@ force either JSON or XML decoding, the ``decode_type`` may be set:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
decode_type='xml'
|
||||
)
|
||||
salt.utils.http.query("http://example.com", decode_type="xml")
|
||||
|
||||
Once translated, the return dict from ``query()`` will include a dict called
|
||||
``dict``.
|
||||
|
@ -307,10 +287,7 @@ turned off.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
decode=False
|
||||
)
|
||||
salt.utils.http.query("http://example.com", decode=False)
|
||||
|
||||
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
|
||||
|
@ -321,12 +298,7 @@ as required. However, each must individually be turned on.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
status=True,
|
||||
headers=True,
|
||||
text=True
|
||||
)
|
||||
salt.utils.http.query("http://example.com", status=True, headers=True, text=True)
|
||||
|
||||
The return from these will be found in the return dict as ``status``,
|
||||
``headers`` and ``text``, respectively.
|
||||
|
@ -341,11 +313,11 @@ to be returned to the user in order to do this.
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'http://example.com',
|
||||
"http://example.com",
|
||||
text=False,
|
||||
headers=False,
|
||||
text_out='/path/to/url_download.txt',
|
||||
headers_out='/path/to/headers_download.txt',
|
||||
text_out="/path/to/url_download.txt",
|
||||
headers_out="/path/to/headers_download.txt",
|
||||
)
|
||||
|
||||
SSL Verification
|
||||
|
@ -356,8 +328,7 @@ debugging purposes, SSL verification can be turned off.
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'https://example.com',
|
||||
verify_ssl=False,
|
||||
"https://example.com", verify_ssl=False,
|
||||
)
|
||||
|
||||
CA Bundles
|
||||
|
@ -373,8 +344,7 @@ using the ``ca_bundle`` variable.
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.query(
|
||||
'https://example.com',
|
||||
ca_bundle='/path/to/ca_bundle.pem',
|
||||
"https://example.com", ca_bundle="/path/to/ca_bundle.pem",
|
||||
)
|
||||
|
||||
Updating CA Bundles
|
||||
|
@ -395,8 +365,8 @@ download which is hazardous or does not meet the needs of the user.
|
|||
.. code-block:: python
|
||||
|
||||
salt.utils.http.update_ca_bundle(
|
||||
target='/path/to/ca-bundle.crt',
|
||||
source='https://example.com/path/to/ca-bundle.crt',
|
||||
target="/path/to/ca-bundle.crt",
|
||||
source="https://example.com/path/to/ca-bundle.crt",
|
||||
opts=__opts__,
|
||||
)
|
||||
|
||||
|
@ -423,10 +393,10 @@ otherwise reasonable to add to the bundle file.
|
|||
salt.utils.http.update_ca_bundle(
|
||||
opts=__opts__,
|
||||
merge_files=[
|
||||
'/etc/ssl/private_cert_1.pem',
|
||||
'/etc/ssl/private_cert_2.pem',
|
||||
'/etc/ssl/private_cert_3.pem',
|
||||
]
|
||||
"/etc/ssl/private_cert_1.pem",
|
||||
"/etc/ssl/private_cert_2.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
|
||||
#!python
|
||||
|
||||
'''
|
||||
"""
|
||||
Functions related to storage servers.
|
||||
'''
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def ips():
|
||||
'''
|
||||
"""
|
||||
Provide a list of all local storage server IPs.
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt \* storage.ips
|
||||
'''
|
||||
"""
|
||||
|
||||
if __grains__.get('virtual', None) in ['VirtualBox', 'oracle']:
|
||||
return ['192.168.33.51', ]
|
||||
if __grains__.get("virtual", None) in ["VirtualBox", "oracle"]:
|
||||
return [
|
||||
"192.168.33.51",
|
||||
]
|
||||
|
||||
colo = __pillar__.get('inventory', {}).get('colo', 'Unknown')
|
||||
return __pillar__.get('storage_servers', {}).get(colo, ['unknown', ])
|
||||
colo = __pillar__.get("inventory", {}).get("colo", "Unknown")
|
||||
return __pillar__.get("storage_servers", {}).get(colo, ["unknown",])
|
||||
|
||||
|
||||
def ip():
|
||||
'''
|
||||
"""
|
||||
Select and return a local storage server IP.
|
||||
|
||||
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
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
numerical_suffix = re.compile(r'^.*(\d+)$')
|
||||
numerical_suffix = re.compile(r"^.*(\d+)$")
|
||||
servers_list = ips()
|
||||
|
||||
m = numerical_suffix.match(__grains__['id'])
|
||||
m = numerical_suffix.match(__grains__["id"])
|
||||
if m:
|
||||
modulus = len(servers_list)
|
||||
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:
|
||||
|
||||
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
|
||||
|
||||
|
||||
`pkg_resources` should be installed, which is normally included in setuptools.
|
||||
|
||||
https://setuptools.readthedocs.io/en/latest/pkg_resources.html
|
||||
|
@ -24,21 +24,23 @@ function:
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name=<NAME>,
|
||||
version=<VERSION>,
|
||||
description=<DESC>,
|
||||
author=<AUTHOR>,
|
||||
author_email=<AUTHOR-EMAIL>,
|
||||
url=' ... ',
|
||||
packages=find_packages(),
|
||||
entry_points='''
|
||||
[salt.loader]
|
||||
engines_dirs = <package>.<loader-module>:engines_dirs
|
||||
fileserver_dirs = <package>.<loader-module>:fileserver_dirs
|
||||
pillar_dirs = <package>.<loader-module>:pillar_dirs
|
||||
returner_dirs = <package>.<loader-module>:returner_dirs
|
||||
roster_dirs = <package>.<loader-module>:roster_dirs
|
||||
''')
|
||||
setup(
|
||||
name=<NAME>,
|
||||
version=<VERSION>,
|
||||
description=<DESC>,
|
||||
author=<AUTHOR>,
|
||||
author_email=<AUTHOR-EMAIL>,
|
||||
url=" ... ",
|
||||
packages=find_packages(),
|
||||
entry_points="""
|
||||
[salt.loader]
|
||||
engines_dirs = <package>.<loader-module>:engines_dirs
|
||||
fileserver_dirs = <package>.<loader-module>:fileserver_dirs
|
||||
pillar_dirs = <package>.<loader-module>:pillar_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
|
||||
|
|
|
@ -37,22 +37,24 @@ Edit it to include needed variables and your new paths
|
|||
.. code-block:: python
|
||||
|
||||
# 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
|
||||
INSTALL_DIR = *location of source code*
|
||||
_location_of_source_code_ = ...
|
||||
INSTALL_DIR = _location_of_source_code_
|
||||
|
||||
CONFIG_DIR = ROOT_DIR + '/etc/salt'
|
||||
CACHE_DIR = ROOT_DIR + '/var/cache/salt'
|
||||
SOCK_DIR = ROOT_DIR + '/var/run/salt'
|
||||
SRV_ROOT_DIR= ROOT_DIR + '/srv'
|
||||
BASE_FILE_ROOTS_DIR = ROOT_DIR + '/srv/salt'
|
||||
BASE_PILLAR_ROOTS_DIR = ROOT_DIR + '/srv/pillar'
|
||||
BASE_MASTER_ROOTS_DIR = ROOT_DIR + '/srv/salt-master'
|
||||
LOGS_DIR = ROOT_DIR + '/var/log/salt'
|
||||
PIDFILE_DIR = ROOT_DIR + '/var/run'
|
||||
CLOUD_DIR = INSTALL_DIR + '/cloud'
|
||||
BOOTSTRAP = CLOUD_DIR + '/deploy/bootstrap-salt.sh'
|
||||
CONFIG_DIR = ROOT_DIR + "/etc/salt"
|
||||
CACHE_DIR = ROOT_DIR + "/var/cache/salt"
|
||||
SOCK_DIR = ROOT_DIR + "/var/run/salt"
|
||||
SRV_ROOT_DIR = ROOT_DIR + "/srv"
|
||||
BASE_FILE_ROOTS_DIR = ROOT_DIR + "/srv/salt"
|
||||
BASE_PILLAR_ROOTS_DIR = ROOT_DIR + "/srv/pillar"
|
||||
BASE_MASTER_ROOTS_DIR = ROOT_DIR + "/srv/salt-master"
|
||||
LOGS_DIR = ROOT_DIR + "/var/log/salt"
|
||||
PIDFILE_DIR = ROOT_DIR + "/var/run"
|
||||
CLOUD_DIR = INSTALL_DIR + "/cloud"
|
||||
BOOTSTRAP = CLOUD_DIR + "/deploy/bootstrap-salt.sh"
|
||||
|
||||
|
||||
Create the directory structure
|
||||
|
|
|
@ -484,12 +484,12 @@ This example shows a very basic Python SLS file:
|
|||
|
||||
#!py
|
||||
|
||||
|
||||
def run():
|
||||
'''
|
||||
"""
|
||||
Install the django package
|
||||
'''
|
||||
return {'include': ['python'],
|
||||
'django': {'pkg': ['installed']}}
|
||||
"""
|
||||
return {"include": ["python"], "django": {"pkg": ["installed"]}}
|
||||
|
||||
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.
|
||||
|
@ -504,8 +504,8 @@ renderer, the above example can be written more succinctly as:
|
|||
|
||||
#!pydsl
|
||||
|
||||
include('python', delayed=True)
|
||||
state('django').pkg.installed()
|
||||
include("python", delayed=True)
|
||||
state("django").pkg.installed()
|
||||
|
||||
The :mod:`pyobjects<salt.renderers.pyobjects>` renderer
|
||||
provides an `"Pythonic"`_ object based approach for building the state data.
|
||||
|
@ -515,7 +515,7 @@ The above example could be written as:
|
|||
|
||||
#!pyobjects
|
||||
|
||||
include('python')
|
||||
include("python")
|
||||
Pkg.installed("django")
|
||||
|
||||
|
||||
|
|
|
@ -146,7 +146,8 @@ a value equivalent to the following python pseudo-code:
|
|||
.. code-block:: python
|
||||
|
||||
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
|
||||
before the state file is processed by the templating engine.
|
||||
|
@ -156,7 +157,7 @@ MAC address for eth0:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
salt['network.hw_addr']('eth0')
|
||||
salt["network.hw_addr"]("eth0")
|
||||
|
||||
To examine the possible arguments to each execution module function,
|
||||
one can examine the `module reference documentation </ref/modules/all>`_:
|
||||
|
|
|
@ -139,10 +139,10 @@ file:
|
|||
.. code-block:: python
|
||||
|
||||
def test_ping(self):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
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
|
||||
|
||||
def test_ping(self):
|
||||
'''
|
||||
"""
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_echo(self):
|
||||
'''
|
||||
"""
|
||||
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
|
||||
``tests/integration/modules/test_aliases.py`` file and
|
||||
|
@ -279,18 +279,13 @@ call should return.
|
|||
.. code-block:: python
|
||||
|
||||
def test_set_target(self):
|
||||
'''
|
||||
"""
|
||||
aliases.set_target and aliases.get_target
|
||||
'''
|
||||
set_ret = self.run_function(
|
||||
'aliases.set_target',
|
||||
alias='fred',
|
||||
target='bob')
|
||||
"""
|
||||
set_ret = self.run_function("aliases.set_target", alias="fred", target="bob")
|
||||
self.assertTrue(set_ret)
|
||||
tgt_ret = self.run_function(
|
||||
'aliases.get_target',
|
||||
alias='fred')
|
||||
self.assertEqual(tgt_ret, 'bob')
|
||||
tgt_ret = self.run_function("aliases.get_target", alias="fred")
|
||||
self.assertEqual(tgt_ret, "bob")
|
||||
|
||||
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.
|
||||
|
@ -335,12 +330,13 @@ the test method:
|
|||
import integration
|
||||
from tests.support.helpers import destructiveTest
|
||||
|
||||
|
||||
class PkgTest(integration.ModuleCase):
|
||||
@destructiveTest
|
||||
def test_pkg_install(self):
|
||||
ret = self.run_function('pkg.install', name='finch')
|
||||
ret = self.run_function("pkg.install", name="finch")
|
||||
self.assertSaltTrueReturn(ret)
|
||||
ret = self.run_function('pkg.purge', name='finch')
|
||||
ret = self.run_function("pkg.purge", name="finch")
|
||||
self.assertSaltTrueReturn(ret)
|
||||
|
||||
|
||||
|
@ -372,13 +368,13 @@ testing the call to ``cp.hash_file``, which is used in ``cp.get_file``.
|
|||
.. code-block:: python
|
||||
|
||||
def test_get_file_not_found(self):
|
||||
'''
|
||||
"""
|
||||
Test if get_file can't find the file.
|
||||
'''
|
||||
with patch('salt.modules.cp.hash_file', MagicMock(return_value=False)):
|
||||
path = 'salt://saltines'
|
||||
dest = '/srv/salt/cheese'
|
||||
ret = ''
|
||||
"""
|
||||
with patch("salt.modules.cp.hash_file", MagicMock(return_value=False)):
|
||||
path = "salt://saltines"
|
||||
dest = "/srv/salt/cheese"
|
||||
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
|
||||
|
@ -446,10 +442,10 @@ can be used
|
|||
# .. inside test
|
||||
with TstSuiteLoggingHandler() as handler:
|
||||
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
|
||||
else:
|
||||
raise AssertionError('Did not find error message')
|
||||
raise AssertionError("Did not find error message")
|
||||
|
||||
|
||||
Automated Test Runs
|
||||
|
|
|
@ -23,15 +23,16 @@ For example, assuming the following simple utility module, saved to
|
|||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
My utils module
|
||||
---------------
|
||||
|
||||
This module contains common functions for use in my other custom types.
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def bar():
|
||||
return 'baz'
|
||||
return "baz"
|
||||
|
||||
Once synced to a minion, this function would be available to other custom Salt
|
||||
types like so:
|
||||
|
@ -39,13 +40,14 @@ types like so:
|
|||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
My awesome execution module
|
||||
---------------------------
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def observe_the_awesomeness():
|
||||
'''
|
||||
"""
|
||||
Prints information from my utility module
|
||||
|
||||
CLI Example:
|
||||
|
@ -53,8 +55,8 @@ types like so:
|
|||
.. code-block:: bash
|
||||
|
||||
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
|
||||
: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
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
My utils module
|
||||
---------------
|
||||
|
||||
This module contains common functions for use in my other custom types.
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
"""
|
||||
Load as a different name
|
||||
'''
|
||||
return 'foo'
|
||||
"""
|
||||
return "foo"
|
||||
|
||||
|
||||
def bar():
|
||||
return 'baz'
|
||||
return "baz"
|
||||
|
||||
.. versionadded:: 2018.3.0
|
||||
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
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
My OOP-style utils module
|
||||
-------------------------
|
||||
|
||||
This module contains common functions for use in my other custom types.
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
class Foo(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def bar(self):
|
||||
return 'baz'
|
||||
return "baz"
|
||||
|
||||
And import them into other custom modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
"""
|
||||
My awesome execution module
|
||||
---------------------------
|
||||
'''
|
||||
"""
|
||||
|
||||
import mymodule
|
||||
|
||||
|
||||
def observe_the_awesomeness():
|
||||
'''
|
||||
"""
|
||||
Prints information from my utility module
|
||||
|
||||
CLI Example:
|
||||
|
@ -126,7 +131,7 @@ And import them into other custom modules:
|
|||
.. code-block:: bash
|
||||
|
||||
salt '*' mymodule.observe_the_awesomeness
|
||||
'''
|
||||
"""
|
||||
foo = mymodule.Foo()
|
||||
return foo.bar()
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ In Python, the above maps to:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'my_key': 'my_value'}
|
||||
{"my_key": "my_value"}
|
||||
|
||||
Alternatively, a value can be associated with a key through indentation.
|
||||
|
||||
|
@ -58,7 +58,7 @@ In Python, the above maps to:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
{'my_key': 'my_value'}
|
||||
{"my_key": "my_value"}
|
||||
|
||||
Dictionaries can be nested:
|
||||
|
||||
|
@ -71,11 +71,7 @@ And in 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
|
||||
------------------
|
||||
|
@ -102,7 +98,7 @@ In Python, the above maps to:
|
|||
|
||||
.. 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
|
||||
-------------
|
||||
|
|
Loading…
Add table
Reference in a new issue