Merge pull request #39736 from techhat/spmstates

Add pre and post states to SPM
This commit is contained in:
Mike Place 2017-03-01 19:57:41 -07:00 committed by GitHub
commit 2d711782e8
3 changed files with 167 additions and 2 deletions

View file

@ -146,6 +146,105 @@ The first 5 of these types (``c``, ``d``, ``g``, ``l``, ``r``) will be placed in
The last two types (``s`` and ``m``) are currently ignored, but they are
reserved for future use.
Pre and Post States
-------------------
It is possible to run Salt states before and after installing a package by
using pre and post states. The following sections may be declared in a
``FORMULA``:
* ``pre_local_state``
* ``pre_tgt_state``
* ``post_local_state``
* ``post_tgt_state``
Sections with ``pre`` in their name are evaluated before a package is installed
and sections with ``post`` are evaluated after a package is installed. ``local``
states are evaluated before ``tgt`` states.
Each of these sections needs to be evaluated as text, rather than as YAML.
Consider the following block:
.. code-block::
pre_local_state: >
echo test > /tmp/spmtest:
cmd:
- run
Note that this declaration uses ``>`` after ``pre_local_state``. This is a YAML
marker that marks the next multi-line block as text, including newlines. It is
important to use this marker whenever declaring ``pre`` or ``post`` states, so
that the text following it can be evaluated properly.
local States
~~~~~~~~~~~~
``local`` states are evaluated locally; this is analagous to issuing a state
run using a ``salt-call --local`` command. These commands will be issued on the
local machine running the ``spm`` command, whether that machine is a master or
a minion.
``local`` states do not require any special arguments, but they must still use
the ``>`` marker to denote that the state is evaluated as text, not a data
structure.
.. code-block::
pre_local_state: >
echo test > /tmp/spmtest:
cmd:
- run
tgt States
~~~~~~~~~~
``tgt`` states are issued against a remote target. This is analogous to issuing
a state using the ``salt`` command. As such it requires that the machine that
the ``spm`` command is running on is a master.
Because ``tgt`` states require that a target be specified, their code blocks
are a little different. Consider the following state:
.. code-block::
pre_tgt_state:
tgt: '*'
data: >
echo test > /tmp/spmtest:
cmd:
- run
With ``tgt`` states, the state data is placed under a ``data`` section, inside
the ``*_tgt_state`` code block. The target is of course specified as a ``tgt``
and you may also optionally specify a ``tgt_type`` (the default is ``glob``).
You still need to use the ``>`` marker, but this time it follows the ``data``
line, rather than the ``*_tgt_state`` line.
Templating States
~~~~~~~~~~~~~~~~~
The reason that state data must be evaluated as text rather than a data
structure is because that state data is first processed through the rendering
engine, as it would be with a standard state run.
This means that you can use Jinja or any other supported renderer inside of
Salt. All formula variables are available to the renderer, so you can reference
``FORMULA`` data inside your state if you need to:
.. code-block::
pre_tgt_state:
tgt: '*'
data: >
echo {{ name }} > /tmp/spmtest:
cmd:
- run
You may also declare your own variables inside the ``FORMULA``. If SPM doesn't
recognize them then it will ignore them, so there are no restrictions on
variable names, outside of avoiding reserved words.
By default the renderer is set to ``yaml_jinja``. You may change this by
changing the ``renderer`` setting in the ``FORMULA`` itself.
Building a Package
------------------
Once a ``FORMULA`` file has been created, it is placed into the root of the

View file

