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 - repo: https://github.com/psf/black
rev: stable rev: 19.10b0
hooks: hooks:
- id: black - id: black
# This tells pre-commit not to pass files to black. # This tells pre-commit not to pass files to black.
@ -717,6 +717,13 @@ repos:
tests/kitchen/.* tests/kitchen/.*
)$ )$
- repo: https://github.com/asottile/blacken-docs
rev: v1.7.0
hooks:
- id: blacken-docs
args: [--skip-errors]
files: ^doc/.*\.rst
additional_dependencies: [black==19.10b0]
- repo: https://github.com/saltstack/salt-nox-pre-commit - repo: https://github.com/saltstack/salt-nox-pre-commit
rev: master rev: master

View file

@ -183,10 +183,10 @@ custom LogRecord attributes to colorize console log output:
.. code-block:: python .. code-block:: python
'%(colorlevel)s' # log level name colorized by level "%(colorlevel)s" # log level name colorized by level
'%(colorname)s' # colorized module name "%(colorname)s" # colorized module name
'%(colorprocess)s' # colorized process number "%(colorprocess)s" # colorized process number
'%(colormsg)s' # log message colorized by level "%(colormsg)s" # log message colorized by level
.. note:: .. note::
The ``%(colorlevel)s``, ``%(colorname)s``, and ``%(colorprocess)`` The ``%(colorlevel)s``, ``%(colorname)s``, and ``%(colorprocess)``
@ -212,9 +212,9 @@ these custom LogRecord attributes that include padding and enclosing brackets
.. code-block:: python .. code-block:: python
'%(bracketlevel)s' # equivalent to [%(levelname)-8s] "%(bracketlevel)s" # equivalent to [%(levelname)-8s]
'%(bracketname)s' # equivalent to [%(name)-17s] "%(bracketname)s" # equivalent to [%(name)-17s]
'%(bracketprocess)s' # equivalent to [%(process)5s] "%(bracketprocess)s" # equivalent to [%(process)5s]
.. code-block:: yaml .. code-block:: yaml

View file

@ -876,10 +876,11 @@ For example, with these custom grains functions:
.. code-block:: python .. code-block:: python
def custom1_k1(): def custom1_k1():
return {'custom1': {'k1': 'v1'}} return {"custom1": {"k1": "v1"}}
def custom1_k2(): def custom1_k2():
return {'custom1': {'k2': 'v2'}} return {"custom1": {"k2": "v2"}}
Without ``grains_deep_merge``, the result would be: Without ``grains_deep_merge``, the result would be:
@ -2710,8 +2711,7 @@ the ``extra_minion_data`` parameter will be
.. code-block:: python .. code-block:: python
{'opt1': 'value1', {"opt1": "value1", "opt2": {"subopt1": "value2"}}
'opt2': {'subopt1': 'value2'}}
Security Settings Security Settings
================= =================

View file

@ -59,7 +59,8 @@ the ``execute`` function with the following signature:
.. code-block:: python .. code-block:: python
def execute(opts, data, func, args, kwargs) def execute(opts, data, func, args, kwargs):
...
Where the args are: Where the args are:

View file

@ -133,13 +133,14 @@ from within a minion module the built in ``__opts__`` data can be passed:
import salt.minion import salt.minion
import salt.fileclient import salt.fileclient
def get_file(path, dest, saltenv='base'):
''' def get_file(path, dest, saltenv="base"):
"""
Used to get a single file from the Salt master Used to get a single file from the Salt master
CLI Example: CLI Example:
salt '*' cp.get_file salt://vimrc /etc/vimrc salt '*' cp.get_file salt://vimrc /etc/vimrc
''' """
# Get the fileclient object # Get the fileclient object
client = salt.fileclient.get_file_client(__opts__) client = salt.fileclient.get_file_client(__opts__)
# Call get_file # Call get_file
@ -153,12 +154,13 @@ data is not available, it needs to be generated:
import salt.fileclient import salt.fileclient
import salt.config import salt.config
def get_file(path, dest, saltenv='base'):
''' def get_file(path, dest, saltenv="base"):
"""
Used to get a single file from the Salt master Used to get a single file from the Salt master
''' """
# Get the configuration data # Get the configuration data
opts = salt.config.minion_config('/etc/salt/minion') opts = salt.config.minion_config("/etc/salt/minion")
# Get the fileclient object # Get the fileclient object
client = salt.fileclient.get_file_client(opts) client = salt.fileclient.get_file_client(opts)
# Call get_file # Call get_file

View file

@ -25,7 +25,8 @@ for a minion instance:
.. code-block:: python .. code-block:: python
import salt.config import salt.config
opts = salt.config.minion_config('/etc/salt/minion')
opts = salt.config.minion_config("/etc/salt/minion")
print(opts) print(opts)
To generate and display `opts` for a master, the process is similar: To generate and display `opts` for a master, the process is similar:
@ -33,5 +34,6 @@ To generate and display `opts` for a master, the process is similar:
.. code-block:: python .. code-block:: python
import salt.config import salt.config
opts = salt.config.master_config('/etc/salt/master')
opts = salt.config.master_config("/etc/salt/master")
print(opts) print(opts)

View file

@ -92,7 +92,7 @@ included libraries.
def is_ok(person): def is_ok(person):
''' Checks whether a person is really a lumberjack ''' """ Checks whether a person is really a lumberjack """
return sleep.all_night(person) and work.all_day(person) return sleep.all_night(person) and work.all_day(person)
Then, create the zip: Then, create the zip:
@ -154,7 +154,7 @@ dict:
.. code-block:: python .. code-block:: python
def foo(bar): def foo(bar):
return __salt__['cmd.run'](bar) return __salt__["cmd.run"](bar)
This code will call the `run` function in the :mod:`cmd <salt.modules.cmdmod>` This code will call the `run` function in the :mod:`cmd <salt.modules.cmdmod>`
module and pass the argument ``bar`` to it. module and pass the argument ``bar`` to it.
@ -229,14 +229,16 @@ as also available in the ``__opts__`` dict.
.. code-block:: python .. code-block:: python
''' """
Cheese module initialization example Cheese module initialization example
''' """
def __init__(opts): def __init__(opts):
''' """
Allow foreign imports if configured to do so Allow foreign imports if configured to do so
''' """
if opts.get('cheese.allow_foreign', False): if opts.get("cheese.allow_foreign", False):
_enable_foreign_products() _enable_foreign_products()
@ -266,9 +268,7 @@ names to Salt :ref:`outputters <all-salt.output>`.
.. code-block:: python .. code-block:: python
__outputter__ = { __outputter__ = {"run": "txt"}
'run': 'txt'
}
This will ensure that the ``txt`` outputter is used to display output from the This will ensure that the ``txt`` outputter is used to display output from the
``run`` function. ``run`` function.
@ -326,44 +326,50 @@ the case when the dependency is unavailable.
.. code-block:: python .. code-block:: python
''' """
Cheese execution (or returner/beacon/etc.) module Cheese execution (or returner/beacon/etc.) module
''' """
try: try:
import enzymes import enzymes
HAS_ENZYMES = True HAS_ENZYMES = True
except ImportError: except ImportError:
HAS_ENZYMES = False HAS_ENZYMES = False
def __virtual__(): def __virtual__():
''' """
only load cheese if enzymes are available only load cheese if enzymes are available
''' """
if HAS_ENZYMES: if HAS_ENZYMES:
return 'cheese' return "cheese"
else: else:
return False, 'The cheese execution module cannot be loaded: enzymes unavailable.' return (
False,
"The cheese execution module cannot be loaded: enzymes unavailable.",
)
def slice(): def slice():
pass pass
.. code-block:: python .. code-block:: python
''' """
Cheese state module. Note that this works in state modules because it is Cheese state module. Note that this works in state modules because it is
guaranteed that execution modules are loaded first guaranteed that execution modules are loaded first
''' """
def __virtual__(): def __virtual__():
''' """
only load cheese if enzymes are available only load cheese if enzymes are available
''' """
# predicate loading of the cheese state on the corresponding execution module # predicate loading of the cheese state on the corresponding execution module
if 'cheese.slice' in __salt__: if "cheese.slice" in __salt__:
return 'cheese' return "cheese"
else: else:
return False, 'The cheese state module cannot be loaded: enzymes unavailable.' return False, "The cheese state module cannot be loaded: enzymes unavailable."
Examples Examples
-------- --------
@ -445,15 +451,15 @@ similar to the following:
.. code-block:: python .. code-block:: python
# Define the module's virtual name # Define the module's virtual name
__virtualname__ = 'pkg' __virtualname__ = "pkg"
def __virtual__(): def __virtual__():
''' """
Confine this module to Mac OS with Homebrew. Confine this module to Mac OS with Homebrew.
''' """
if salt.utils.path.which('brew') and __grains__['os'] == 'MacOS': if salt.utils.path.which("brew") and __grains__["os"] == "MacOS":
return __virtualname__ return __virtualname__
return False return False
@ -472,12 +478,11 @@ For example:
.. code-block:: python .. code-block:: python
def __virtual__(): def __virtual__():
''' """
Only load if git exists on the system Only load if git exists on the system
''' """
if salt.utils.path.which('git') is None: if salt.utils.path.which("git") is None:
return (False, return (False, "The git execution module cannot be loaded: git unavailable.")
'The git execution module cannot be loaded: git unavailable.')
else: else:
return True return True
@ -505,13 +510,13 @@ To add documentation add a `Python docstring`_ to the function.
.. code-block:: python .. code-block:: python
def spam(eggs): def spam(eggs):
''' """
A function to make some spam with eggs! A function to make some spam with eggs!
CLI Example:: CLI Example::
salt '*' test.spam eggs salt '*' test.spam eggs
''' """
return eggs return eggs
Now when the sys.doc call is executed the docstring will be cleanly returned Now when the sys.doc call is executed the docstring will be cleanly returned
@ -560,9 +565,9 @@ logs. The following code snippet demonstrates writing log messages:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.info('Here is Some Information') log.info("Here is Some Information")
log.warning('You Should Not Do That') log.warning("You Should Not Do That")
log.error('It Is Busted') log.error("It Is Busted")
Aliasing Functions Aliasing Functions
================== ==================
@ -580,8 +585,8 @@ module, or from the cli, the alias name should be used.
.. code-block:: python .. code-block:: python
__func_alias__ = { __func_alias__ = {
'set_': 'set', "set_": "set",
'list_': 'list', "list_": "list",
} }
Private Functions Private Functions
@ -604,10 +609,11 @@ Objects NOT Loaded into the Salt Minion
.. code-block:: python .. code-block:: python
def _foobar(baz): # Preceded with an _ def _foobar(baz): # Preceded with an _
return baz return baz
cheese = {} # Not a callable Python object
cheese = {} # Not a callable Python object
Useful Decorators for Modules Useful Decorators for Modules
============================= =============================
@ -640,29 +646,32 @@ removing it
try: try:
import dependency_that_sometimes_exists import dependency_that_sometimes_exists
except ImportError as e: except ImportError as e:
log.trace('Failed to import dependency_that_sometimes_exists: {0}'.format(e)) log.trace("Failed to import dependency_that_sometimes_exists: {0}".format(e))
@depends('dependency_that_sometimes_exists')
@depends("dependency_that_sometimes_exists")
def foo(): def foo():
''' """
Function with a dependency on the "dependency_that_sometimes_exists" module, Function with a dependency on the "dependency_that_sometimes_exists" module,
if the "dependency_that_sometimes_exists" is missing this function will not exist if the "dependency_that_sometimes_exists" is missing this function will not exist
''' """
return True return True
def _fallback(): def _fallback():
''' """
Fallback function for the depends decorator to replace a function with Fallback function for the depends decorator to replace a function with
''' """
return '"dependency_that_sometimes_exists" needs to be installed for this function to exist' return '"dependency_that_sometimes_exists" needs to be installed for this function to exist'
@depends('dependency_that_sometimes_exists', fallback_function=_fallback)
@depends("dependency_that_sometimes_exists", fallback_function=_fallback)
def foo(): def foo():
''' """
Function with a dependency on the "dependency_that_sometimes_exists" module. Function with a dependency on the "dependency_that_sometimes_exists" module.
If the "dependency_that_sometimes_exists" is missing this function will be If the "dependency_that_sometimes_exists" is missing this function will be
replaced with "_fallback" replaced with "_fallback"
''' """
return True return True
In addition to global dependencies the depends decorator also supports raw In addition to global dependencies the depends decorator also supports raw
@ -675,10 +684,12 @@ booleans.
HAS_DEP = False HAS_DEP = False
try: try:
import dependency_that_sometimes_exists import dependency_that_sometimes_exists
HAS_DEP = True HAS_DEP = True
except ImportError: except ImportError:
pass pass
@depends(HAS_DEP) @depends(HAS_DEP)
def foo(): def foo():
return True return True

View file

@ -40,7 +40,7 @@ In Python, the above maps to:
.. code-block:: python .. code-block:: python
{'my_key': 'my_value'} {"my_key": "my_value"}
Dictionaries can be nested: Dictionaries can be nested:
@ -53,7 +53,7 @@ And in Python:
.. code-block:: python .. code-block:: python
{'first_level_dict_key': {'second_level_dict_key': 'value_in_second_level_dict' } } {"first_level_dict_key": {"second_level_dict_key": "value_in_second_level_dict"}}
Rule Three: Dashes Rule Three: Dashes
------------------ ------------------

View file

@ -73,17 +73,14 @@ to install a package:
#!py #!py
def run(): def run():
''' """
Install version 1.5-1.el7 of package "python-foo" Install version 1.5-1.el7 of package "python-foo"
''' """
return { return {
'include': ['python'], "include": ["python"],
'python-foo': { "python-foo": {"pkg.installed": [{"version": "1.5-1.el7"},]},
'pkg.installed': [
{'version': '1.5-1.el7'},
]
}
} }
This would be equivalent to the following: This would be equivalent to the following:
@ -185,7 +182,8 @@ strings or file-like objects as input. For example:
import mycoolmodule import mycoolmodule
from salt.ext import six from salt.ext import six
def render(data, saltenv='base', sls='', **kwargs):
def render(data, saltenv="base", sls="", **kwargs):
if not isinstance(data, six.string_types): if not isinstance(data, six.string_types):
# Read from file-like object # Read from file-like object
data = data.read() data = data.read()
@ -227,7 +225,8 @@ Here is a simple YAML renderer example:
from salt.utils.yamlloader import SaltYamlSafeLoader from salt.utils.yamlloader import SaltYamlSafeLoader
from salt.ext import six from salt.ext import six
def render(yaml_data, saltenv='', sls='', **kws):
def render(yaml_data, saltenv="", sls="", **kws):
if not isinstance(yaml_data, six.string_types): if not isinstance(yaml_data, six.string_types):
yaml_data = yaml_data.read() yaml_data = yaml_data.read()
data = salt.utils.yaml.safe_load(yaml_data) data = salt.utils.yaml.safe_load(yaml_data)

View file

