Blacken docs

This commit is contained in:
Pedro Algarvio 2020-06-09 09:58:34 +01:00 committed by Daniel Wozniak
parent 807bb03fee
commit 5e72e192f9
57 changed files with 1069 additions and 1022 deletions

View file

@ -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

View file

@ -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

View file

@ -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
=================

View file

@ -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:

View file

@ -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

View 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)

View file

@ -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

View file

@ -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
------------------

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
-------------------------

View file

@ -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)

View file

@ -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'
}
]
}

View file

@ -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
================

View file

@ -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)

View file

@ -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()

View file

@ -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)},],
},
}

View file

@ -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

View file

@ -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

View file

@ -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
**************************

View file

@ -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

View file

@ -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

View file

@ -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"]},
# ...
)

View file

@ -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...",
],
},
}

View file

@ -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.

View file

@ -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:

View file

@ -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)

View file

@ -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
...

View file

@ -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)

View file

@ -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:

View file

@ -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``

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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``:

View file

@ -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

View file

@ -675,7 +675,7 @@ Additional Features
.. code-block:: python
ret1 = __salt__['salt.execute']('*', 'mod.fun')
ret1 = __salt__["salt.execute"]("*", "mod.fun")
New Modules
===========

View file

@ -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

View file

@ -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>`

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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",
],
)

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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>`_:

View file

@ -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

View file

@ -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()

View file

@ -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
-------------