@ -20,6 +20,7 @@ import grp
import sys
# Import Salt libs
import salt.client
import salt.config
import salt.loader
import salt.cache
@ -31,6 +32,7 @@ from salt.ext.six import string_types
from salt.ext.six.moves import input
from salt.ext.six.moves import zip
from salt.ext.six.moves import filter
from salt.template import compile_template
# Get logging started
log = logging.getLogger(__name__)
@ -229,6 +231,10 @@ class SPMClient(object):
if len(args) < 2:
raise SPMInvocationError('A package must be specified')
caller_opts = self.opts.copy()
caller_opts['file_client'] = 'local'
self.caller = salt.client.Caller(mopts=caller_opts)
self.client = salt.client.get_local_client(self.opts['conf_file'])
cache = salt.cache.Cache(self.opts)
packages = args[1:]
@ -468,6 +474,22 @@ class SPMClient(object):
# We've decided to install
self._pkgdb_fun('register_pkg', pkg_name, formula_def, self.db_conn)
# Run the pre_local_state script, if present
if 'pre_local_state' in formula_def:
high_data = self._render(formula_def['pre_local_state'], formula_def)
ret = self.caller.cmd('state.high', data=high_data)
if 'pre_tgt_state' in formula_def:
log.debug('Executing pre_tgt_state script')
high_data = self._render(formula_def['pre_tgt_state']['data'], formula_def)
tgt = formula_def['pre_tgt_state']['tgt']
ret = self.client.run_job(
tgt=formula_def['pre_tgt_state']['tgt'],
fun='state.high',
tgt_type=formula_def['pre_tgt_state'].get('tgt_type', 'glob'),
timout=self.opts['timeout'],
data=high_data,
)
# No defaults for this in config.py; default to the current running
# user and group
uid = self.opts.get('spm_uid', os.getuid())
@ -505,6 +527,23 @@ class SPMClient(object):
digest,
self.db_conn)
# Run the post_local_state script, if present
if 'post_local_state' in formula_def:
log.debug('Executing post_local_state script')
high_data = self._render(formula_def['post_local_state'], formula_def)
self.caller.cmd('state.high', data=high_data)
if 'post_tgt_state' in formula_def:
log.debug('Executing post_tgt_state script')
high_data = self._render(formula_def['post_tgt_state']['data'], formula_def)
tgt = formula_def['post_tgt_state']['tgt']
ret = self.client.run_job(
tgt=formula_def['post_tgt_state']['tgt'],
fun='state.high',
tgt_type=formula_def['post_tgt_state'].get('tgt_type', 'glob'),
timout=self.opts['timeout'],
data=high_data,
)
formula_tar.close()
def _resolve_deps(self, formula_def):
@ -998,6 +1037,27 @@ class SPMClient(object):
return None
return member
def _render(self, data, formula_def):
'''
Render a [pre|post]_local_state or [pre|post]_tgt_state script
'''
# FORMULA can contain a renderer option
renderer = formula_def.get('renderer', self.opts.get('renderer', 'yaml_jinja'))
rend = salt.loader.render(self.opts, {})
blacklist = self.opts.get('renderer_blacklist')
whitelist = self.opts.get('renderer_whitelist')
template_vars = formula_def.copy()
template_vars['opts'] = self.opts.copy()
return compile_template(
':string:',
rend,
renderer,
blacklist,
whitelist,
input_data=data,
**template_vars
)
class SPMUserInterface(object):
'''

View file

@ -19,7 +19,9 @@ config = salt.config.minion_config(None)
config['file_roots'] = {'base': [os.path.join(_TMP_SPM, 'salt')]}
config['pillar_roots'] = {'base': [os.path.join(_TMP_SPM, 'pillar')]}
__opts__ = {
__opts__ = salt.config.DEFAULT_MINION_OPTS
__opts__.update({
'spm_logfile': os.path.join(_TMP_SPM, 'log'),
'spm_repos_config': os.path.join(_TMP_SPM, 'etc', 'spm.repos'),
'spm_cache_dir': os.path.join(_TMP_SPM, 'cache'),
@ -37,9 +39,10 @@ __opts__ = {
'force': False,
'verbose': False,
'cache': 'localfs',
'cachedir': os.path.join(_TMP_SPM, 'cache'),
'spm_repo_dups': 'ignore',
'spm_share_dir': os.path.join(_TMP_SPM, 'share'),
}
})
_F1 = {
'definition': {
@ -90,6 +93,9 @@ class SPMTest(TestCase):
os.mkdir(_TMP_SPM)
self.ui = SPMTestUserInterface()
self.client = salt.spm.SPMClient(self.ui, __opts__)
master_cache = salt.config.DEFAULT_MASTER_OPTS['cachedir']
if not os.path.exists(master_cache):
os.makedirs(master_cache)
def tearDown(self):
shutil.rmtree(_TMP_SPM, ignore_errors=True)