@ -74,19 +74,17 @@ Other optional functions can be included to add support for
import redis import redis
import salt.utils.json import salt.utils.json
def returner(ret): def returner(ret):
''' """
Return information to a redis server Return information to a redis server
''' """
# Get a redis connection # Get a redis connection
serv = redis.Redis( serv = redis.Redis(host="redis-serv.example.com", port=6379, db="0")
host='redis-serv.example.com', serv.sadd("%(id)s:jobs" % ret, ret["jid"])
port=6379, serv.set("%(jid)s:%(id)s" % ret, salt.utils.json.dumps(ret["return"]))
db='0') serv.sadd("jobs", ret["jid"])
serv.sadd("%(id)s:jobs" % ret, ret['jid']) serv.sadd(ret["jid"], ret["id"])
serv.set("%(jid)s:%(id)s" % ret, salt.utils.json.dumps(ret['return']))
serv.sadd('jobs', ret['jid'])
serv.sadd(ret['jid'], ret['id'])
The above example of a returner set to send the data to a Redis server The above example of a returner set to send the data to a Redis server
serializes the data as JSON and sets it in redis. serializes the data as JSON and sets it in redis.
@ -120,11 +118,13 @@ loaded as simply ``redis``:
try: try:
import redis import redis
HAS_REDIS = True HAS_REDIS = True
except ImportError: except ImportError:
HAS_REDIS = False HAS_REDIS = False
__virtualname__ = 'redis' __virtualname__ = "redis"
def __virtual__(): def __virtual__():
if not HAS_REDIS: if not HAS_REDIS:
@ -154,9 +154,9 @@ must implement the following functions:
.. code-block:: python .. code-block:: python
def prep_jid(nocache, passed_jid=None): # pylint: disable=unused-argument def prep_jid(nocache, passed_jid=None): # pylint: disable=unused-argument
''' """
Do any work necessary to prepare a JID, including sending a custom id Do any work necessary to prepare a JID, including sending a custom id
''' """
return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid() return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid()
``save_load`` ``save_load``
@ -171,26 +171,27 @@ must implement the following functions:
import salt.utils.json import salt.utils.json
def save_load(jid, load, minions=None): def save_load(jid, load, minions=None):
''' """
Save the load to the specified jid id Save the load to the specified jid id
''' """
query = '''INSERT INTO salt.jids ( query = """INSERT INTO salt.jids (
jid, load jid, load
) VALUES ( ) VALUES (
'{0}', '{1}' '{0}', '{1}'
);'''.format(jid, salt.utils.json.dumps(load)) );""".format(
jid, salt.utils.json.dumps(load)
)
# cassandra_cql.cql_query may raise a CommandExecutionError # cassandra_cql.cql_query may raise a CommandExecutionError
try: try:
__salt__['cassandra_cql.cql_query'](query) __salt__["cassandra_cql.cql_query"](query)
except CommandExecutionError: except CommandExecutionError:
log.critical('Could not save load in jids table.') log.critical("Could not save load in jids table.")
raise raise
except Exception as e: except Exception as e:
log.critical( log.critical("Unexpected error while inserting into jids: {0}".format(e))
'Unexpected error while inserting into jids: {0}'.format(e)
)
raise raise
@ -201,26 +202,30 @@ must implement the following functions:
.. code-block:: python .. code-block:: python
def get_load(jid): def get_load(jid):
''' """
Return the load data that marks a specified jid Return the load data that marks a specified jid
''' """
query = '''SELECT load FROM salt.jids WHERE jid = '{0}';'''.format(jid) query = """SELECT load FROM salt.jids WHERE jid = '{0}';""".format(jid)
ret = {} ret = {}
# cassandra_cql.cql_query may raise a CommandExecutionError # cassandra_cql.cql_query may raise a CommandExecutionError
try: try:
data = __salt__['cassandra_cql.cql_query'](query) data = __salt__["cassandra_cql.cql_query"](query)
if data: if data:
load = data[0].get('load') load = data[0].get("load")
if load: if load:
ret = json.loads(load) ret = json.loads(load)
except CommandExecutionError: except CommandExecutionError:
log.critical('Could not get load from jids table.') log.critical("Could not get load from jids table.")
raise raise
except Exception as e: except Exception as e:
log.critical('''Unexpected error while getting load from log.critical(
jids: {0}'''.format(str(e))) """Unexpected error while getting load from
jids: {0}""".format(
str(e)
)
)
raise raise
return ret return ret
@ -322,20 +327,21 @@ contains the jid and therefore is guaranteed to be unique.
import salt.utils.json import salt.utils.json
def event_return(events):
'''
Return event to mysql server
Requires that configuration be enabled via 'event_return' def event_return(events):
option in master config. """
''' Return event to mysql server
with _get_serv(events, commit=True) as cur:
for event in events: Requires that configuration be enabled via 'event_return'
tag = event.get('tag', '') option in master config.
data = event.get('data', '') """
sql = '''INSERT INTO `salt_events` (`tag`, `data`, `master_id` ) with _get_serv(events, commit=True) as cur:
VALUES (%s, %s, %s)''' for event in events:
cur.execute(sql, (tag, salt.utils.json.dumps(data), __opts__['id'])) tag = event.get("tag", "")
data = event.get("data", "")
sql = """INSERT INTO `salt_events` (`tag`, `data`, `master_id` )
VALUES (%s, %s, %s)"""
cur.execute(sql, (tag, salt.utils.json.dumps(data), __opts__["id"]))
Testing the Returner Testing the Returner

View file

@ -36,7 +36,7 @@ fired onto the master event bus where. For example:
.. code-block:: python .. code-block:: python
def a_runner(outputter=None, display_progress=False): def a_runner(outputter=None, display_progress=False):
print('Hello world') print("Hello world")
... ...
The above would result in an event fired as follows: The above would result in an event fired as follows:
@ -62,7 +62,7 @@ A custom runner may send its own progress event by using the
.. code-block:: python .. code-block:: python
if display_progress: if display_progress:
__jid_event__.fire_event({'message': 'A progress message'}, 'progress') __jid_event__.fire_event({"message": "A progress message"}, "progress")
The above would produce output on the console reading: ``A progress message`` The above would produce output on the console reading: ``A progress message``
as well as an event on the event similar to: as well as an event on the event similar to:
@ -117,11 +117,12 @@ responding to Salt calls could look like this:
# Import salt modules # Import salt modules
import salt.client import salt.client
def up(): def up():
''' """
Print a list of all of the minions that are up Print a list of all of the minions that are up
''' """
client = salt.client.LocalClient(__opts__['conf_file']) client = salt.client.LocalClient(__opts__["conf_file"])
minions = client.cmd('*', 'test.version', timeout=1) minions = client.cmd("*", "test.version", timeout=1)
for minion in sorted(minions): for minion in sorted(minions):
print minion print minion

View file

@ -102,48 +102,48 @@ This example, simplified from the pkg state, shows how to create mod_aggregate f
.. code-block:: python .. code-block:: python
def mod_aggregate(low, chunks, running): def mod_aggregate(low, chunks, running):
''' """
The mod_aggregate function which looks up all packages in the available The mod_aggregate function which looks up all packages in the available
low chunks and merges them into a single pkgs ref in the present low data low chunks and merges them into a single pkgs ref in the present low data
''' """
pkgs = [] pkgs = []
# What functions should we aggregate? # What functions should we aggregate?
agg_enabled = [ agg_enabled = [
'installed', "installed",
'latest', "latest",
'removed', "removed",
'purged', "purged",
] ]
# The `low` data is just a dict with the state, function (fun) and # The `low` data is just a dict with the state, function (fun) and
# arguments passed in from the sls # arguments passed in from the sls
if low.get('fun') not in agg_enabled: if low.get("fun") not in agg_enabled:
return low return low
# Now look into what other things are set to execute # Now look into what other things are set to execute
for chunk in chunks: for chunk in chunks:
# The state runtime uses "tags" to track completed jobs, it may # The state runtime uses "tags" to track completed jobs, it may
# look familiar with the _|- # look familiar with the _|-
tag = __utils__['state.gen_tag'](chunk) tag = __utils__["state.gen_tag"](chunk)
if tag in running: if tag in running:
# Already ran the pkg state, skip aggregation # Already ran the pkg state, skip aggregation
continue continue
if chunk.get('state') == 'pkg': if chunk.get("state") == "pkg":
if '__agg__' in chunk: if "__agg__" in chunk:
continue continue
# Check for the same function # Check for the same function
if chunk.get('fun') != low.get('fun'): if chunk.get("fun") != low.get("fun"):
continue continue
# Pull out the pkg names! # Pull out the pkg names!
if 'pkgs' in chunk: if "pkgs" in chunk:
pkgs.extend(chunk['pkgs']) pkgs.extend(chunk["pkgs"])
chunk['__agg__'] = True chunk["__agg__"] = True
elif 'name' in chunk: elif "name" in chunk:
pkgs.append(chunk['name']) pkgs.append(chunk["name"])
chunk['__agg__'] = True chunk["__agg__"] = True
if pkgs: if pkgs:
if 'pkgs' in low: if "pkgs" in low:
low['pkgs'].extend(pkgs) low["pkgs"].extend(pkgs)
else: else:
low['pkgs'] = pkgs low["pkgs"] = pkgs
# The low has been modified and needs to be returned to the state # The low has been modified and needs to be returned to the state
# runtime for execution # runtime for execution
return low return low

View file

@ -66,13 +66,10 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
ret = {'name': name, ret = {"name": name, "result": False, "changes": {}, "comment": ""}
'result': False,
'changes': {},
'comment': ''}
if foo and bar: if foo and bar:
ret['comment'] = 'Only one of foo and bar is permitted' ret["comment"] = "Only one of foo and bar is permitted"
return ret return ret
2. Check if changes need to be made. This is best done with an 2. Check if changes need to be made. This is best done with an
@ -83,7 +80,7 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
result = __salt__['modname.check'](name) result = __salt__["modname.check"](name)
3. If step 2 found that the minion is already in the desired state, then exit 3. If step 2 found that the minion is already in the desired state, then exit
immediately with a ``True`` result and without making any changes. immediately with a ``True`` result and without making any changes.
@ -91,8 +88,8 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
if result: if result:
ret['result'] = True ret["result"] = True
ret['comment'] = '{0} is already installed'.format(name) ret["comment"] = "{0} is already installed".format(name)
return ret return ret
4. If step 2 found that changes *do* need to be made, then check to see if the 4. If step 2 found that changes *do* need to be made, then check to see if the
@ -102,10 +99,10 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
if __opts__['test']: if __opts__["test"]:
ret['result'] = None ret["result"] = None
ret['comment'] = '{0} would be installed'.format(name) ret["comment"] = "{0} would be installed".format(name)
ret['changes'] = result ret["changes"] = result
return ret return ret
5. Make the desired changes. This should again be done using a function from an 5. Make the desired changes. This should again be done using a function from an
@ -115,7 +112,7 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
result = __salt__['modname.install'](name) result = __salt__["modname.install"](name)
6. Perform the same check from step 2 again to confirm whether or not the 6. Perform the same check from step 2 again to confirm whether or not the
minion is in the desired state. Just as in step 2, this function should be minion is in the desired state. Just as in step 2, this function should be
@ -123,7 +120,7 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
ret['changes'] = __salt__['modname.check'](name) ret["changes"] = __salt__["modname.check"](name)
As you can see here, we are setting the ``changes`` key in the return As you can see here, we are setting the ``changes`` key in the return
dictionary to the result of the ``modname.check`` function (just as we did dictionary to the result of the ``modname.check`` function (just as we did
@ -135,11 +132,11 @@ A well-written state function will follow these steps:
.. code-block:: python .. code-block:: python
if ret['changes']: if ret["changes"]:
ret['comment'] = '{0} failed to install'.format(name) ret["comment"] = "{0} failed to install".format(name)
else: else:
ret['result'] = True ret["result"] = True
ret['comment'] = '{0} was installed'.format(name) ret["comment"] = "{0} was installed".format(name)
return ret return ret
@ -202,7 +199,7 @@ Salt state modules can be cross-called by accessing the value in the
.. code-block:: python .. code-block:: python
ret = __states__['file.managed'](name='/tmp/myfile', source='salt://myfile') ret = __states__["file.managed"](name="/tmp/myfile", source="salt://myfile")
This code will call the `managed` function in the :mod:`file This code will call the `managed` function in the :mod:`file
<salt.states.file>` state module and pass the arguments ``name`` and ``source`` <salt.states.file>` state module and pass the arguments ``name`` and ``source``
@ -225,8 +222,7 @@ A State Module must return a dict containing the following keys/values:
.. code-block:: python .. code-block:: python
ret['changes'].update({'my_pkg_name': {'old': '', ret["changes"].update({"my_pkg_name": {"old": "", "new": "my_pkg_name-1.0"}})
'new': 'my_pkg_name-1.0'}})
- **result:** A tristate value. ``True`` if the action was successful, - **result:** A tristate value. ``True`` if the action was successful,
@ -273,9 +269,9 @@ run. An example of such a check could look like this:
.. code-block:: python .. code-block:: python
# Return comment of changes if test. # Return comment of changes if test.
if __opts__['test']: if __opts__["test"]:
ret['result'] = None ret["result"] = None
ret['comment'] = 'State Foo will execute with param {0}'.format(bar) ret["comment"] = "State Foo will execute with param {0}".format(bar)
return ret return ret
Make sure to test and return before performing any real actions on the minion. Make sure to test and return before performing any real actions on the minion.
@ -337,13 +333,13 @@ A good example of the mod_init function is found in the pkg state module:
.. code-block:: python .. code-block:: python
def mod_init(low): def mod_init(low):
''' """
Refresh the package database here so that it only needs to happen once Refresh the package database here so that it only needs to happen once
''' """
if low['fun'] == 'installed' or low['fun'] == 'latest': if low["fun"] == "installed" or low["fun"] == "latest":
rtag = __gen_rtag() rtag = __gen_rtag()
if not os.path.exists(rtag): if not os.path.exists(rtag):
open(rtag, 'w+').write('') open(rtag, "w+").write("")
return True return True
else: else:
return False return False
@ -368,9 +364,9 @@ logs. The following code snippet demonstrates writing log messages:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.info('Here is Some Information') log.info("Here is Some Information")
log.warning('You Should Not Do That') log.warning("You Should Not Do That")
log.error('It Is Busted') log.error("It Is Busted")
Strings and Unicode Strings and Unicode
@ -424,8 +420,9 @@ Example state module
import salt.exceptions import salt.exceptions
def enforce_custom_thing(name, foo, bar=True): def enforce_custom_thing(name, foo, bar=True):
''' """
Enforce the state of a custom thing Enforce the state of a custom thing
This state module does a custom thing. It calls out to the execution module This state module does a custom thing. It calls out to the execution module
@ -438,52 +435,53 @@ Example state module
A required argument A required argument
bar : True bar : True
An argument with a default value An argument with a default value
''' """
ret = { ret = {
'name': name, "name": name,
'changes': {}, "changes": {},
'result': False, "result": False,
'comment': '', "comment": "",
} }
# Start with basic error-checking. Do all the passed parameters make sense # Start with basic error-checking. Do all the passed parameters make sense
# and agree with each-other? # and agree with each-other?
if bar == True and foo.startswith('Foo'): if bar == True and foo.startswith("Foo"):
raise salt.exceptions.SaltInvocationError( raise salt.exceptions.SaltInvocationError(
'Argument "foo" cannot start with "Foo" if argument "bar" is True.') 'Argument "foo" cannot start with "Foo" if argument "bar" is True.'
)
# Check the current state of the system. Does anything need to change? # Check the current state of the system. Does anything need to change?
current_state = __salt__['my_custom_module.current_state'](name) current_state = __salt__["my_custom_module.current_state"](name)
if current_state == foo: if current_state == foo:
ret['result'] = True ret["result"] = True
ret['comment'] = 'System already in the correct state' ret["comment"] = "System already in the correct state"
return ret return ret
# The state of the system does need to be changed. Check if we're running # The state of the system does need to be changed. Check if we're running
# in ``test=true`` mode. # in ``test=true`` mode.
if __opts__['test'] == True: if __opts__["test"] == True:
ret['comment'] = 'The state of "{0}" will be changed.'.format(name) ret["comment"] = 'The state of "{0}" will be changed.'.format(name)
ret['changes'] = { ret["changes"] = {
'old': current_state, "old": current_state,
'new': 'Description, diff, whatever of the new state', "new": "Description, diff, whatever of the new state",
} }
# Return ``None`` when running with ``test=true``. # Return ``None`` when running with ``test=true``.
ret['result'] = None ret["result"] = None
return ret return ret
# Finally, make the actual change and return the result. # Finally, make the actual change and return the result.
new_state = __salt__['my_custom_module.change_state'](name, foo) new_state = __salt__["my_custom_module.change_state"](name, foo)
ret['comment'] = 'The state of "{0}" was changed!'.format(name) ret["comment"] = 'The state of "{0}" was changed!'.format(name)
ret['changes'] = { ret["changes"] = {
'old': current_state, "old": current_state,
'new': new_state, "new": new_state,
} }
ret['result'] = True ret["result"] = True
return ret return ret

View file

@ -360,8 +360,7 @@ The return data structure would look something like this:
.. code-block:: python .. code-block:: python
[{'changes': ['/foo/bar'], 'tag': 'foo'}, [{"changes": ["/foo/bar"], "tag": "foo"}, {"changes": ["/foo/baz"], "tag": "bar"}]
{'changes': ['/foo/baz'], 'tag': 'bar'}]
Calling Execution Modules Calling Execution Modules
------------------------- -------------------------

View file

@ -145,7 +145,7 @@ library. The following two lines set up the imports:
.. code-block:: python .. code-block:: python
from salt.cloud.libcloudfuncs import * # pylint: disable=W0614,W0401 from salt.cloud.libcloudfuncs import * # pylint: disable=W0614,W0401
import salt.utils.functools import salt.utils.functools
And then a series of declarations will make the necessary functions available And then a series of declarations will make the necessary functions available
@ -162,7 +162,9 @@ within the cloud module.
destroy = salt.utils.functools.namespaced_function(destroy, globals()) destroy = salt.utils.functools.namespaced_function(destroy, globals())
list_nodes = salt.utils.functools.namespaced_function(list_nodes, globals()) list_nodes = salt.utils.functools.namespaced_function(list_nodes, globals())
list_nodes_full = salt.utils.functools.namespaced_function(list_nodes_full, globals()) list_nodes_full = salt.utils.functools.namespaced_function(list_nodes_full, globals())
list_nodes_select = salt.utils.functools.namespaced_function(list_nodes_select, globals()) list_nodes_select = salt.utils.functools.namespaced_function(
list_nodes_select, globals()
)
show_instance = salt.utils.functools.namespaced_function(show_instance, globals()) show_instance = salt.utils.functools.namespaced_function(show_instance, globals())
If necessary, these functions may be replaced by removing the appropriate If necessary, these functions may be replaced by removing the appropriate
@ -300,11 +302,11 @@ general, the following code can be used as-is:
.. code-block:: python .. code-block:: python
def list_nodes_select(call=None): def list_nodes_select(call=None):
''' """
Return a list of the VMs that are on the provider, with select fields Return a list of the VMs that are on the provider, with select fields
''' """
return salt.utils.cloud.list_nodes_select( return salt.utils.cloud.list_nodes_select(
list_nodes_full('function'), __opts__['query.selection'], call, list_nodes_full("function"), __opts__["query.selection"], call,
) )
However, depending on the cloud provider, additional variables may be required. However, depending on the cloud provider, additional variables may be required.
@ -315,16 +317,14 @@ appropriately:
.. code-block:: python .. code-block:: python
def list_nodes_select(conn=None, call=None): def list_nodes_select(conn=None, call=None):
''' """
Return a list of the VMs that are on the provider, with select fields Return a list of the VMs that are on the provider, with select fields
''' """
if not conn: if not conn:
conn = get_conn() # pylint: disable=E0602 conn = get_conn() # pylint: disable=E0602
return salt.utils.cloud.list_nodes_select( return salt.utils.cloud.list_nodes_select(
list_nodes_full(conn, 'function'), list_nodes_full(conn, "function"), __opts__["query.selection"], call,
__opts__['query.selection'],
call,
) )
This function is normally called with the ``-S`` option: This function is normally called with the ``-S`` option:
@ -372,15 +372,15 @@ useful information to the user. A basic action looks like:
.. code-block:: python .. code-block:: python
def show_instance(name, call=None): def show_instance(name, call=None):
''' """
Show the details from EC2 concerning an AMI Show the details from EC2 concerning an AMI
''' """
if call != 'action': if call != "action":
raise SaltCloudSystemExit( raise SaltCloudSystemExit(
'The show_instance action must be called with -a or --action.' "The show_instance action must be called with -a or --action."
) )
return _get_node(name) return _get_node(name)
Please note that generic kwargs, if used, are passed through to actions as Please note that generic kwargs, if used, are passed through to actions as
``kwargs`` and not ``**kwargs``. An example of this is seen in the Functions ``kwargs`` and not ``**kwargs``. An example of this is seen in the Functions
@ -406,16 +406,15 @@ useful information to the user. A basic function looks like:
.. code-block:: python .. code-block:: python
def show_image(kwargs, call=None): def show_image(kwargs, call=None):
''' """
Show the details from EC2 concerning an AMI Show the details from EC2 concerning an AMI
''' """
if call != 'function': if call != "function":
raise SaltCloudSystemExit( raise SaltCloudSystemExit(
'The show_image action must be called with -f or --function.' "The show_image action must be called with -f or --function."
) )
params = {'ImageId.1': kwargs['image'], params = {"ImageId.1": kwargs["image"], "Action": "DescribeImages"}
'Action': 'DescribeImages'}
result = query(params) result = query(params)
log.info(result) log.info(result)

View file

@ -572,33 +572,43 @@ data:
.. code-block:: python .. code-block:: python
[{'deploy': False, [
'image': 'ami-08d97e61', {
'profile': 'Fedora-17', "deploy": False,
'provider': 'my-ec2-config', "image": "ami-08d97e61",
'securitygroup': ['default'], "profile": "Fedora-17",
'size': 't1.micro', "provider": "my-ec2-config",
'ssh_username': 'ec2_user'}, "securitygroup": ["default"],
{'deploy': False, "size": "t1.micro",
'image': 'ami-09b61d60', "ssh_username": "ec2_user",
'profile': 'CentOS-5', },
'provider': 'my-aws-config', {
'securitygroup': ['default'], "deploy": False,
'size': 't1.micro', "image": "ami-09b61d60",
'ssh_username': 'ec2_user'}, "profile": "CentOS-5",
{'deploy': False, "provider": "my-aws-config",
'image': 'ami-54cf5c3d', "securitygroup": ["default"],
'profile': 'Amazon-Linux-AMI-2012.09-64bit', "size": "t1.micro",
'provider': 'my-ec2-config', "ssh_username": "ec2_user",
'securitygroup': ['default'], },
'size': 't1.micro', {
'ssh_username': 'ec2_user'}, "deploy": False,
{'deploy': False, "image": "ami-54cf5c3d",
'profile': 'development-instances', "profile": "Amazon-Linux-AMI-2012.09-64bit",
'provider': 'my-ec2-config', "provider": "my-ec2-config",
'securitygroup': ['default'], "securitygroup": ["default"],
'size': 't1.micro', "size": "t1.micro",
'ssh_username': 'ec2_user'}] "ssh_username": "ec2_user",
},
{
"deploy": False,
"profile": "development-instances",
"provider": "my-ec2-config",
"securitygroup": ["default"],
"size": "t1.micro",
"ssh_username": "ec2_user",
},
]
Pretty cool right? Pretty cool right?
@ -642,33 +652,36 @@ data:
.. code-block:: python .. code-block:: python
'providers': { "providers": {
'my-develop-envs': [ "my-develop-envs": [
{'availability_zone': 'ap-southeast-1b', {
'id': 'HJGRYCILJLKJYG', "availability_zone": "ap-southeast-1b",
'key': 'kdjgfsgm;woormgl/aserigjksjdhasdfgn', "id": "HJGRYCILJLKJYG",
'keyname': 'test', "key": "kdjgfsgm;woormgl/aserigjksjdhasdfgn",
'location': 'ap-southeast-1', "keyname": "test",
'private_key': '/root/test.pem', "location": "ap-southeast-1",
'driver': 'aws', "private_key": "/root/test.pem",
'securitygroup': 'quick-start' "driver": "aws",
"securitygroup": "quick-start",
}, },
{'location': 'Raleigh', {
'password': 'mypass', "location": "Raleigh",
'driver': 'ibmsce', "password": "mypass",
'ssh_key_file': '/etc/salt/ibm/mykey.pem', "driver": "ibmsce",
'ssh_key_name': 'mykey', "ssh_key_file": "/etc/salt/ibm/mykey.pem",
'user': 'myuser@mycorp.com' "ssh_key_name": "mykey",
"user": "myuser@mycorp.com",
},
],
"my-productions-envs": [
{
"availability_zone": "us-east-1",
"location": "us-east-1",
"password": "mypass",
"driver": "ibmsce",
"ssh_key_file": "/etc/salt/ibm/mykey.pem",
"ssh_key_name": "mykey",
"user": "my-production-user@mycorp.com",
} }
], ],
'my-productions-envs': [
{'availability_zone': 'us-east-1',
'location': 'us-east-1',
'password': 'mypass',
'driver': 'ibmsce',
'ssh_key_file': '/etc/salt/ibm/mykey.pem',
'ssh_key_name': 'mykey',
'user': 'my-production-user@mycorp.com'
}
]
} }

View file

@ -32,9 +32,7 @@ using the ``ec2-config`` provider, the payload for this tag would look like:
.. code-block:: python .. code-block:: python
{'name': 'web1', {"name": "web1", "profile": "ec2-centos", "provider": "ec2-config:ec2"}
'profile': 'ec2-centos',
'provider': 'ec2-config:ec2'}
Available Events Available Events
================ ================

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 salt.cloud
import pprint import pprint
client = salt.cloud.CloudClient('/etc/salt/cloud')
client = salt.cloud.CloudClient("/etc/salt/cloud")
nodes = client.query() nodes = client.query()
pprint.pprint(nodes) pprint.pprint(nodes)

View file

@ -97,7 +97,7 @@ code and can contain special formatting. For example:
.. code-block:: python .. code-block:: python
def my_function(value): def my_function(value):
''' """
Upper-case the given value Upper-case the given value
Usage: Usage:
@ -110,7 +110,7 @@ code and can contain special formatting. For example:
:param value: a string :param value: a string
:return: a copy of ``value`` that has been upper-cased :return: a copy of ``value`` that has been upper-cased
''' """
return value.upper() return value.upper()
Specify a release for additions or changes Specify a release for additions or changes
@ -122,13 +122,13 @@ denotes what Salt release will be affected. For example:
.. code-block:: python .. code-block:: python
def my_function(value): def my_function(value):
''' """
Upper-case the given value Upper-case the given value
.. versionadded:: 2014.7.0 .. versionadded:: 2014.7.0
<...snip...> <...snip...>
''' """
return value.upper() return value.upper()
For changes to a function: For changes to a function:
@ -136,14 +136,14 @@ For changes to a function:
.. code-block:: python .. code-block:: python
def my_function(value, strip=False): def my_function(value, strip=False):
''' """
Upper-case the given value Upper-case the given value
.. versionchanged:: 2016.3.0 .. versionchanged:: 2016.3.0
Added a flag to also strip whitespace from the string. Added a flag to also strip whitespace from the string.
<...snip...> <...snip...>
''' """
if strip: if strip:
return value.upper().strip() return value.upper().strip()
return value.upper() return value.upper()

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'. # This example has the minion id in the form 'web-03-dev'.
# Easily access the grains dictionary: # Easily access the grains dictionary:
try: try:
app, instance_number, environment = __grains__['id'].split('-') app, instance_number, environment = __grains__["id"].split("-")
instance_number = int(instance_number) instance_number = int(instance_number)
except ValueError: except ValueError:
app, instance_number, environment = ['Unknown', 0, 'dev'] app, instance_number, environment = ["Unknown", 0, "dev"]
list_of_roles.add(app) list_of_roles.add(app)
if app == 'web' and environment == 'dev': if app == "web" and environment == "dev":
list_of_roles.add('primary') list_of_roles.add("primary")
list_of_roles.add('secondary') list_of_roles.add("secondary")
elif app == 'web' and environment == 'staging': elif app == "web" and environment == "staging":
if instance_number == 0: if instance_number == 0:
list_of_roles.add('primary') list_of_roles.add("primary")
else: else:
list_of_roles.add('secondary') list_of_roles.add("secondary")
# Easily cross-call Salt execution modules: # Easily cross-call Salt execution modules:
if __salt__['myutils.query_valid_ec2_instance'](): if __salt__["myutils.query_valid_ec2_instance"]():
list_of_roles.add('is_ec2_instance') list_of_roles.add("is_ec2_instance")
return { return {
'set_roles_grains': { "set_roles_grains": {
'grains.present': [ "grains.present": [{"name": "roles"}, {"value": list(list_of_roles)},],
{'name': 'roles'},
{'value': list(list_of_roles)},
],
}, },
} }

View file

@ -68,8 +68,8 @@ All strings which require formatting should use the `.format` string method:
.. code-block:: python .. code-block:: python
data = 'some text' data = "some text"
more = '{0} and then some'.format(data) more = "{0} and then some".format(data)
Make sure to use indices or identifiers in the format brackets, since empty Make sure to use indices or identifiers in the format brackets, since empty
brackets are not supported by python 2.6. brackets are not supported by python 2.6.
@ -84,15 +84,15 @@ When adding a new function or state, where possible try to use a
.. code-block:: python .. code-block:: python
def new_func(msg=''): def new_func(msg=""):
''' """
.. versionadded:: 0.16.0 .. versionadded:: 0.16.0
Prints what was passed to the function. Prints what was passed to the function.
msg : None msg : None
The string to be printed. The string to be printed.
''' """
print(msg) print(msg)
If you are uncertain what version should be used, either consult a core If you are uncertain what version should be used, either consult a core
@ -109,8 +109,8 @@ significantly, the ``versionchanged`` directive can be used to clarify this:
.. code-block:: python .. code-block:: python
def new_func(msg='', signature=''): def new_func(msg="", signature=""):
''' """
.. versionadded:: 0.16.0 .. versionadded:: 0.16.0
Prints what was passed to the function. Prints what was passed to the function.
@ -124,8 +124,8 @@ significantly, the ``versionchanged`` directive can be used to clarify this:
An optional signature. An optional signature.
.. versionadded 0.17.0 .. versionadded 0.17.0
''' """
print('Greetings! {0}\n\n{1}'.format(msg, signature)) print("Greetings! {0}\n\n{1}".format(msg, signature))
Dictionaries Dictionaries
@ -154,8 +154,9 @@ To say this more directly with an example, this is `GOOD`:
import os import os
def minion_path(): def minion_path():
path = os.path.join(self.opts['cachedir'], 'minions') path = os.path.join(self.opts["cachedir"], "minions")
return path return path
This on the other hand is `DISCOURAGED`: This on the other hand is `DISCOURAGED`:
@ -164,8 +165,9 @@ This on the other hand is `DISCOURAGED`:
from os.path import join from os.path import join
def minion_path(): def minion_path():
path = join(self.opts['cachedir'], 'minions') path = join(self.opts["cachedir"], "minions")
return path return path
The time when this is changed is for importing exceptions, generally directly The time when this is changed is for importing exceptions, generally directly

View file

@ -45,10 +45,10 @@ Consider the following example:
def some_function(bar=False, foo=None): def some_function(bar=False, foo=None):
if foo is not None: if foo is not None:
salt.utils.versions.warn_until( salt.utils.versions.warn_until(
'Aluminum', "Aluminum",
'The \'foo\' argument has been deprecated and its ' "The 'foo' argument has been deprecated and its "
'functionality removed, as such, its usage is no longer ' "functionality removed, as such, its usage is no longer "
'required.' "required.",
) )
Development begins on the ``Aluminum`` release when the ``Magnesium`` branch is Development begins on the ``Aluminum`` release when the ``Magnesium`` branch is

View file

@ -79,8 +79,8 @@ Example file in the template directory
.. code-block:: python .. code-block:: python
print('Hello {{module_name}}') print("Hello {{module_name}}")
__virtual__ = '{{__virtual_name__}}' __virtual__ = "{{__virtual_name__}}"
Default context properties Default context properties
************************** **************************

View file

@ -30,8 +30,10 @@ the debugger should be started:
.. code-block:: python .. code-block:: python
test = 'test123' test = "test123"
import IPython; IPython.embed_kernel() import IPython
IPython.embed_kernel()
After running a Salt command that hits that line, the following will show up in After running a Salt command that hits that line, the following will show up in
the log file: the log file:
@ -146,8 +148,8 @@ functions to be called as they have been set up by the salt loader.
.. code-block:: python .. code-block:: python
__salt__['cmd.run']('fdisk -l') __salt__["cmd.run"]("fdisk -l")
__salt__['network.ip_addrs']() __salt__["network.ip_addrs"]()
.. note:: .. note::
@ -205,8 +207,8 @@ each file. Here is an example from salt/modules/cp.py:
.. code-block:: python .. code-block:: python
if not 'cp.fileclient' in __context__: if not "cp.fileclient" in __context__:
__context__['cp.fileclient'] = salt.fileclient.get_file_client(__opts__) __context__["cp.fileclient"] = salt.fileclient.get_file_client(__opts__)
.. note:: Because __context__ may or may not have been destroyed, always be .. note:: Because __context__ may or may not have been destroyed, always be

View file

@ -65,6 +65,7 @@ import errors and set a flag that the ``__virtual__`` function can use later.
try: try:
import weird_thing import weird_thing
EXAMPLE_A_LOADED = True EXAMPLE_A_LOADED = True
except ImportError: except ImportError:
EXAMPLE_A_LOADED = False EXAMPLE_A_LOADED = False
@ -80,7 +81,7 @@ things ``modulename.option``.
.. code-block:: python .. code-block:: python
__opts__ = { 'example_a.someconfig': 137 } __opts__ = {"example_a.someconfig": 137}
Initialization Initialization
@ -91,8 +92,9 @@ signature:
.. code-block:: python .. code-block:: python
def __init__( __opts__ ): def __init__(__opts__):
# Do init work here # Do init work here
...
**Note**: The ``__init__`` function is ran every time a particular minion causes **Note**: The ``__init__`` function is ran every time a particular minion causes
@ -127,7 +129,8 @@ installed.
.. code-block:: python .. code-block:: python
# This external pillar will be known as `something_else` # This external pillar will be known as `something_else`
__virtualname__ = 'something_else' __virtualname__ = "something_else"
def __virtual__(): def __virtual__():
if EXAMPLE_A_LOADED: if EXAMPLE_A_LOADED:
@ -151,9 +154,9 @@ Using our example above:
.. code-block:: python .. code-block:: python
ext_pillar( id, pillar, 'some argument' ) # example_a ext_pillar(id, pillar, "some argument") # example_a
ext_pillar( id, pillar, 'argumentA', 'argumentB' ) # example_b ext_pillar(id, pillar, "argumentA", "argumentB") # example_b
ext_pillar( id, pillar, keyA='valueA', keyB='valueB' ) # example_c ext_pillar(id, pillar, keyA="valueA", keyB="valueB") # example_c
In the ``example_a`` case, ``pillar`` will contain the items from the In the ``example_a`` case, ``pillar`` will contain the items from the
@ -168,11 +171,11 @@ is called once for each minion that fetches its pillar data.
.. code-block:: python .. code-block:: python
def ext_pillar( minion_id, pillar, *args, **kwargs ): def ext_pillar(minion_id, pillar, *args, **kwargs):
my_pillar = {'external_pillar': {}} my_pillar = {"external_pillar": {}}
my_pillar['external_pillar'] = get_external_pillar_dictionary() my_pillar["external_pillar"] = get_external_pillar_dictionary()
return my_pillar return my_pillar

View file

@ -101,11 +101,7 @@ This is done via setuptools entry points:
setup( setup(
# ... # ...
entry_points={ entry_points={"salt.loader": ["module_dirs=spirofs.loader:module"]},
'salt.loader': [
'module_dirs=spirofs.loader:module',
],
},
# ... # ...
) )

View file

@ -23,7 +23,7 @@ so:
.. code-block:: python .. code-block:: python
__salt__['pkg_resource.add_pkg'](ret, name, version) __salt__["pkg_resource.add_pkg"](ret, name, version)
The last thing that should be done before returning is to execute The last thing that should be done before returning is to execute
:mod:`pkg_resource.sort_pkglist <salt.modules.pkg_resource.sort_pkglist>`. This :mod:`pkg_resource.sort_pkglist <salt.modules.pkg_resource.sort_pkglist>`. This
@ -32,7 +32,7 @@ future versions of Salt.
.. code-block:: python .. code-block:: python
__salt__['pkg_resource.sort_pkglist'](ret) __salt__["pkg_resource.sort_pkglist"](ret)
``list_pkgs`` returns a dictionary of installed packages, with the keys being ``list_pkgs`` returns a dictionary of installed packages, with the keys being
@ -41,8 +41,7 @@ data:
.. code-block:: python .. code-block:: python
{'foo': '1.2.3-4', {"foo": "1.2.3-4", "bar": "5.6.7-8"}
'bar': '5.6.7-8'}
latest_version latest_version
@ -83,7 +82,7 @@ Deprecated and destined to be removed. For now, should just do the following:
.. code-block:: python .. code-block:: python
return __salt__['pkg.latest_version'](name) != '' return __salt__["pkg.latest_version"](name) != ""
install install
@ -106,9 +105,7 @@ system.
.. code-block:: python .. code-block:: python
pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](name, pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](name, pkgs, sources)
pkgs,
sources)
Two values will be returned to the :strong:`install` function. The first of Two values will be returned to the :strong:`install` function. The first of
them will be a dictionary. The keys of this dictionary will be package names, them will be a dictionary. The keys of this dictionary will be package names,
@ -164,8 +161,8 @@ changed.
.. code-block:: python .. code-block:: python
basedir = '/etc/yum.repos.d' basedir = "/etc/yum.repos.d"
__salt__['pkg.list_repos'](basedir) __salt__["pkg.list_repos"](basedir)
list_repos list_repos
^^^^^^^^^^ ^^^^^^^^^^
@ -173,7 +170,7 @@ Lists the repositories that are currently configured on this system.
.. code-block:: python .. code-block:: python
__salt__['pkg.list_repos']() __salt__["pkg.list_repos"]()
Returns a dictionary, in the following format: Returns a dictionary, in the following format:
@ -190,7 +187,7 @@ Displays all local configuration for a specific repository.
.. code-block:: python .. code-block:: python
__salt__['pkg.get_repo'](repo='myrepo') __salt__["pkg.get_repo"](repo="myrepo")
The information is formatted in much the same way as list_repos, but is The information is formatted in much the same way as list_repos, but is
specific to only one repo. specific to only one repo.
@ -211,7 +208,7 @@ success.
.. code-block:: python .. code-block:: python
__salt__['pkg.del_repo'](repo='myrepo') __salt__["pkg.del_repo"](repo="myrepo")
mod_repo mod_repo
^^^^^^^^ ^^^^^^^^
@ -224,7 +221,7 @@ refer to the documentation for your specific repo manager for specifics.
.. code-block:: python .. code-block:: python
__salt__['pkg.mod_repo'](repo='myrepo', url='http://myurl.com/repo') __salt__["pkg.mod_repo"](repo="myrepo", url="http://myurl.com/repo")
Low-Package Functions Low-Package Functions
@ -251,14 +248,13 @@ packages will be listed.
.. code-block:: python .. code-block:: python
installed = __salt__['lowpkg.list_pkgs']('foo', 'bar') installed = __salt__["lowpkg.list_pkgs"]("foo", "bar")
Example output: Example output:
.. code-block:: python .. code-block:: python
{'foo': '1.2.3-4', {"foo": "1.2.3-4", "bar": "5.6.7-8"}
'bar': '5.6.7-8'}
verify verify
^^^^^^ ^^^^^^
@ -269,14 +265,18 @@ included.
.. code-block:: python .. code-block:: python
installed = __salt__['lowpkg.verify']('httpd') installed = __salt__["lowpkg.verify"]("httpd")
Example output: Example output:
.. code-block:: python .. code-block:: python
{'/etc/httpd/conf/httpd.conf': {'mismatch': ['size', 'md5sum', 'mtime'], {
'type': 'config'}} "/etc/httpd/conf/httpd.conf": {
"mismatch": ["size", "md5sum", "mtime"],
"type": "config",
}
}
file_list file_list
^^^^^^^^^ ^^^^^^^^^
@ -285,7 +285,7 @@ specified, then all files for all known packages are returned.
.. code-block:: python .. code-block:: python
installed = __salt__['lowpkg.file_list']('httpd', 'apache') installed = __salt__["lowpkg.file_list"]("httpd", "apache")
This function does not return which files belong to which packages; all files This function does not return which files belong to which packages; all files
are returned as one giant list (hence the `file_list` function name. However, are returned as one giant list (hence the `file_list` function name. However,
@ -294,11 +294,10 @@ any errors to the user in a sane manner.
.. code-block:: python .. code-block:: python
{'errors': ['package apache is not installed'], {
'files': ['/etc/httpd', "errors": ["package apache is not installed"],
'/etc/httpd/conf', "files": ["/etc/httpd", "/etc/httpd/conf", "/etc/httpd/conf.d", "...SNIP..."],
'/etc/httpd/conf.d', }
'...SNIP...']}
file_dict file_dict
^^^^^^^^^ ^^^^^^^^^
@ -307,17 +306,21 @@ specified, then all files for all known packages are returned.
.. code-block:: python .. code-block:: python
installed = __salt__['lowpkg.file_dict']('httpd', 'apache', 'kernel') installed = __salt__["lowpkg.file_dict"]("httpd", "apache", "kernel")
Unlike `file_list`, this function will break down which files belong to which Unlike `file_list`, this function will break down which files belong to which
packages. It will also return errors in the same manner as `file_list`. packages. It will also return errors in the same manner as `file_list`.
.. code-block:: python .. code-block:: python
{'errors': ['package apache is not installed'], {
'packages': {'httpd': ['/etc/httpd', "errors": ["package apache is not installed"],
'/etc/httpd/conf', "packages": {
'...SNIP...'], "httpd": ["/etc/httpd", "/etc/httpd/conf", "...SNIP..."],
'kernel': ['/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac', "kernel": [
'/boot/System.map-2.6.32-279.el6.x86_64', "/boot/.vmlinuz-2.6.32-279.el6.x86_64.hmac",
'...SNIP...']}} "/boot/System.map-2.6.32-279.el6.x86_64",
"...SNIP...",
],
},
}

View file

@ -358,6 +358,7 @@ the actual testing, such as functions containing assertions, must start with
.. code-block:: python .. code-block:: python
def test_user_present(self): def test_user_present(self):
...
When functions in test files are not prepended with ``test_``, the function When functions in test files are not prepended with ``test_``, the function
acts as a normal, helper function and is not run as a test by the test suite. acts as a normal, helper function and is not run as a test by the test suite.

View file

@ -89,16 +89,12 @@ kernel is not present, then the test should be skipped.
.. code-block:: python .. code-block:: python
def setUp(self): def setUp(self):
''' """
Sets up test requirements Sets up test requirements
''' """
os_grain = self.run_function('grains.item', ['kernel']) os_grain = self.run_function("grains.item", ["kernel"])
if os_grain['kernel'] not in 'Darwin': if os_grain["kernel"] not in "Darwin":
self.skipTest( self.skipTest("Test not applicable to '{kernel}' kernel".format(**os_grain))
'Test not applicable to \'{kernel}\' kernel'.format(
**os_grain
)
)
The ``setUp`` function can be used for many things. The above code snippet is The ``setUp`` function can be used for many things. The above code snippet is
only one example. Another example might be to ensure that a particular setting only one example. Another example might be to ensure that a particular setting
@ -272,24 +268,25 @@ Now the workhorse method ``run_function`` can be used to test a module:
.. code-block:: python .. code-block:: python
import os import os
import tests.integration as integration from tests.support.case import ModuleCase
class TestModuleTest(integration.ModuleCase): class TestModuleTest(ModuleCase):
''' """
Validate the test module Validate the test module
''' """
def test_ping(self): def test_ping(self):
''' """
test.ping test.ping
''' """
self.assertTrue(self.run_function('test.ping')) self.assertTrue(self.run_function("test.ping"))
def test_echo(self): def test_echo(self):
''' """
test.echo test.echo
''' """
self.assertEqual(self.run_function('test.echo', ['text']), 'text') self.assertEqual(self.run_function("test.echo", ["text"]), "text")
The fist example illustrates the testing master issuing a ``test.ping`` call The fist example illustrates the testing master issuing a ``test.ping`` call
to a testing minion. The test asserts that the minion returned with a ``True`` to a testing minion. The test asserts that the minion returned with a ``True``
@ -312,26 +309,29 @@ Validating the shell commands can be done via shell tests:
import shutil import shutil
import tempfile import tempfile
import tests.integration as integration from tests.support.case import ShellCase
class KeyTest(integration.ShellCase):
''' class KeyTest(ShellCase):
"""
Test salt-key script Test salt-key script
''' """
_call_binary_ = 'salt-key' _call_binary_ = "salt-key"
def test_list(self): def test_list(self):
''' """
test salt-key -L test salt-key -L
''' """
data = self.run_key('-L') data = self.run_key("-L")
expect = [ expect = [
'Unaccepted Keys:', "Unaccepted Keys:",
'Accepted Keys:', "Accepted Keys:",
'minion', "minion",
'sub_minion', "sub_minion",
'Rejected:', ''] "Rejected:",
"",
]
self.assertEqual(data, expect) self.assertEqual(data, expect)
This example verifies that the ``salt-key`` command executes and returns as This example verifies that the ``salt-key`` command executes and returns as
@ -345,9 +345,9 @@ Testing salt-ssh functionality can be done using the SSHCase test class:
.. code-block:: python .. code-block:: python
import tests.integration as integration from tests.support.case import SSHCase
class SSHGrainsTest(integration.SSHCase): class SSHGrainsTest(SSHCase):
''' '''
Test salt-ssh grains functionality Test salt-ssh grains functionality
Depend on proper environment set by integration.SSHCase class Depend on proper environment set by integration.SSHCase class
@ -370,20 +370,23 @@ on a minion event bus.
.. code-block:: python .. code-block:: python
import tests.integration as integration
import salt.utils.event import salt.utils.event
from tests.support.mixins import SaltEventAssertsMixin
class TestEvent(integration.SaltEventAssertsMixin):
''' class TestEvent(SaltEventAssertsMixin):
"""
Example test of firing an event and receiving it Example test of firing an event and receiving it
''' """
def test_event(self): def test_event(self):
e = salt.utils.event.get_event('minion', sock_dir=self.minion_opts['sock_dir'], opts=self.minion_opts) e = salt.utils.event.get_event(
"minion", sock_dir=self.minion_opts["sock_dir"], opts=self.minion_opts
)
e.fire_event({'a': 'b'}, '/test_event') e.fire_event({"a": "b"}, "/test_event")
self.assertMinionEventReceived({'a': 'b'}) self.assertMinionEventReceived({"a": "b"})
Syndic Example via SyndicCase Syndic Example via SyndicCase
@ -393,17 +396,19 @@ Testing Salt's Syndic can be done via the SyndicCase test class:
.. code-block:: python .. code-block:: python
import tests.integration as integration from tests.support.case import SyndicCase
class TestSyndic(integration.SyndicCase):
''' class TestSyndic(SyndicCase):
"""
Validate the syndic interface by testing the test module Validate the syndic interface by testing the test module
''' """
def test_ping(self): def test_ping(self):
''' """
test.ping test.ping
''' """
self.assertTrue(self.run_function('test.ping')) self.assertTrue(self.run_function("test.ping"))
This example verifies that a ``test.ping`` command is issued from the testing This example verifies that a ``test.ping`` command is issued from the testing
master, is passed through to the testing syndic, down to the minion, and back master, is passed through to the testing syndic, down to the minion, and back
@ -446,16 +451,16 @@ to test states:
# Import salt libs # Import salt libs
import salt.utils.files import salt.utils.files
HFILE = os.path.join(TMP, 'hosts') HFILE = os.path.join(TMP, "hosts")
class HostTest(ModuleCase, SaltReturnAssertsMixin): class HostTest(ModuleCase, SaltReturnAssertsMixin):
''' """
Validate the host state Validate the host state
''' """
def setUp(self): def setUp(self):
shutil.copyfile(os.path.join(FILES, 'hosts'), HFILE) shutil.copyfile(os.path.join(FILES, "hosts"), HFILE)
super(HostTest, self).setUp() super(HostTest, self).setUp()
def tearDown(self): def tearDown(self):
@ -464,18 +469,18 @@ to test states:
super(HostTest, self).tearDown() super(HostTest, self).tearDown()
def test_present(self): def test_present(self):
''' """
host.present host.present
''' """
name = 'spam.bacon' name = "spam.bacon"
ip = '10.10.10.10' ip = "10.10.10.10"
ret = self.run_state('host.present', name=name, ip=ip) ret = self.run_state("host.present", name=name, ip=ip)
self.assertSaltTrueReturn(ret) self.assertSaltTrueReturn(ret)
with salt.utils.files.fopen(HFILE) as fp_: with salt.utils.files.fopen(HFILE) as fp_:
output = fp_.read() output = fp_.read()
self.assertIn('{0}\t\t{1}'.format(ip, name), output) self.assertIn("{0}\t\t{1}".format(ip, name), output)
To access the integration files, a variable named ``FILES`` points to the To access the integration files, a variable named ``FILES`` points to the
``tests/integration/files`` directory. This is where the referenced ``tests/integration/files`` directory. This is where the referenced
``host.present`` sls file resides. ``host.present`` sls file resides.
@ -507,24 +512,25 @@ the test method:
.. code-block:: python .. code-block:: python
import tests.integration as integration from tests.support.case import ModuleCase
from tests.support.helpers import destructiveTest, skip_if_not_root from tests.support.helpers import destructiveTest, skip_if_not_root
class DestructiveExampleModuleTest(integration.ModuleCase):
''' class DestructiveExampleModuleTest(ModuleCase):
"""
Demonstrate a destructive test Demonstrate a destructive test
''' """
@destructiveTest @destructiveTest
@skip_if_not_root @skip_if_not_root
def test_user_not_present(self): def test_user_not_present(self):
''' """
This is a DESTRUCTIVE TEST it creates a new user on the minion. This is a DESTRUCTIVE TEST it creates a new user on the minion.
And then destroys that user. And then destroys that user.
''' """
ret = self.run_state('user.present', name='salt_test') ret = self.run_state("user.present", name="salt_test")
self.assertSaltTrueReturn(ret) self.assertSaltTrueReturn(ret)
ret = self.run_state('user.absent', name='salt_test') ret = self.run_state("user.absent", name="salt_test")
self.assertSaltTrueReturn(ret) self.assertSaltTrueReturn(ret)
@ -634,27 +640,28 @@ the test function:
from tests.support.helpers import expensiveTest from tests.support.helpers import expensiveTest
@expensiveTest @expensiveTest
def test_instance(self): def test_instance(self):
''' """
Test creating an instance on Linode Test creating an instance on Linode
''' """
name = 'linode-testing' name = "linode-testing"
# create the instance # create the instance
instance = self.run_cloud('-p linode-test {0}'.format(name)) instance = self.run_cloud("-p linode-test {0}".format(name))
str = ' {0}'.format(name) str = " {0}".format(name)
# check if instance with salt installed returned as expected # check if instance with salt installed returned as expected
try: try:
self.assertIn(str, instance) self.assertIn(str, instance)
except AssertionError: except AssertionError:
self.run_cloud('-d {0} --assume-yes'.format(name)) self.run_cloud("-d {0} --assume-yes".format(name))
raise raise
# delete the instance # delete the instance
delete = self.run_cloud('-d {0} --assume-yes'.format(name)) delete = self.run_cloud("-d {0} --assume-yes".format(name))
str = ' True' str = " True"
try: try:
self.assertIn(str, delete) self.assertIn(str, delete)
except AssertionError: except AssertionError:

View file

@ -113,14 +113,10 @@ to the module being tested one should do:
import salt.modules.somemodule as somemodule import salt.modules.somemodule as somemodule
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
class SomeModuleTest(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self): def setup_loader_modules(self):
return { return {somemodule: {"__opts__": {"test": True}}}
somemodule: {
'__opts__': {'test': True}
}
}
Consider this more extensive example from Consider this more extensive example from
``tests/unit/modules/test_libcloud_dns.py``: ``tests/unit/modules/test_libcloud_dns.py``:
@ -133,10 +129,7 @@ Consider this more extensive example from
# Import Salt Testing Libs # Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, MagicMock
patch,
MagicMock,
)
import salt.modules.libcloud_dns as libcloud_dns import salt.modules.libcloud_dns as libcloud_dns
@ -149,23 +142,18 @@ Consider this more extensive example from
return MockDNSDriver() return MockDNSDriver()
@patch('salt.modules.libcloud_dns._get_driver', @patch("salt.modules.libcloud_dns._get_driver", MagicMock(return_value=MockDNSDriver()))
MagicMock(return_value=MockDNSDriver()))
class LibcloudDnsModuleTestCase(TestCase, LoaderModuleMockMixin): class LibcloudDnsModuleTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self): def setup_loader_modules(self):
module_globals = { module_globals = {
'__salt__': { "__salt__": {
'config.option': MagicMock(return_value={ "config.option": MagicMock(
'test': { return_value={"test": {"driver": "test", "key": "2orgk34kgk34g"}}
'driver': 'test', )
'key': '2orgk34kgk34g'
}
})
} }
} }
if libcloud_dns.HAS_LIBCLOUD is False: if libcloud_dns.HAS_LIBCLOUD is False:
module_globals['sys.modules'] = {'libcloud': MagicMock()} module_globals["sys.modules"] = {"libcloud": MagicMock()}
return {libcloud_dns: module_globals} return {libcloud_dns: module_globals}
@ -193,18 +181,15 @@ a separate implementation which has additional functionality.
.. code-block:: python .. code-block:: python
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, mock_open
patch
mock_open,
)
import salt.modules.mymod as mymod import salt.modules.mymod as mymod
class MyAwesomeTestCase(TestCase):
class MyAwesomeTestCase(TestCase):
def test_something(self): def test_something(self):
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n') fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
with patch('salt.utils.files.fopen', fopen_mock): with patch("salt.utils.files.fopen", fopen_mock):
result = mymod.myfunc() result = mymod.myfunc()
assert result is True assert result is True
@ -245,30 +230,31 @@ those cases, you can pass ``read_data`` as a dictionary:
import textwrap import textwrap
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, mock_open
patch
mock_open,
)
import salt.modules.mymod as mymod import salt.modules.mymod as mymod
class MyAwesomeTestCase(TestCase):
class MyAwesomeTestCase(TestCase):
def test_something(self): def test_something(self):
contents = { contents = {
'/etc/foo.conf': textwrap.dedent('''\ "/etc/foo.conf": textwrap.dedent(
"""\
foo foo
bar bar
baz baz
'''), """
'/etc/b*.conf': textwrap.dedent('''\ ),
"/etc/b*.conf": textwrap.dedent(
"""\
one one
two two
three three
'''), """
),
} }
fopen_mock = mock_open(read_data=contents) fopen_mock = mock_open(read_data=contents)
with patch('salt.utils.files.fopen', fopen_mock): with patch("salt.utils.files.fopen", fopen_mock):
result = mymod.myfunc() result = mymod.myfunc()
assert result is True assert result is True
@ -284,8 +270,8 @@ below two ``mock_open`` calls would produce identical results:
.. code-block:: python .. code-block:: python
mock_open(read_data='foo\n') mock_open(read_data="foo\n")
mock_open(read_data={'*': 'foo\n'}) mock_open(read_data={"*": "foo\n"})
.. note:: .. note::
Take care when specifying the ``read_data`` as a dictionary, in cases where Take care when specifying the ``read_data`` as a dictionary, in cases where
@ -300,9 +286,9 @@ below two ``mock_open`` calls would produce identical results:
.. code-block:: python .. code-block:: python
contents = OrderedDict() contents = OrderedDict()
contents['/etc/bar.conf'] = 'foo\nbar\nbaz\n' contents["/etc/bar.conf"] = "foo\nbar\nbaz\n"
contents['/etc/b*.conf'] = IOError(errno.EACCES, 'Permission denied') contents["/etc/b*.conf"] = IOError(errno.EACCES, "Permission denied")
contents['*'] = 'This is a fallback for files not beginning with "/etc/b"\n' contents["*"] = 'This is a fallback for files not beginning with "/etc/b"\n'
fopen_mock = mock_open(read_data=contents) fopen_mock = mock_open(read_data=contents)
Raising Exceptions Raising Exceptions
@ -315,19 +301,16 @@ Instead of a string, an exception can also be used as the ``read_data``:
import errno import errno
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, mock_open
patch
mock_open,
)
import salt.modules.mymod as mymod import salt.modules.mymod as mymod
class MyAwesomeTestCase(TestCase):
class MyAwesomeTestCase(TestCase):
def test_something(self): def test_something(self):
exc = IOError(errno.EACCES, 'Permission denied') exc = IOError(errno.EACCES, "Permission denied")
fopen_mock = mock_open(read_data=exc) fopen_mock = mock_open(read_data=exc)
with patch('salt.utils.files.fopen', fopen_mock): with patch("salt.utils.files.fopen", fopen_mock):
mymod.myfunc() mymod.myfunc()
The above example would raise the specified exception when any file is opened. The above example would raise the specified exception when any file is opened.
@ -350,39 +333,42 @@ and produce a mocked filehandle with the specified contents. For example:
import textwrap import textwrap
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, mock_open
patch
mock_open,
)
import salt.modules.mymod as mymod import salt.modules.mymod as mymod
class MyAwesomeTestCase(TestCase):
class MyAwesomeTestCase(TestCase):
def test_something(self): def test_something(self):
contents = { contents = {
'/etc/foo.conf': [ "/etc/foo.conf": [
textwrap.dedent('''\ textwrap.dedent(
"""\
foo foo
bar bar
'''), """
textwrap.dedent('''\ ),
textwrap.dedent(
"""\
foo foo
bar bar
baz baz
'''), """
),
], ],
'/etc/b*.conf': [ "/etc/b*.conf": [
IOError(errno.ENOENT, 'No such file or directory'), IOError(errno.ENOENT, "No such file or directory"),
textwrap.dedent('''\ textwrap.dedent(
"""\
one one
two two
three three
'''), """
),
], ],
} }
fopen_mock = mock_open(read_data=contents) fopen_mock = mock_open(read_data=contents)
with patch('salt.utils.files.fopen', fopen_mock): with patch("salt.utils.files.fopen", fopen_mock):
result = mymod.myfunc() result = mymod.myfunc()
assert result is True assert result is True
@ -423,8 +409,8 @@ so, just add an ``as`` clause to the end of the ``patch`` statement:
.. code-block:: python .. code-block:: python
fopen_mock = mock_open(read_data='foo\nbar\nbaz\n') fopen_mock = mock_open(read_data="foo\nbar\nbaz\n")
with patch('salt.utils.files.fopen', fopen_mock) as m_open: with patch("salt.utils.files.fopen", fopen_mock) as m_open:
# do testing here # do testing here
... ...
... ...
@ -448,29 +434,25 @@ several useful attributes:
.. code-block:: python .. code-block:: python
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import patch, mock_open, MockCall
patch
mock_open,
MockCall,
)
import salt.modules.mymod as mymod import salt.modules.mymod as mymod
class MyAwesomeTestCase(TestCase):
class MyAwesomeTestCase(TestCase):
def test_something(self): def test_something(self):
with patch('salt.utils.files.fopen', mock_open(read_data=b'foo\n')) as m_open: with patch("salt.utils.files.fopen", mock_open(read_data=b"foo\n")) as m_open:
mymod.myfunc() mymod.myfunc()
# Assert that only two opens attempted # Assert that only two opens attempted
assert m_open.call_count == 2 assert m_open.call_count == 2
# Assert that only /etc/foo.conf was opened # Assert that only /etc/foo.conf was opened
assert all(call.args[0] == '/etc/foo.conf' for call in m_open.calls) assert all(call.args[0] == "/etc/foo.conf" for call in m_open.calls)
# Asser that the first open was for binary read, and the # Asser that the first open was for binary read, and the
# second was for binary write. # second was for binary write.
assert m_open.calls == [ assert m_open.calls == [
MockCall('/etc/foo.conf', 'rb'), MockCall("/etc/foo.conf", "rb"),
MockCall('/etc/foo.conf', 'wb'), MockCall("/etc/foo.conf", "wb"),
] ]
Note that ``MockCall`` is imported from ``tests.support.mock`` in the above Note that ``MockCall`` is imported from ``tests.support.mock`` in the above
@ -526,35 +508,35 @@ Examples
.. code-block:: python .. code-block:: python
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open: with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
# Run the code you are unit testing # Run the code you are unit testing
mymod.myfunc() mymod.myfunc()
# Check that only the expected file was opened, and that it was opened # Check that only the expected file was opened, and that it was opened
# only once. # only once.
assert m_open.call_count == 1 assert m_open.call_count == 1
assert list(m_open.filehandles) == ['/etc/foo.conf'] assert list(m_open.filehandles) == ["/etc/foo.conf"]
# "opens" will be a list of all the mocked filehandles opened # "opens" will be a list of all the mocked filehandles opened
opens = m_open.filehandles['/etc/foo.conf'] opens = m_open.filehandles["/etc/foo.conf"]
# Check that we wrote the expected lines ("expected" here is assumed to # Check that we wrote the expected lines ("expected" here is assumed to
# be a list of strings) # be a list of strings)
assert opens[0].write_calls == expected assert opens[0].write_calls == expected
.. code-block:: python .. code-block:: python
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open: with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
# Run the code you are unit testing # Run the code you are unit testing
mymod.myfunc() mymod.myfunc()
# Check that .readlines() was called (remember, it's a Mock) # Check that .readlines() was called (remember, it's a Mock)
m_open.filehandles['/etc/foo.conf'][0].readlines.assert_called() m_open.filehandles["/etc/foo.conf"][0].readlines.assert_called()
.. code-block:: python .. code-block:: python
with patch('salt.utils.files.fopen', mock_open(read_data=contents)) as m_open: with patch("salt.utils.files.fopen", mock_open(read_data=contents)) as m_open:
# Run the code you are unit testing # Run the code you are unit testing
mymod.myfunc() mymod.myfunc()
# Check that we read the file and also wrote to it # Check that we read the file and also wrote to it
m_open.filehandles['/etc/foo.conf'][0].read.assert_called_once() m_open.filehandles["/etc/foo.conf"][0].read.assert_called_once()
m_open.filehandles['/etc/foo.conf'][1].writelines.assert_called_once() m_open.filehandles["/etc/foo.conf"][1].writelines.assert_called_once()
.. _`Mock()`: https://github.com/testing-cabal/mock .. _`Mock()`: https://github.com/testing-cabal/mock
@ -628,11 +610,13 @@ methods, here presented in pseduo-code in an imaginary execution module called
.. code-block:: python .. code-block:: python
def create_user(username): def create_user(username):
qry = 'CREATE USER {0}'.format(username) qry = "CREATE USER {0}".format(username)
execute_query(qry) execute_query(qry)
def execute_query(qry): def execute_query(qry):
# Connect to a database and actually do the query... # Connect to a database and actually do the query...
...
Here, let's imagine that we want to create a unit test for the `create_user` Here, let's imagine that we want to create a unit test for the `create_user`
function. In doing so, we want to avoid any calls out to an external system and function. In doing so, we want to avoid any calls out to an external system and
@ -661,10 +645,10 @@ additional imports for MagicMock:
class DbTestCase(TestCase): class DbTestCase(TestCase):
def test_create_user(self): def test_create_user(self):
# First, we replace 'execute_query' with our own mock function # First, we replace 'execute_query' with our own mock function
with patch.object(db, 'execute_query', MagicMock()) as db_exq: with patch.object(db, "execute_query", MagicMock()) as db_exq:
# Now that the exits are blocked, we can run the function under test. # Now that the exits are blocked, we can run the function under test.
db.create_user('testuser') db.create_user("testuser")
# We could now query our mock object to see which calls were made # We could now query our mock object to see which calls were made
# to it. # to it.
@ -672,7 +656,7 @@ additional imports for MagicMock:
# Construct a call object that simulates the way we expected # Construct a call object that simulates the way we expected
# execute_query to have been called. # execute_query to have been called.
expected_call = call('CREATE USER testuser') expected_call = call("CREATE USER testuser")
# Compare the expected call with the list of actual calls. The # Compare the expected call with the list of actual calls. The
# test will succeed or fail depending on the output of this # test will succeed or fail depending on the output of this
@ -730,14 +714,15 @@ we might write the skeleton for testing ``fib.py``:
# Create test case class and inherit from Salt's customized TestCase # Create test case class and inherit from Salt's customized TestCase
class FibTestCase(TestCase): class FibTestCase(TestCase):
''' """
This class contains a set of functions that test salt.modules.fib. This class contains a set of functions that test salt.modules.fib.
''' """
def test_fib(self): def test_fib(self):
''' """
To create a unit test, we should prefix the name with `test_' so To create a unit test, we should prefix the name with `test_' so
that it's recognized by the test runner. that it's recognized by the test runner.
''' """
fib_five = (0, 1, 1, 2, 3) fib_five = (0, 1, 1, 2, 3)
self.assertEqual(fib.calculate(5), fib_five) self.assertEqual(fib.calculate(5), fib_five)
@ -774,7 +759,7 @@ Consider the following function from salt/modules/linux_sysctl.py.
.. code-block:: python .. code-block:: python
def get(name): def get(name):
''' """
Return a single sysctl parameter for this minion Return a single sysctl parameter for this minion
CLI Example: CLI Example:
@ -782,9 +767,9 @@ Consider the following function from salt/modules/linux_sysctl.py.
.. code-block:: bash .. code-block:: bash
salt '*' sysctl.get net.ipv4.ip_forward salt '*' sysctl.get net.ipv4.ip_forward
''' """
cmd = 'sysctl -n {0}'.format(name) cmd = "sysctl -n {0}".format(name)
out = __salt__['cmd.run'](cmd) out = __salt__["cmd.run"](cmd)
return out return out
This function is very simple, comprising only four source lines of code and This function is very simple, comprising only four source lines of code and
@ -805,24 +790,21 @@ will also redefine the ``__salt__`` dictionary such that it only contains
# Import Salt Testing Libs # Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import MagicMock, patch
MagicMock,
patch,
)
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin): class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
''' """
TestCase for salt.modules.linux_sysctl module TestCase for salt.modules.linux_sysctl module
''' """
def test_get(self): def test_get(self):
''' """
Tests the return of get function Tests the return of get function
''' """
mock_cmd = MagicMock(return_value=1) mock_cmd = MagicMock(return_value=1)
with patch.dict(linux_sysctl.__salt__, {'cmd.run': mock_cmd}): with patch.dict(linux_sysctl.__salt__, {"cmd.run": mock_cmd}):
self.assertEqual(linux_sysctl.get('net.ipv4.ip_forward'), 1) self.assertEqual(linux_sysctl.get("net.ipv4.ip_forward"), 1)
Since ``get()`` has only one raise or return statement and that statement is a Since ``get()`` has only one raise or return statement and that statement is a
success condition, the test function is simply named ``test_get()``. As success condition, the test function is simply named ``test_get()``. As
@ -847,7 +829,7 @@ salt/modules/linux_sysctl.py source file.
.. code-block:: python .. code-block:: python
def assign(name, value): def assign(name, value):
''' """
Assign a single sysctl parameter for this minion Assign a single sysctl parameter for this minion
CLI Example: CLI Example:
@ -855,31 +837,30 @@ salt/modules/linux_sysctl.py source file.
.. code-block:: bash .. code-block:: bash
salt '*' sysctl.assign net.ipv4.ip_forward 1 salt '*' sysctl.assign net.ipv4.ip_forward 1
''' """
value = str(value) value = str(value)
sysctl_file = '/proc/sys/{0}'.format(name.replace('.', '/')) sysctl_file = "/proc/sys/{0}".format(name.replace(".", "/"))
if not os.path.exists(sysctl_file): if not os.path.exists(sysctl_file):
raise CommandExecutionError('sysctl {0} does not exist'.format(name)) raise CommandExecutionError("sysctl {0} does not exist".format(name))
ret = {} ret = {}
cmd = 'sysctl -w {0}="{1}"'.format(name, value) cmd = 'sysctl -w {0}="{1}"'.format(name, value)
data = __salt__['cmd.run_all'](cmd) data = __salt__["cmd.run_all"](cmd)
out = data['stdout'] out = data["stdout"]
err = data['stderr'] err = data["stderr"]
# Example: # Example:
# # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216" # # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
# net.ipv4.tcp_rmem = 4096 87380 16777216 # net.ipv4.tcp_rmem = 4096 87380 16777216
regex = re.compile(r'^{0}\s+=\s+{1}$'.format(re.escape(name), regex = re.compile(r"^{0}\s+=\s+{1}$".format(re.escape(name), re.escape(value)))
re.escape(value)))
if not regex.match(out) or 'Invalid argument' in str(err): if not regex.match(out) or "Invalid argument" in str(err):
if data['retcode'] != 0 and err: if data["retcode"] != 0 and err:
error = err error = err
else: else:
error = out error = out
raise CommandExecutionError('sysctl -w failed: {0}'.format(error)) raise CommandExecutionError("sysctl -w failed: {0}".format(error))
new_name, new_value = out.split(' = ', 1) new_name, new_value = out.split(" = ", 1)
ret[new_name] = new_value ret[new_name] = new_value
return ret return ret
@ -904,53 +885,63 @@ with.
# Import Salt Testing Libs # Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase from tests.support.unit import TestCase
from tests.support.mock import ( from tests.support.mock import MagicMock, patch
MagicMock,
patch,
)
class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin): class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
''' """
TestCase for salt.modules.linux_sysctl module TestCase for salt.modules.linux_sysctl module
''' """
@patch('os.path.exists', MagicMock(return_value=False)) @patch("os.path.exists", MagicMock(return_value=False))
def test_assign_proc_sys_failed(self): def test_assign_proc_sys_failed(self):
''' """
Tests if /proc/sys/<kernel-subsystem> exists or not Tests if /proc/sys/<kernel-subsystem> exists or not
''' """
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '', cmd = {
'stdout': 'net.ipv4.ip_forward = 1'} "pid": 1337,
"retcode": 0,
"stderr": "",
"stdout": "net.ipv4.ip_forward = 1",
}
mock_cmd = MagicMock(return_value=cmd) mock_cmd = MagicMock(return_value=cmd)
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}): with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
self.assertRaises(CommandExecutionError, self.assertRaises(
linux_sysctl.assign, CommandExecutionError, linux_sysctl.assign, "net.ipv4.ip_forward", 1
'net.ipv4.ip_forward', 1) )
@patch('os.path.exists', MagicMock(return_value=True)) @patch("os.path.exists", MagicMock(return_value=True))
def test_assign_cmd_failed(self): def test_assign_cmd_failed(self):
''' """
Tests if the assignment was successful or not Tests if the assignment was successful or not
''' """
cmd = {'pid': 1337, 'retcode': 0, 'stderr': cmd = {
'sysctl: setting key "net.ipv4.ip_forward": Invalid argument', "pid": 1337,
'stdout': 'net.ipv4.ip_forward = backward'} "retcode": 0,
"stderr": 'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
"stdout": "net.ipv4.ip_forward = backward",
}
mock_cmd = MagicMock(return_value=cmd) mock_cmd = MagicMock(return_value=cmd)
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}): with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
self.assertRaises(CommandExecutionError, self.assertRaises(
linux_sysctl.assign, CommandExecutionError,
'net.ipv4.ip_forward', 'backward') linux_sysctl.assign,
"net.ipv4.ip_forward",
"backward",
)
@patch('os.path.exists', MagicMock(return_value=True)) @patch("os.path.exists", MagicMock(return_value=True))
def test_assign_success(self): def test_assign_success(self):
''' """
Tests the return of successful assign function Tests the return of successful assign function
''' """
cmd = {'pid': 1337, 'retcode': 0, 'stderr': '', cmd = {
'stdout': 'net.ipv4.ip_forward = 1'} "pid": 1337,
ret = {'net.ipv4.ip_forward': '1'} "retcode": 0,
"stderr": "",
"stdout": "net.ipv4.ip_forward = 1",
}
ret = {"net.ipv4.ip_forward": "1"}
mock_cmd = MagicMock(return_value=cmd) mock_cmd = MagicMock(return_value=cmd)
with patch.dict(linux_sysctl.__salt__, {'cmd.run_all': mock_cmd}): with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
self.assertEqual(linux_sysctl.assign( self.assertEqual(linux_sysctl.assign("net.ipv4.ip_forward", 1), ret)
'net.ipv4.ip_forward', 1), ret)

View file

@ -84,13 +84,11 @@ The following code will check for a single event:
import salt.config import salt.config
import salt.utils.event import salt.utils.event
opts = salt.config.client_config('/etc/salt/master') opts = salt.config.client_config("/etc/salt/master")
event = salt.utils.event.get_event( event = salt.utils.event.get_event(
'master', "master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
sock_dir=opts['sock_dir'], )
transport=opts['transport'],
opts=opts)
data = event.get_event() data = event.get_event()
@ -106,15 +104,15 @@ instead of the default 5.
.. code-block:: python .. code-block:: python
data = event.get_event(wait=10, tag='salt/auth') data = event.get_event(wait=10, tag="salt/auth")
To retrieve the tag as well as the event data, pass ``full=True``: To retrieve the tag as well as the event data, pass ``full=True``:
.. code-block:: python .. code-block:: python
evdata = event.get_event(wait=10, tag='salt/job', full=True) evdata = event.get_event(wait=10, tag="salt/job", full=True)
tag, data = evdata['tag'], evdata['data'] tag, data = evdata["tag"], evdata["data"]
Instead of looking for a single event, the ``iter_events`` method can be used to Instead of looking for a single event, the ``iter_events`` method can be used to
@ -124,7 +122,7 @@ The iter_events method also accepts a tag but not a wait time:
.. code-block:: python .. code-block:: python
for data in event.iter_events(tag='salt/auth'): for data in event.iter_events(tag="salt/auth"):
print(data) print(data)
And finally event tags can be globbed, such as they can be in the Reactor, And finally event tags can be globbed, such as they can be in the Reactor,
@ -137,21 +135,19 @@ using the fnmatch library.
import salt.config import salt.config
import salt.utils.event import salt.utils.event
opts = salt.config.client_config('/etc/salt/master') opts = salt.config.client_config("/etc/salt/master")
sevent = salt.utils.event.get_event( sevent = salt.utils.event.get_event(
'master', "master", sock_dir=opts["sock_dir"], transport=opts["transport"], opts=opts
sock_dir=opts['sock_dir'], )
transport=opts['transport'],
opts=opts)
while True: while True:
ret = sevent.get_event(full=True) ret = sevent.get_event(full=True)
if ret is None: if ret is None:
continue continue
if fnmatch.fnmatch(ret['tag'], 'salt/job/*/ret/*'): if fnmatch.fnmatch(ret["tag"], "salt/job/*/ret/*"):
do_something_with_job_return(ret['data']) do_something_with_job_return(ret["data"])
Firing Events Firing Events
============= =============
@ -184,12 +180,12 @@ a minion on a non-Windows system:
# Job on minion # Job on minion
import salt.utils.event import salt.utils.event
opts = salt.config.minion_config('/etc/salt/minion') opts = salt.config.minion_config("/etc/salt/minion")
event = salt.utils.event.MinionEvent(opts) event = salt.utils.event.MinionEvent(opts)
for evdata in event.iter_events(match_type = 'regex', for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
tag = 'custom/.*'):
# do your processing here... # do your processing here...
...
And an example of listening local events on a Windows system: And an example of listening local events on a Windows system:
@ -201,9 +197,9 @@ And an example of listening local events on a Windows system:
opts = salt.config.minion_config(salt.minion.DEFAULT_MINION_OPTS) opts = salt.config.minion_config(salt.minion.DEFAULT_MINION_OPTS)
event = salt.utils.event.MinionEvent(opts) event = salt.utils.event.MinionEvent(opts)
for evdata in event.iter_events(match_type = 'regex', for evdata in event.iter_events(match_type="regex", tag="custom/.*"):
tag = 'custom/.*'):
# do your processing here... # do your processing here...
...
.. code-block:: bash .. code-block:: bash
@ -224,19 +220,20 @@ easily done using the normal cross-calling syntax:
# /srv/salt/_modules/my_custom_module.py # /srv/salt/_modules/my_custom_module.py
def do_something(): def do_something():
''' """
Do something and fire an event to the master when finished Do something and fire an event to the master when finished
CLI Example:: CLI Example::
salt '*' my_custom_module:do_something salt '*' my_custom_module:do_something
''' """
# do something! # do something!
__salt__['event.send']('myco/my_custom_module/finished', { __salt__["event.send"](
'finished': True, "myco/my_custom_module/finished",
'message': "The something is finished!", {"finished": True, "message": "The something is finished!",},
}) )
From Custom Python Scripts From Custom Python Scripts
-------------------------- --------------------------
@ -257,3 +254,4 @@ done at the CLI:
if not ret: if not ret:
# the event could not be sent, process the error here # the event could not be sent, process the error here
...

View file

@ -58,9 +58,9 @@ Example Process Class
self.opts = opts self.opts = opts
def run(self): def run(self):
self.event = SaltEvent('master', self.opts['sock_dir']) self.event = SaltEvent("master", self.opts["sock_dir"])
i = 0 i = 0
while True: while True:
self.event.fire_event({'iteration': i}, 'ext_processes/test{0}') self.event.fire_event({"iteration": i}, "ext_processes/test{0}")
time.sleep(60) time.sleep(60)

View file

@ -146,12 +146,12 @@ dictionary. For example:
.. code-block:: python .. code-block:: python
def yourfunction(): def yourfunction():
# initialize a grains dictionary # initialize a grains dictionary
grains = {} grains = {}
# Some code for logic that sets grains like # Some code for logic that sets grains like
grains['yourcustomgrain'] = True grains["yourcustomgrain"] = True
grains['anothergrain'] = 'somevalue' grains["anothergrain"] = "somevalue"
return grains return grains
The name of the function does not matter and will not factor into the grains The name of the function does not matter and will not factor into the grains
data at all; only the keys/values returned become part of the grains. data at all; only the keys/values returned become part of the grains.
@ -205,14 +205,14 @@ grain data structure. For example, consider this custom grain file:
#!/usr/bin/env python #!/usr/bin/env python
def _my_custom_grain(): def _my_custom_grain():
my_grain = {'foo': 'bar', 'hello': 'world'} my_grain = {"foo": "bar", "hello": "world"}
return my_grain return my_grain
def main(): def main():
# initialize a grains dictionary # initialize a grains dictionary
grains = {} grains = {}
grains['my_grains'] = _my_custom_grain() grains["my_grains"] = _my_custom_grain()
return grains return grains
The output of this example renders like so: The output of this example renders like so:

View file

@ -379,7 +379,7 @@ Returns:
.. code-block:: python .. code-block:: python
('defabcdef',) ("defabcdef",)
.. jinja_ref:: regex_match .. jinja_ref:: regex_match
@ -740,7 +740,7 @@ Returns:
.. code-block:: python .. code-block:: python
{'new': [4], 'old': [3]} {"new": [4], "old": [3]}
.. jinja_ref:: compare_dicts .. jinja_ref:: compare_dicts
@ -762,7 +762,7 @@ Returns:
.. code-block:: python .. code-block:: python
{'a': {'new': 'c', 'old': 'b'}} {"a": {"new": "c", "old": "b"}}
.. jinja_ref:: is_hex .. jinja_ref:: is_hex
@ -1018,7 +1018,7 @@ Returns:
.. code-block:: python .. code-block:: python
{'a': '\xd0\x94'} {"a": "\xd0\x94"}
.. jinja_ref:: tojson .. jinja_ref:: tojson
@ -1381,7 +1381,7 @@ Returns:
.. code-block:: python .. code-block:: python
{'c1': 'foo'} {"c1": "foo"}
.. code-block:: jinja .. code-block:: jinja
@ -1391,7 +1391,7 @@ Returns:
.. code-block:: python .. code-block:: python
'default' "default"
.. jinja_ref:: json_query .. jinja_ref:: json_query
@ -1571,7 +1571,7 @@ Returns:
.. code-block:: python .. code-block:: python
['192.168.0.1', 'fe80::'] ["192.168.0.1", "fe80::"]
.. jinja_ref:: ipv4 .. jinja_ref:: ipv4
@ -1594,7 +1594,7 @@ Returns:
.. code-block:: python .. code-block:: python
['192.168.0.1'] ["192.168.0.1"]
.. jinja_ref:: ipv6 .. jinja_ref:: ipv6
@ -1617,7 +1617,7 @@ Returns:
.. code-block:: python .. code-block:: python
['fe80::'] ["fe80::"]
.. jinja_ref:: network_hosts .. jinja_ref:: network_hosts
@ -1644,7 +1644,7 @@ Returns:
.. code-block:: python .. code-block:: python
['192.168.0.1', '192.168.0.2'] ["192.168.0.1", "192.168.0.2"]
.. jinja_ref:: network_size .. jinja_ref:: network_size
@ -1947,7 +1947,7 @@ Returns:
.. code-block:: python .. code-block:: python
[{'value': 3}] [{"value": 3}]
.. jinja_ref:: match .. jinja_ref:: match
@ -1976,7 +1976,7 @@ Returns:
.. code-block:: python .. code-block:: python
[{'value': 'b'}, {'value': 'c'}] [{"value": "b"}, {"value": "c"}]
Test supports additional optional arguments: ``ignorecase``, ``multiline`` Test supports additional optional arguments: ``ignorecase``, ``multiline``

View file

@ -61,8 +61,9 @@ bare-bones example:
import logging import logging
import sys import sys
# Define the module's virtual name # Define the module's virtual name
__virtualname__ = 'customtop' __virtualname__ = "customtop"
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -72,8 +73,8 @@ bare-bones example:
def top(**kwargs): def top(**kwargs):
log.debug('Calling top in customtop') log.debug("Calling top in customtop")
return {'base': ['test']} return {"base": ["test"]}
`salt minion state.show_top` should then display something like: `salt minion state.show_top` should then display something like:

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 __future__ import absolute_import, print_function, unicode_literals
from salt.ext import six # pylint: disable=3rd-party-module-not-gated from salt.ext import six # pylint: disable=3rd-party-module-not-gated
def match(self, tgt): def match(self, tgt):
''' """
Determines if this host is on the list Determines if this host is on the list
''' """
if isinstance(tgt, six.string_types): if isinstance(tgt, six.string_types):
# The stock matcher splits on `,`. Change to `/` below. # The stock matcher splits on `,`. Change to `/` below.
tgt = tgt.split('/') tgt = tgt.split("/")
return bool(self.opts['id'] in tgt) return bool(self.opts["id"] in tgt)
Place this code in a file called ``list_matcher.py`` in ``_matchers`` in your Place this code in a file called ``list_matcher.py`` in ``_matchers`` in your

View file

@ -278,10 +278,10 @@ in remote execution functions. Here is some example pseudocode:
def myrunner(): def myrunner():
... ...
do stuff # do stuff
... ...
if some_error_condition: if some_error_condition:
__context__['retcode'] = 1 __context__["retcode"] = 1
return result return result
This allows a custom runner/wheel function to report its failure so that This allows a custom runner/wheel function to report its failure so that

View file

@ -85,12 +85,14 @@ they are being loaded for the correct proxytype, example below:
.. code-block:: python .. code-block:: python
def __virtual__(): def __virtual__():
''' """
Only work on proxy Only work on proxy
''' """
try: try:
if salt.utils.platform.is_proxy() and \ if (
__opts__['proxy']['proxytype'] == 'ssh_sample': salt.utils.platform.is_proxy()
and __opts__["proxy"]["proxytype"] == "ssh_sample"
):
return __virtualname__ return __virtualname__
except KeyError: except KeyError:
pass pass
@ -110,10 +112,10 @@ to ``__proxy__``. This enables patterns like
.. code-block:: python .. code-block:: python
def get_ip(proxy): def get_ip(proxy):
''' """
Ask the remote device what IP it has Ask the remote device what IP it has
''' """
return {'ip':proxy['proxymodulename.get_ip']()} return {"ip": proxy["proxymodulename.get_ip"]()}
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function
@ -397,10 +399,10 @@ and status; "package" installation, and a ping.
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
This is a simple proxy-minion designed to connect to and communicate with This is a simple proxy-minion designed to connect to and communicate with
the bottle-based web service contained in https://github.com/saltstack/salt-contrib/tree/master/proxyminion_rest_example the bottle-based web service contained in https://github.com/saltstack/salt-contrib/tree/master/proxyminion_rest_example
''' """
from __future__ import absolute_import from __future__ import absolute_import
# Import python libs # Import python libs
@ -410,7 +412,7 @@ and status; "package" installation, and a ping.
HAS_REST_EXAMPLE = True HAS_REST_EXAMPLE = True
# This must be present or the Salt loader won't load this module # This must be present or the Salt loader won't load this module
__proxyenabled__ = ['rest_sample'] __proxyenabled__ = ["rest_sample"]
# Variables are scoped to this module so we can have persistent data # Variables are scoped to this module so we can have persistent data
@ -425,182 +427,207 @@ and status; "package" installation, and a ping.
# This does nothing, it's here just as an example and to provide a log # This does nothing, it's here just as an example and to provide a log
# entry when the module is loaded. # entry when the module is loaded.
def __virtual__(): def __virtual__():
''' """
Only return if all the modules are available Only return if all the modules are available
''' """
log.debug('rest_sample proxy __virtual__() called...') log.debug("rest_sample proxy __virtual__() called...")
return True return True
def _complicated_function_that_determines_if_alive(): def _complicated_function_that_determines_if_alive():
return True return True
# Every proxy module needs an 'init', though you can # Every proxy module needs an 'init', though you can
# just put DETAILS['initialized'] = True here if nothing # just put DETAILS['initialized'] = True here if nothing
# else needs to be done. # else needs to be done.
def init(opts): def init(opts):
log.debug('rest_sample proxy init() called...') log.debug("rest_sample proxy init() called...")
DETAILS['initialized'] = True DETAILS["initialized"] = True
# Save the REST URL # Save the REST URL
DETAILS['url'] = opts['proxy']['url'] DETAILS["url"] = opts["proxy"]["url"]
# Make sure the REST URL ends with a '/' # Make sure the REST URL ends with a '/'
if not DETAILS['url'].endswith('/'): if not DETAILS["url"].endswith("/"):
DETAILS['url'] += '/' DETAILS["url"] += "/"
def alive(opts): def alive(opts):
''' """
This function returns a flag with the connection state. This function returns a flag with the connection state.
It is very useful when the proxy minion establishes the communication It is very useful when the proxy minion establishes the communication
via a channel that requires a more elaborated keep-alive mechanism, e.g. via a channel that requires a more elaborated keep-alive mechanism, e.g.
NETCONF over SSH. NETCONF over SSH.
''' """
log.debug('rest_sample proxy alive() called...') log.debug("rest_sample proxy alive() called...")
return _complicated_function_that_determines_if_alive() return _complicated_function_that_determines_if_alive()
def initialized(): def initialized():
''' """
Since grains are loaded in many different places and some of those Since grains are loaded in many different places and some of those
places occur before the proxy can be initialized, return whether places occur before the proxy can be initialized, return whether
our init() function has been called our init() function has been called
''' """
return DETAILS.get('initialized', False) return DETAILS.get("initialized", False)
def grains(): def grains():
''' """
Get the grains from the proxied device Get the grains from the proxied device
''' """
if not DETAILS.get('grains_cache', {}): if not DETAILS.get("grains_cache", {}):
r = salt.utils.http.query(DETAILS['url']+'info', decode_type='json', decode=True) r = salt.utils.http.query(
DETAILS['grains_cache'] = r['dict'] DETAILS["url"] + "info", decode_type="json", decode=True
return DETAILS['grains_cache'] )
DETAILS["grains_cache"] = r["dict"]
return DETAILS["grains_cache"]
def grains_refresh(): def grains_refresh():
''' """
Refresh the grains from the proxied device Refresh the grains from the proxied device
''' """
DETAILS['grains_cache'] = None DETAILS["grains_cache"] = None
return grains() return grains()
def fns(): def fns():
return {'details': 'This key is here because a function in ' return {
'grains/rest_sample.py called fns() here in the proxymodule.'} "details": "This key is here because a function in "
"grains/rest_sample.py called fns() here in the proxymodule."
}
def service_start(name): def service_start(name):
''' """
Start a "service" on the REST server Start a "service" on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'service/start/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "service/start/" + name, decode_type="json", decode=True
)
return r["dict"]
def service_stop(name): def service_stop(name):
''' """
Stop a "service" on the REST server Stop a "service" on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'service/stop/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "service/stop/" + name, decode_type="json", decode=True
)
return r["dict"]
def service_restart(name): def service_restart(name):
''' """
Restart a "service" on the REST server Restart a "service" on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'service/restart/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "service/restart/" + name, decode_type="json", decode=True
)
return r["dict"]
def service_list(): def service_list():
''' """
List "services" on the REST server List "services" on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'service/list', decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "service/list", decode_type="json", decode=True
)
return r["dict"]
def service_status(name): def service_status(name):
''' """
Check if a service is running on the REST server Check if a service is running on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'service/status/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "service/status/" + name, decode_type="json", decode=True
)
return r["dict"]
def package_list(): def package_list():
''' """
List "packages" installed on the REST server List "packages" installed on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'package/list', decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "package/list", decode_type="json", decode=True
)
return r["dict"]
def package_install(name, **kwargs): def package_install(name, **kwargs):
''' """
Install a "package" on the REST server Install a "package" on the REST server
''' """
cmd = DETAILS['url']+'package/install/'+name cmd = DETAILS["url"] + "package/install/" + name
if kwargs.get('version', False): if kwargs.get("version", False):
cmd += '/'+kwargs['version'] cmd += "/" + kwargs["version"]
else: else:
cmd += '/1.0' cmd += "/1.0"
r = salt.utils.http.query(cmd, decode_type='json', decode=True) r = salt.utils.http.query(cmd, decode_type="json", decode=True)
return r['dict'] return r["dict"]
def fix_outage(): def fix_outage():
r = salt.utils.http.query(DETAILS['url']+'fix_outage') r = salt.utils.http.query(DETAILS["url"] + "fix_outage")
return r return r
def uptodate(name): def uptodate(name):
''' """
Call the REST endpoint to see if the packages on the "server" are up to date. Call the REST endpoint to see if the packages on the "server" are up to date.
''' """
r = salt.utils.http.query(DETAILS['url']+'package/remove/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
)
return r["dict"]
def package_remove(name): def package_remove(name):
''' """
Remove a "package" on the REST server Remove a "package" on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'package/remove/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "package/remove/" + name, decode_type="json", decode=True
)
return r["dict"]
def package_status(name): def package_status(name):
''' """
Check the installation status of a package on the REST server Check the installation status of a package on the REST server
''' """
r = salt.utils.http.query(DETAILS['url']+'package/status/'+name, decode_type='json', decode=True) r = salt.utils.http.query(
return r['dict'] DETAILS["url"] + "package/status/" + name, decode_type="json", decode=True
)
return r["dict"]
def ping(): def ping():
''' """
Is the REST server up? Is the REST server up?
''' """
r = salt.utils.http.query(DETAILS['url']+'ping', decode_type='json', decode=True) r = salt.utils.http.query(DETAILS["url"] + "ping", decode_type="json", decode=True)
try: try:
return r['dict'].get('ret', False) return r["dict"].get("ret", False)
except Exception: except Exception:
return False return False
def shutdown(opts): def shutdown(opts):
''' """
For this proxy shutdown is a no-op For this proxy shutdown is a no-op
''' """
log.debug('rest_sample proxy shutdown() called...') log.debug("rest_sample proxy shutdown() called...")
.. _grains support code: .. _grains support code:
@ -702,19 +729,23 @@ Example from ``salt/grains/rest_sample.py``:
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
Generate baseline proxy minion grains Generate baseline proxy minion grains
''' """
from __future__ import absolute_import from __future__ import absolute_import
import salt.utils.platform import salt.utils.platform
__proxyenabled__ = ['rest_sample'] __proxyenabled__ = ["rest_sample"]
__virtualname__ = "rest_sample"
__virtualname__ = 'rest_sample'
def __virtual__(): def __virtual__():
try: try:
if salt.utils.platform.is_proxy() and __opts__['proxy']['proxytype'] == 'rest_sample': if (
salt.utils.platform.is_proxy()
and __opts__["proxy"]["proxytype"] == "rest_sample"
):
return __virtualname__ return __virtualname__
except KeyError: except KeyError:
pass pass
@ -746,12 +777,12 @@ This proxymodule enables "package" installation.
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
This is a simple proxy-minion designed to connect to and communicate with This is a simple proxy-minion designed to connect to and communicate with
a server that exposes functionality via SSH. a server that exposes functionality via SSH.
This can be used as an option when the device does not provide This can be used as an option when the device does not provide
an api over HTTP and doesn't have the python stack to run a minion. an api over HTTP and doesn't have the python stack to run a minion.
''' """
from __future__ import absolute_import from __future__ import absolute_import
# Import python libs # Import python libs
@ -763,7 +794,7 @@ This proxymodule enables "package" installation.
from salt.utils.vt import TerminalException from salt.utils.vt import TerminalException
# This must be present or the Salt loader won't load this module # This must be present or the Salt loader won't load this module
__proxyenabled__ = ['ssh_sample'] __proxyenabled__ = ["ssh_sample"]
DETAILS = {} DETAILS = {}
@ -774,25 +805,27 @@ This proxymodule enables "package" installation.
# This does nothing, it's here just as an example and to provide a log # This does nothing, it's here just as an example and to provide a log
# entry when the module is loaded. # entry when the module is loaded.
def __virtual__(): def __virtual__():
''' """
Only return if all the modules are available Only return if all the modules are available
''' """
log.info('ssh_sample proxy __virtual__() called...') log.info("ssh_sample proxy __virtual__() called...")
return True return True
def init(opts): def init(opts):
''' """
Required. Required.
Can be used to initialize the server connection. Can be used to initialize the server connection.
''' """
try: try:
DETAILS['server'] = SSHConnection(host=__opts__['proxy']['host'], DETAILS["server"] = SSHConnection(
username=__opts__['proxy']['username'], host=__opts__["proxy"]["host"],
password=__opts__['proxy']['password']) username=__opts__["proxy"]["username"],
password=__opts__["proxy"]["password"],
)
# connected to the SSH server # connected to the SSH server
out, err = DETAILS['server'].sendline('help') out, err = DETAILS["server"].sendline("help")
except TerminalException as e: except TerminalException as e:
log.error(e) log.error(e)
@ -800,73 +833,73 @@ This proxymodule enables "package" installation.
def shutdown(opts): def shutdown(opts):
''' """
Disconnect Disconnect
''' """
DETAILS['server'].close_connection() DETAILS["server"].close_connection()
def parse(out): def parse(out):
''' """
Extract json from out. Extract json from out.
Parameter Parameter
out: Type string. The data returned by the out: Type string. The data returned by the
ssh command. ssh command.
''' """
jsonret = [] jsonret = []
in_json = False in_json = False
for ln_ in out.split('\n'): for ln_ in out.split("\n"):
if '{' in ln_: if "{" in ln_:
in_json = True in_json = True
if in_json: if in_json:
jsonret.append(ln_) jsonret.append(ln_)
if '}' in ln_: if "}" in ln_:
in_json = False in_json = False
return salt.utils.json.loads('\n'.join(jsonret)) return salt.utils.json.loads("\n".join(jsonret))
def package_list(): def package_list():
''' """
List "packages" by executing a command via ssh List "packages" by executing a command via ssh
This function is called in response to the salt command This function is called in response to the salt command
..code-block::bash ..code-block::bash
salt target_minion pkg.list_pkgs salt target_minion pkg.list_pkgs
''' """
# Send the command to execute # Send the command to execute
out, err = DETAILS['server'].sendline('pkg_list') out, err = DETAILS["server"].sendline("pkg_list")
# "scrape" the output and return the right fields as a dict # "scrape" the output and return the right fields as a dict
return parse(out) return parse(out)
def package_install(name, **kwargs): def package_install(name, **kwargs):
''' """
Install a "package" on the REST server Install a "package" on the REST server
''' """
cmd = 'pkg_install ' + name cmd = "pkg_install " + name
if 'version' in kwargs: if "version" in kwargs:
cmd += '/'+kwargs['version'] cmd += "/" + kwargs["version"]
else: else:
cmd += '/1.0' cmd += "/1.0"
# Send the command to execute # Send the command to execute
out, err = DETAILS['server'].sendline(cmd) out, err = DETAILS["server"].sendline(cmd)
# "scrape" the output and return the right fields as a dict # "scrape" the output and return the right fields as a dict
return parse(out) return parse(out)
def package_remove(name): def package_remove(name):
''' """
Remove a "package" on the REST server Remove a "package" on the REST server
''' """
cmd = 'pkg_remove ' + name cmd = "pkg_remove " + name
# Send the command to execute # Send the command to execute
out, err = DETAILS['server'].sendline(cmd) out, err = DETAILS["server"].sendline(cmd)
# "scrape" the output and return the right fields as a dict # "scrape" the output and return the right fields as a dict
return parse(out) return parse(out)

View file

@ -110,12 +110,12 @@ write Salt States in pure Python:
#!py #!py
def run(): def run():
''' """
Install the python-mako package Install the python-mako package
''' """
return {'include': ['python'], return {"include": ["python"], "python-mako": {"pkg": ["installed"]}}
'python-mako': {'pkg': ['installed']}}
This renderer is used by making a run function that returns the Highstate data This renderer is used by making a run function that returns the Highstate data
structure. Any capabilities of Python can be used in pure Python sls modules. structure. Any capabilities of Python can be used in pure Python sls modules.

View file

@ -162,8 +162,8 @@ This addition to the LocalClient api can be used like so:
import salt.client import salt.client
client = salt.client.LocalClient('/etc/salt/master') client = salt.client.LocalClient("/etc/salt/master")
ret = client.cmd('*', 'cmd.run', ['ls -l'], kwarg={'cwd': '/etc'}) ret = client.cmd("*", "cmd.run", ["ls -l"], kwarg={"cwd": "/etc"})
This update has been added to all cmd methods in the LocalClient class. This update has been added to all cmd methods in the LocalClient class.

View file

@ -378,10 +378,7 @@ eAuth Changes
.. code-block:: python .. code-block:: python
[{'web*': ['test.*', [{"web*": ["test.*", "network.*"]}, "@wheel", "@runner"]
'network.*']},
'@wheel',
'@runner']
- External auth is supported by :ref:`salt-run <salt-run>` and - External auth is supported by :ref:`salt-run <salt-run>` and
:ref:`salt-key <salt-key>` now. Note that master must be started to :ref:`salt-key <salt-key>` now. Note that master must be started to
@ -554,13 +551,15 @@ General Deprecations
.. code-block:: python .. code-block:: python
def fcn(msg='', env='base', refresh=True, saltenv='base', **kwargs): def fcn(msg="", env="base", refresh=True, saltenv="base", **kwargs):
...
has been changed to has been changed to
.. code-block:: python .. code-block:: python
def fcn(msg='', refresh=True, saltenv='base', **kwargs): def fcn(msg="", refresh=True, saltenv="base", **kwargs):
...
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function - If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
that also accepts arbitrary keyword arguments, then a new warning informs the that also accepts arbitrary keyword arguments, then a new warning informs the
@ -569,12 +568,13 @@ General Deprecations
.. code-block:: python .. code-block:: python
def fcn(msg='', refresh=True, saltenv='base', **kwargs): def fcn(msg="", refresh=True, saltenv="base", **kwargs):
...
.. code-block:: python .. code-block:: python
# will result in a warning log message # will result in a warning log message
fcn(msg='add more salt', env='prod', refresh=False) fcn(msg="add more salt", env="prod", refresh=False)
- If ``env`` (or ``__env__``) is supplied as a keyword argument to a function - If ``env`` (or ``__env__``) is supplied as a keyword argument to a function
that does not accept arbitrary keyword arguments, then python will issue an that does not accept arbitrary keyword arguments, then python will issue an
@ -582,12 +582,13 @@ General Deprecations
.. code-block:: python .. code-block:: python
def fcn(msg='', refresh=True, saltenv='base'): def fcn(msg="", refresh=True, saltenv="base"):
...
.. code-block:: python .. code-block:: python
# will result in a python TypeError # will result in a python TypeError
fcn(msg='add more salt', env='prod', refresh=False) fcn(msg="add more salt", env="prod", refresh=False)
- If ``env`` (or ``__env__``) is supplied as a positional argument to a - If ``env`` (or ``__env__``) is supplied as a positional argument to a
function, then undefined behavior will occur, as the removal of ``env`` and function, then undefined behavior will occur, as the removal of ``env`` and
@ -596,12 +597,13 @@ General Deprecations
.. code-block:: python .. code-block:: python
def fcn(msg='', refresh=True, saltenv='base'): def fcn(msg="", refresh=True, saltenv="base"):
...
.. code-block:: python .. code-block:: python
# will result in refresh evaluating to True and saltenv likely not being a string at all # will result in refresh evaluating to True and saltenv likely not being a string at all
fcn('add more salt', 'prod', False) fcn("add more salt", "prod", False)
- Deprecations in ``minion.py``: - Deprecations in ``minion.py``:

View file

@ -156,12 +156,11 @@ they are being loaded for the correct proxytype, example below:
.. code-block:: python .. code-block:: python
def __virtual__(): def __virtual__():
''' """
Only work on proxy Only work on proxy
''' """
try: try:
if salt.utils.is_proxy() and \ if salt.utils.is_proxy() and __opts__["proxy"]["proxytype"] == "ssh_sample":
__opts__['proxy']['proxytype'] == 'ssh_sample':
return __virtualname__ return __virtualname__
except KeyError: except KeyError:
pass pass
@ -185,10 +184,10 @@ to ``__proxy__``. This enables patterns like
.. code-block:: python .. code-block:: python
def get_ip(proxy): def get_ip(proxy):
''' """
Ask the remote device what IP it has Ask the remote device what IP it has
''' """
return {'ip':proxy['proxymodulename.get_ip']()} return {"ip": proxy["proxymodulename.get_ip"]()}
Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function Then the grain ``ip`` will contain the result of calling the ``get_ip()`` function

View file

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

View file

@ -216,10 +216,10 @@ they failed. Here's some example pseudocode:
def myrunner(): def myrunner():
... ...
do stuff # do stuff
... ...
if some_error_condition: if some_error_condition:
__context__['retcode'] = 1 __context__["retcode"] = 1
return result return result
Variable Update Intervals for Fileserver Backends Variable Update Intervals for Fileserver Backends

View file

@ -29,7 +29,7 @@ occurred:
.. code-block:: python .. code-block:: python
if something_went_wrong: if something_went_wrong:
__context__['retcode'] = 42 __context__["retcode"] = 42
This is actually how states signal that they have failed. Different cases This is actually how states signal that they have failed. Different cases
result in different codes being set in the :ref:`__context__ <dunder-context>` result in different codes being set in the :ref:`__context__ <dunder-context>`

View file

@ -140,7 +140,7 @@ something like:
.. code-block:: python .. code-block:: python
mykey = __salt__['config.get']('mykey') mykey = __salt__["config.get"]("mykey")
Templating renderers use a similar construct. To get the ``mykey`` value from Templating renderers use a similar construct. To get the ``mykey`` value from
above in Jinja, you would use: above in Jinja, you would use:
@ -175,7 +175,7 @@ in the module as well:
.. code-block:: python .. code-block:: python
__func_alias__ = { __func_alias__ = {
'set_': 'set', "set_": "set",
} }
This is because ``set`` is a Python built-in, and therefore functions should not This is because ``set`` is a Python built-in, and therefore functions should not

View file

@ -41,7 +41,7 @@ connection object, then that connection object is returned. For instance, the
.. code-block:: python .. code-block:: python
conn = sqlite3.connect(__opts__['spm_db'], isolation_level=None) conn = sqlite3.connect(__opts__["spm_db"], isolation_level=None)
... ...
return conn return conn
@ -259,7 +259,7 @@ This function will not generally be more complex than:
.. code-block:: python .. code-block:: python
def hash_file(path, hashobj, conn=None): def hash_file(path, hashobj, conn=None):
with salt.utils.files.fopen(path, 'r') as f: with salt.utils.files.fopen(path, "r") as f:
hashobj.update(f.read()) hashobj.update(f.read())
return hashobj.hexdigest() return hashobj.hexdigest()

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. and therefore anything parsed as YAML in Salt is subject to them.
.. _`YAML Spec`: https://yaml.org/spec/1.2/spec.html#id2792424 .. _`YAML Spec`: https://yaml.org/spec/1.2/spec.html#id2792424
.. __: https://github.com/yaml/pyyaml/blob/eb459f8/lib/yaml/scanner.py#L279-L293 .. __: https://github.com/yaml/pyyaml/blob/eb459f8/lib/yaml/scanner.py#L279-L293

View file

@ -64,14 +64,14 @@ single URL:
.. code-block:: python .. code-block:: python
salt.utils.http.query('http://example.com') salt.utils.http.query("http://example.com")
By default the query will be performed with a ``GET`` method. The method can By default the query will be performed with a ``GET`` method. The method can
be overridden with the ``method`` argument: be overridden with the ``method`` argument:
.. code-block:: python .. code-block:: python
salt.utils.http.query('http://example.com/delete/url', 'DELETE') salt.utils.http.query("http://example.com/delete/url", "DELETE")
When using the ``POST`` method (and others, such as ``PUT``), extra data is usually When using the ``POST`` method (and others, such as ``PUT``), extra data is usually
sent as well. This data can be sent directly (would be URL encoded when necessary), sent as well. This data can be sent directly (would be URL encoded when necessary),
@ -80,9 +80,7 @@ or in whatever format is required by the remote server (XML, JSON, plain text, e
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url", method="POST", data=json.dumps(mydict)
method='POST',
data=json.dumps(mydict)
) )
Data Formatting and Templating Data Formatting and Templating
@ -96,9 +94,7 @@ the file (untemplated):
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url", method="POST", data_file="/srv/salt/somefile.xml"
method='POST',
data_file='/srv/salt/somefile.xml'
) )
To pass through a file that contains jinja + yaml templating (the default): To pass through a file that contains jinja + yaml templating (the default):
@ -106,11 +102,11 @@ To pass through a file that contains jinja + yaml templating (the default):
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url",
method='POST', method="POST",
data_file='/srv/salt/somefile.jinja', data_file="/srv/salt/somefile.jinja",
data_render=True, data_render=True,
template_dict={'key1': 'value1', 'key2': 'value2'} template_dict={"key1": "value1", "key2": "value2"},
) )
To pass through a file that contains mako templating: To pass through a file that contains mako templating:
@ -118,12 +114,12 @@ To pass through a file that contains mako templating:
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url",
method='POST', method="POST",
data_file='/srv/salt/somefile.mako', data_file="/srv/salt/somefile.mako",
data_render=True, data_render=True,
data_renderer='mako', data_renderer="mako",
template_dict={'key1': 'value1', 'key2': 'value2'} template_dict={"key1": "value1", "key2": "value2"},
) )
Because this function uses Salt's own rendering system, any Salt renderer can Because this function uses Salt's own rendering system, any Salt renderer can
@ -136,21 +132,21 @@ However, this can be changed to ``master`` if necessary.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url",
method='POST', method="POST",
data_file='/srv/salt/somefile.jinja', data_file="/srv/salt/somefile.jinja",
data_render=True, data_render=True,
template_dict={'key1': 'value1', 'key2': 'value2'}, template_dict={"key1": "value1", "key2": "value2"},
opts=__opts__ opts=__opts__,
) )
salt.utils.http.query( salt.utils.http.query(
'http://example.com/post/url', "http://example.com/post/url",
method='POST', method="POST",
data_file='/srv/salt/somefile.jinja', data_file="/srv/salt/somefile.jinja",
data_render=True, data_render=True,
template_dict={'key1': 'value1', 'key2': 'value2'}, template_dict={"key1": "value1", "key2": "value2"},
node='master' node="master",
) )
Headers Headers
@ -165,12 +161,12 @@ a Python dict.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com/delete/url', "http://example.com/delete/url",
method='POST', method="POST",
header_file='/srv/salt/headers.jinja', header_file="/srv/salt/headers.jinja",
header_render=True, header_render=True,
header_renderer='jinja', header_renderer="jinja",
template_dict={'key1': 'value1', 'key2': 'value2'} template_dict={"key1": "value1", "key2": "value2"},
) )
Because much of the data that would be templated between headers and data may be Because much of the data that would be templated between headers and data may be
@ -186,9 +182,7 @@ password may be passed in as ``username`` and ``password``, respectively.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com', "http://example.com", username="larry", password="5700g3543v4r",
username='larry',
password='5700g3543v4r',
) )
Cookies and Sessions Cookies and Sessions
@ -199,10 +193,7 @@ are turned off by default. To turn cookies on, set ``cookies`` to True.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query("http://example.com", cookies=True)
'http://example.com',
cookies=True
)
By default cookies are stored in Salt's cache directory, normally By default cookies are stored in Salt's cache directory, normally
``/var/cache/salt``, as a file called ``cookies.txt``. However, this location ``/var/cache/salt``, as a file called ``cookies.txt``. However, this location
@ -211,9 +202,7 @@ may be changed with the ``cookie_jar`` argument:
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com', "http://example.com", cookies=True, cookie_jar="/path/to/cookie_jar.txt"
cookies=True,
cookie_jar='/path/to/cookie_jar.txt'
) )
By default, the format of the cookie jar is LWP (aka, lib-www-perl). This By default, the format of the cookie jar is LWP (aka, lib-www-perl). This
@ -223,10 +212,10 @@ format of the cookie jar can be set to Mozilla:
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com', "http://example.com",
cookies=True, cookies=True,
cookie_jar='/path/to/cookie_jar.txt', cookie_jar="/path/to/cookie_jar.txt",
cookie_format='mozilla' cookie_format="mozilla",
) )
Because Salt commands are normally one-off commands that are piped together, Because Salt commands are normally one-off commands that are piped together,
@ -238,9 +227,7 @@ Salt's cache directory, is ``cookies.session.p``. This can also be changed.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com', "http://example.com", persist_session=True, session_cookie_jar="/path/to/jar.p"
persist_session=True,
session_cookie_jar='/path/to/jar.p'
) )
The format of this file is msgpack, which is consistent with much of the rest The format of this file is msgpack, which is consistent with much of the rest
@ -265,11 +252,7 @@ these are set in the minion configuration file.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query("http://example.com", opts=__opts__, backend="tornado")
'http://example.com',
opts=__opts__,
backend='tornado'
)
Return Data Return Data
~~~~~~~~~~~ ~~~~~~~~~~~
@ -294,10 +277,7 @@ force either JSON or XML decoding, the ``decode_type`` may be set:
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query("http://example.com", decode_type="xml")
'http://example.com',
decode_type='xml'
)
Once translated, the return dict from ``query()`` will include a dict called Once translated, the return dict from ``query()`` will include a dict called
``dict``. ``dict``.
@ -307,10 +287,7 @@ turned off.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query("http://example.com", decode=False)
'http://example.com',
decode=False
)
If decoding is turned on, and references to JSON or XML cannot be found, then If decoding is turned on, and references to JSON or XML cannot be found, then
this module will default to plain text, and return the undecoded data as this module will default to plain text, and return the undecoded data as
@ -321,12 +298,7 @@ as required. However, each must individually be turned on.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query("http://example.com", status=True, headers=True, text=True)
'http://example.com',
status=True,
headers=True,
text=True
)
The return from these will be found in the return dict as ``status``, The return from these will be found in the return dict as ``status``,
``headers`` and ``text``, respectively. ``headers`` and ``text``, respectively.
@ -341,11 +313,11 @@ to be returned to the user in order to do this.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'http://example.com', "http://example.com",
text=False, text=False,
headers=False, headers=False,
text_out='/path/to/url_download.txt', text_out="/path/to/url_download.txt",
headers_out='/path/to/headers_download.txt', headers_out="/path/to/headers_download.txt",
) )
SSL Verification SSL Verification
@ -356,8 +328,7 @@ debugging purposes, SSL verification can be turned off.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'https://example.com', "https://example.com", verify_ssl=False,
verify_ssl=False,
) )
CA Bundles CA Bundles
@ -373,8 +344,7 @@ using the ``ca_bundle`` variable.
.. code-block:: python .. code-block:: python
salt.utils.http.query( salt.utils.http.query(
'https://example.com', "https://example.com", ca_bundle="/path/to/ca_bundle.pem",
ca_bundle='/path/to/ca_bundle.pem',
) )
Updating CA Bundles Updating CA Bundles
@ -395,8 +365,8 @@ download which is hazardous or does not meet the needs of the user.
.. code-block:: python .. code-block:: python
salt.utils.http.update_ca_bundle( salt.utils.http.update_ca_bundle(
target='/path/to/ca-bundle.crt', target="/path/to/ca-bundle.crt",
source='https://example.com/path/to/ca-bundle.crt', source="https://example.com/path/to/ca-bundle.crt",
opts=__opts__, opts=__opts__,
) )
@ -423,10 +393,10 @@ otherwise reasonable to add to the bundle file.
salt.utils.http.update_ca_bundle( salt.utils.http.update_ca_bundle(
opts=__opts__, opts=__opts__,
merge_files=[ merge_files=[
'/etc/ssl/private_cert_1.pem', "/etc/ssl/private_cert_1.pem",
'/etc/ssl/private_cert_2.pem', "/etc/ssl/private_cert_2.pem",
'/etc/ssl/private_cert_3.pem', "/etc/ssl/private_cert_3.pem",
] ],
) )

View file

@ -100,31 +100,33 @@ Note how the module encapsulates all of the logic around finding the storage ser
# _modules/storage.py # _modules/storage.py
#!python #!python
''' """
Functions related to storage servers. Functions related to storage servers.
''' """
import re import re
def ips(): def ips():
''' """
Provide a list of all local storage server IPs. Provide a list of all local storage server IPs.
CLI Example:: CLI Example::
salt \* storage.ips salt \* storage.ips
''' """
if __grains__.get('virtual', None) in ['VirtualBox', 'oracle']: if __grains__.get("virtual", None) in ["VirtualBox", "oracle"]:
return ['192.168.33.51', ] return [
"192.168.33.51",
]
colo = __pillar__.get('inventory', {}).get('colo', 'Unknown') colo = __pillar__.get("inventory", {}).get("colo", "Unknown")
return __pillar__.get('storage_servers', {}).get(colo, ['unknown', ]) return __pillar__.get("storage_servers", {}).get(colo, ["unknown",])
def ip(): def ip():
''' """
Select and return a local storage server IP. Select and return a local storage server IP.
This loadbalances across storage servers by using the modulus of the client's id number. This loadbalances across storage servers by using the modulus of the client's id number.
@ -138,12 +140,12 @@ Note how the module encapsulates all of the logic around finding the storage ser
salt \* storage.ip salt \* storage.ip
''' """
numerical_suffix = re.compile(r'^.*(\d+)$') numerical_suffix = re.compile(r"^.*(\d+)$")
servers_list = ips() servers_list = ips()
m = numerical_suffix.match(__grains__['id']) m = numerical_suffix.match(__grains__["id"])
if m: if m:
modulus = len(servers_list) modulus = len(servers_list)
server_number = int(m.group(1)) server_number = int(m.group(1))

View file

@ -11,7 +11,7 @@ The salt loader was enhanced to look for external modules by looking at the
`salt.loader` entry-point: `salt.loader` entry-point:
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
`pkg_resources` should be installed, which is normally included in setuptools. `pkg_resources` should be installed, which is normally included in setuptools.
https://setuptools.readthedocs.io/en/latest/pkg_resources.html https://setuptools.readthedocs.io/en/latest/pkg_resources.html
@ -24,21 +24,23 @@ function:
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup(name=<NAME>, setup(
version=<VERSION>, name=<NAME>,
description=<DESC>, version=<VERSION>,
author=<AUTHOR>, description=<DESC>,
author_email=<AUTHOR-EMAIL>, author=<AUTHOR>,
url=' ... ', author_email=<AUTHOR-EMAIL>,
packages=find_packages(), url=" ... ",
entry_points=''' packages=find_packages(),
[salt.loader] entry_points="""
engines_dirs = <package>.<loader-module>:engines_dirs [salt.loader]
fileserver_dirs = <package>.<loader-module>:fileserver_dirs engines_dirs = <package>.<loader-module>:engines_dirs
pillar_dirs = <package>.<loader-module>:pillar_dirs fileserver_dirs = <package>.<loader-module>:fileserver_dirs
returner_dirs = <package>.<loader-module>:returner_dirs pillar_dirs = <package>.<loader-module>:pillar_dirs
roster_dirs = <package>.<loader-module>:roster_dirs returner_dirs = <package>.<loader-module>:returner_dirs
''') roster_dirs = <package>.<loader-module>:roster_dirs
"""
)
The above setup script example mentions a loader module. here's an example of The above setup script example mentions a loader module. here's an example of

View file

@ -37,22 +37,24 @@ Edit it to include needed variables and your new paths
.. code-block:: python .. code-block:: python
# you need to edit this # you need to edit this
ROOT_DIR = *your current dir* + '/salt/root' _your_current_dir_ = ...
ROOT_DIR = _your_current_dir_ + "/salt/root"
# you need to edit this # you need to edit this
INSTALL_DIR = *location of source code* _location_of_source_code_ = ...
INSTALL_DIR = _location_of_source_code_
CONFIG_DIR = ROOT_DIR + '/etc/salt' CONFIG_DIR = ROOT_DIR + "/etc/salt"
CACHE_DIR = ROOT_DIR + '/var/cache/salt' CACHE_DIR = ROOT_DIR + "/var/cache/salt"
SOCK_DIR = ROOT_DIR + '/var/run/salt' SOCK_DIR = ROOT_DIR + "/var/run/salt"
SRV_ROOT_DIR= ROOT_DIR + '/srv' SRV_ROOT_DIR = ROOT_DIR + "/srv"
BASE_FILE_ROOTS_DIR = ROOT_DIR + '/srv/salt' BASE_FILE_ROOTS_DIR = ROOT_DIR + "/srv/salt"
BASE_PILLAR_ROOTS_DIR = ROOT_DIR + '/srv/pillar' BASE_PILLAR_ROOTS_DIR = ROOT_DIR + "/srv/pillar"
BASE_MASTER_ROOTS_DIR = ROOT_DIR + '/srv/salt-master' BASE_MASTER_ROOTS_DIR = ROOT_DIR + "/srv/salt-master"
LOGS_DIR = ROOT_DIR + '/var/log/salt' LOGS_DIR = ROOT_DIR + "/var/log/salt"
PIDFILE_DIR = ROOT_DIR + '/var/run' PIDFILE_DIR = ROOT_DIR + "/var/run"
CLOUD_DIR = INSTALL_DIR + '/cloud' CLOUD_DIR = INSTALL_DIR + "/cloud"
BOOTSTRAP = CLOUD_DIR + '/deploy/bootstrap-salt.sh' BOOTSTRAP = CLOUD_DIR + "/deploy/bootstrap-salt.sh"
Create the directory structure Create the directory structure

View file

@ -484,12 +484,12 @@ This example shows a very basic Python SLS file:
#!py #!py
def run(): def run():
''' """
Install the django package Install the django package
''' """
return {'include': ['python'], return {"include": ["python"], "django": {"pkg": ["installed"]}}
'django': {'pkg': ['installed']}}
This is a very simple example; the first line has an SLS shebang that This is a very simple example; the first line has an SLS shebang that
tells Salt to not use the default renderer, but to use the ``py`` renderer. tells Salt to not use the default renderer, but to use the ``py`` renderer.
@ -504,8 +504,8 @@ renderer, the above example can be written more succinctly as:
#!pydsl #!pydsl
include('python', delayed=True) include("python", delayed=True)
state('django').pkg.installed() state("django").pkg.installed()
The :mod:`pyobjects<salt.renderers.pyobjects>` renderer The :mod:`pyobjects<salt.renderers.pyobjects>` renderer
provides an `"Pythonic"`_ object based approach for building the state data. provides an `"Pythonic"`_ object based approach for building the state data.
@ -515,7 +515,7 @@ The above example could be written as:
#!pyobjects #!pyobjects
include('python') include("python")
Pkg.installed("django") Pkg.installed("django")

View file

@ -146,7 +146,8 @@ a value equivalent to the following python pseudo-code:
.. code-block:: python .. code-block:: python
import salt.modules.file import salt.modules.file
file.group_to_gid('some_group_that_exists')
file.group_to_gid("some_group_that_exists")
Note that for the above example to work, ``some_group_that_exists`` must exist Note that for the above example to work, ``some_group_that_exists`` must exist
before the state file is processed by the templating engine. before the state file is processed by the templating engine.
@ -156,7 +157,7 @@ MAC address for eth0:
.. code-block:: python .. code-block:: python
salt['network.hw_addr']('eth0') salt["network.hw_addr"]("eth0")
To examine the possible arguments to each execution module function, To examine the possible arguments to each execution module function,
one can examine the `module reference documentation </ref/modules/all>`_: one can examine the `module reference documentation </ref/modules/all>`_:

View file

@ -139,10 +139,10 @@ file:
.. code-block:: python .. code-block:: python
def test_ping(self): def test_ping(self):
''' """
test.ping test.ping
''' """
self.assertTrue(self.run_function('test.ping')) self.assertTrue(self.run_function("test.ping"))
The test above is a very simple example where the ``test.ping`` function is The test above is a very simple example where the ``test.ping`` function is
executed by Salt's test suite runner and is asserting that the minion returned executed by Salt's test suite runner and is asserting that the minion returned
@ -254,20 +254,20 @@ minion's return is expected.
.. code-block:: python .. code-block:: python
def test_ping(self): def test_ping(self):
''' """
test.ping test.ping
''' """
self.assertTrue(self.run_function('test.ping')) self.assertTrue(self.run_function("test.ping"))
Args can be passed in to the ``run_function`` method as well: Args can be passed in to the ``run_function`` method as well:
.. code-block:: python .. code-block:: python
def test_echo(self): def test_echo(self):
''' """
test.echo test.echo
''' """
self.assertEqual(self.run_function('test.echo', ['text']), 'text') self.assertEqual(self.run_function("test.echo", ["text"]), "text")
The next example is taken from the The next example is taken from the
``tests/integration/modules/test_aliases.py`` file and ``tests/integration/modules/test_aliases.py`` file and
@ -279,18 +279,13 @@ call should return.
.. code-block:: python .. code-block:: python
def test_set_target(self): def test_set_target(self):
''' """
aliases.set_target and aliases.get_target aliases.set_target and aliases.get_target
''' """
set_ret = self.run_function( set_ret = self.run_function("aliases.set_target", alias="fred", target="bob")
'aliases.set_target',
alias='fred',
target='bob')
self.assertTrue(set_ret) self.assertTrue(set_ret)
tgt_ret = self.run_function( tgt_ret = self.run_function("aliases.get_target", alias="fred")
'aliases.get_target', self.assertEqual(tgt_ret, "bob")
alias='fred')
self.assertEqual(tgt_ret, 'bob')
Using multiple Salt commands in this manner provides two useful benefits. The first is Using multiple Salt commands in this manner provides two useful benefits. The first is
that it provides some additional coverage for the ``aliases.set_target`` function. that it provides some additional coverage for the ``aliases.set_target`` function.
@ -335,12 +330,13 @@ the test method:
import integration import integration
from tests.support.helpers import destructiveTest from tests.support.helpers import destructiveTest
class PkgTest(integration.ModuleCase): class PkgTest(integration.ModuleCase):
@destructiveTest @destructiveTest
def test_pkg_install(self): def test_pkg_install(self):
ret = self.run_function('pkg.install', name='finch') ret = self.run_function("pkg.install", name="finch")
self.assertSaltTrueReturn(ret) self.assertSaltTrueReturn(ret)
ret = self.run_function('pkg.purge', name='finch') ret = self.run_function("pkg.purge", name="finch")
self.assertSaltTrueReturn(ret) self.assertSaltTrueReturn(ret)
@ -372,13 +368,13 @@ testing the call to ``cp.hash_file``, which is used in ``cp.get_file``.
.. code-block:: python .. code-block:: python
def test_get_file_not_found(self): def test_get_file_not_found(self):
''' """
Test if get_file can't find the file. Test if get_file can't find the file.
''' """
with patch('salt.modules.cp.hash_file', MagicMock(return_value=False)): with patch("salt.modules.cp.hash_file", MagicMock(return_value=False)):
path = 'salt://saltines' path = "salt://saltines"
dest = '/srv/salt/cheese' dest = "/srv/salt/cheese"
ret = '' ret = ""
assert cp.get_file(path, dest) == ret assert cp.get_file(path, dest) == ret
Note that Salt's ``cp`` module is imported at the top of the file, along with all Note that Salt's ``cp`` module is imported at the top of the file, along with all
@ -446,10 +442,10 @@ can be used
# .. inside test # .. inside test
with TstSuiteLoggingHandler() as handler: with TstSuiteLoggingHandler() as handler:
for message in handler.messages: for message in handler.messages:
if message.startswith('ERROR: This is the error message we seek'): if message.startswith("ERROR: This is the error message we seek"):
break break
else: else:
raise AssertionError('Did not find error message') raise AssertionError("Did not find error message")
Automated Test Runs Automated Test Runs

View file

@ -23,15 +23,16 @@ For example, assuming the following simple utility module, saved to
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
My utils module My utils module
--------------- ---------------
This module contains common functions for use in my other custom types. This module contains common functions for use in my other custom types.
''' """
def bar(): def bar():
return 'baz' return "baz"
Once synced to a minion, this function would be available to other custom Salt Once synced to a minion, this function would be available to other custom Salt
types like so: types like so:
@ -39,13 +40,14 @@ types like so:
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
My awesome execution module My awesome execution module
--------------------------- ---------------------------
''' """
def observe_the_awesomeness(): def observe_the_awesomeness():
''' """
Prints information from my utility module Prints information from my utility module
CLI Example: CLI Example:
@ -53,8 +55,8 @@ types like so:
.. code-block:: bash .. code-block:: bash
salt '*' mymodule.observe_the_awesomeness salt '*' mymodule.observe_the_awesomeness
''' """
return __utils__['foo.bar']() return __utils__["foo.bar"]()
Utility modules, like any other kind of Salt extension, support using a Utility modules, like any other kind of Salt extension, support using a
:ref:`__virtual__ function <modules-virtual-name>` to conditionally load them, :ref:`__virtual__ function <modules-virtual-name>` to conditionally load them,
@ -65,21 +67,23 @@ the ``foo`` utility module with a ``__virtual__`` function.
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
My utils module My utils module
--------------- ---------------
This module contains common functions for use in my other custom types. This module contains common functions for use in my other custom types.
''' """
def __virtual__(): def __virtual__():
''' """
Load as a different name Load as a different name
''' """
return 'foo' return "foo"
def bar(): def bar():
return 'baz' return "baz"
.. versionadded:: 2018.3.0 .. versionadded:: 2018.3.0
Instantiating objects from classes declared in util modules works with Instantiating objects from classes declared in util modules works with
@ -90,35 +94,36 @@ Also you could even write your utility modules in object oriented fashion:
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
My OOP-style utils module My OOP-style utils module
------------------------- -------------------------
This module contains common functions for use in my other custom types. This module contains common functions for use in my other custom types.
''' """
class Foo(object): class Foo(object):
def __init__(self): def __init__(self):
pass pass
def bar(self): def bar(self):
return 'baz' return "baz"
And import them into other custom modules: And import them into other custom modules:
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
My awesome execution module My awesome execution module
--------------------------- ---------------------------
''' """
import mymodule import mymodule
def observe_the_awesomeness(): def observe_the_awesomeness():
''' """
Prints information from my utility module Prints information from my utility module
CLI Example: CLI Example:
@ -126,7 +131,7 @@ And import them into other custom modules:
.. code-block:: bash .. code-block:: bash
salt '*' mymodule.observe_the_awesomeness salt '*' mymodule.observe_the_awesomeness
''' """
foo = mymodule.Foo() foo = mymodule.Foo()
return foo.bar() return foo.bar()

View file

@ -40,7 +40,7 @@ In Python, the above maps to:
.. code-block:: python .. code-block:: python
{'my_key': 'my_value'} {"my_key": "my_value"}
Alternatively, a value can be associated with a key through indentation. Alternatively, a value can be associated with a key through indentation.
@ -58,7 +58,7 @@ In Python, the above maps to:
.. code-block:: python .. code-block:: python
{'my_key': 'my_value'} {"my_key": "my_value"}
Dictionaries can be nested: Dictionaries can be nested:
@ -71,11 +71,7 @@ And in Python:
.. code-block:: python .. code-block:: python
{ {"first_level_dict_key": {"second_level_dict_key": "value_in_second_level_dict"}}
'first_level_dict_key': {
'second_level_dict_key': 'value_in_second_level_dict'
}
}
Rule Three: Dashes Rule Three: Dashes
------------------ ------------------
@ -102,7 +98,7 @@ In Python, the above maps to:
.. code-block:: python .. code-block:: python
{'my_dictionary': ['list_value_one', 'list_value_two', 'list_value_three']} {"my_dictionary": ["list_value_one", "list_value_two", "list_value_three"]}
Learning More Learning More
------------- -------------