mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 08:35:21 +00:00
Merge pull request #54993 from mchugh19/port-52187
master-port 52187 and 52351
This commit is contained in:
commit
6472827ff5
@ -3,3 +3,30 @@
|
||||
==================================
|
||||
Salt Release Notes - Codename Neon
|
||||
==================================
|
||||
|
||||
|
||||
Slot Syntax Updates
|
||||
===================
|
||||
|
||||
The slot syntax has been updated to support parsing dictionary responses and to append text.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
demo dict parsing and append:
|
||||
test.configurable_test_state:
|
||||
- name: slot example
|
||||
- changes: False
|
||||
- comment: __slot__:salt:test.arg(shell="/bin/bash").kwargs.shell ~ /appended
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
local:
|
||||
----------
|
||||
ID: demo dict parsing and append
|
||||
Function: test.configurable_test_state
|
||||
Name: slot example
|
||||
Result: True
|
||||
Comment: /bin/bash/appended
|
||||
Started: 09:59:58.623575
|
||||
Duration: 1.229 ms
|
||||
Changes:
|
||||
|
@ -5,6 +5,7 @@ Slots
|
||||
=====
|
||||
|
||||
.. versionadded:: 2018.3.0
|
||||
.. versionchanged:: Neon
|
||||
|
||||
.. note:: This functionality is under development and could be changed in the
|
||||
future releases
|
||||
@ -33,7 +34,14 @@ Slot syntax looks close to the simple python function call.
|
||||
|
||||
__slot__:salt:<module>.<function>(<args>, ..., <kwargs...>, ...)
|
||||
|
||||
Also there are some specifics in the syntax coming from the execution functions
|
||||
For the Neon release, this syntax has been updated to support parsing functions
|
||||
which return dictionaries and for appending text to the slot result.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
__slot__:salt:<module>.<function>(<args>..., <kwargs...>, ...).dictionary ~ append
|
||||
|
||||
There are some specifics in the syntax coming from the execution functions
|
||||
nature and a desire to simplify the user experience. First one is that you
|
||||
don't need to quote the strings passed to the slots functions. The second one
|
||||
is that all arguments handled as strings.
|
||||
@ -51,3 +59,12 @@ This will execute the :py:func:`test.echo <salt.modules.test.echo>` execution
|
||||
functions right before calling the state. The functions in the example will
|
||||
return `/tmp/some_file` and `/etc/hosts` strings that will be used as a target
|
||||
and source arguments in the state function `file.copy`.
|
||||
|
||||
Here is an example of result parsing and appending:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
file-in-user-home:
|
||||
file.copy:
|
||||
- name: __slot__:salt:user.info(someuser).home ~ /subdirectory
|
||||
- source: salt://somefile
|
||||
|
@ -2054,24 +2054,96 @@ class State(object):
|
||||
'test.arg(\'arg\', kw=\'kwarg\')')
|
||||
return slot
|
||||
log.debug('Calling slot: %s(%s, %s)', fun, args, kwargs)
|
||||
return self.functions[fun](*args, **kwargs)
|
||||
slot_return = self.functions[fun](*args, **kwargs)
|
||||
|
||||
# Given input __slot__:salt:test.arg(somekey="value").not.exist ~ /appended
|
||||
# slot_text should be __slot...).not.exist
|
||||
# append_data should be ~ /appended
|
||||
slot_text = fmt[2].split('~')[0]
|
||||
append_data = fmt[2].split('~', 1)[1:]
|
||||
log.debug('slot_text: %s', slot_text)
|
||||
log.debug('append_data: %s', append_data)
|
||||
|
||||
# Support parsing slot dict response
|
||||
# return_get should result in a kwargs.nested.dict path by getting
|
||||
# everything after first closing paren: )
|
||||
return_get = None
|
||||
try:
|
||||
return_get = slot_text[slot_text.rindex(')')+1:]
|
||||
except ValueError:
|
||||
pass
|
||||
if return_get:
|
||||
#remove first period
|
||||
return_get = return_get.split('.', 1)[1].strip()
|
||||
log.debug('Searching slot result %s for %s', slot_return, return_get)
|
||||
slot_return = salt.utils.data.traverse_dict_and_list(slot_return,
|
||||
return_get,
|
||||
default=None,
|
||||
delimiter='.'
|
||||
)
|
||||
|
||||
if append_data:
|
||||
if isinstance(slot_return, six.string_types):
|
||||
# Append text to slot string result
|
||||
append_data = ' '.join(append_data).strip()
|
||||
log.debug('appending to slot result: %s', append_data)
|
||||
slot_return += append_data
|
||||
else:
|
||||
log.error('Ignoring slot append, slot result is not a string')
|
||||
|
||||
return slot_return
|
||||
|
||||
def format_slots(self, cdata):
|
||||
'''
|
||||
Read in the arguments from the low level slot syntax to make a last
|
||||
minute runtime call to gather relevant data for the specific routine
|
||||
|
||||
Will parse strings, first level of dictionary values, and strings and
|
||||
first level dict values inside of lists
|
||||
'''
|
||||
# __slot__:salt.cmd.run(foo, bar, baz=qux)
|
||||
SLOT_TEXT = '__slot__:'
|
||||
ctx = (('args', enumerate(cdata['args'])),
|
||||
('kwargs', cdata['kwargs'].items()))
|
||||
for atype, avalues in ctx:
|
||||
for ind, arg in avalues:
|
||||
arg = salt.utils.data.decode(arg, keep=True)
|
||||
if not isinstance(arg, six.text_type) \
|
||||
or not arg.startswith('__slot__:'):
|
||||
if isinstance(arg, dict):
|
||||
# Search dictionary values for __slot__:
|
||||
for key, value in arg.items():
|
||||
try:
|
||||
if value.startswith(SLOT_TEXT):
|
||||
log.trace("Slot processsing dict value %s", value)
|
||||
cdata[atype][ind][key] = self.__eval_slot(value)
|
||||
except AttributeError:
|
||||
# Not a string/slot
|
||||
continue
|
||||
elif isinstance(arg, list):
|
||||
for idx, listvalue in enumerate(arg):
|
||||
log.trace("Slot processing list value: %s", listvalue)
|
||||
if isinstance(listvalue, dict):
|
||||
# Search dict values in list for __slot__:
|
||||
for key, value in listvalue.items():
|
||||
try:
|
||||
if value.startswith(SLOT_TEXT):
|
||||
log.trace("Slot processsing nested dict value %s", value)
|
||||
cdata[atype][ind][idx][key] = self.__eval_slot(value)
|
||||
except AttributeError:
|
||||
# Not a string/slot
|
||||
continue
|
||||
if isinstance(listvalue, six.text_type):
|
||||
# Search strings in a list for __slot__:
|
||||
if listvalue.startswith(SLOT_TEXT):
|
||||
log.trace("Slot processsing nested string %s", listvalue)
|
||||
cdata[atype][ind][idx] = self.__eval_slot(listvalue)
|
||||
elif isinstance(arg, six.text_type) \
|
||||
and arg.startswith(SLOT_TEXT):
|
||||
# Search strings for __slot__:
|
||||
log.trace("Slot processsing %s", arg)
|
||||
cdata[atype][ind] = self.__eval_slot(arg)
|
||||
else:
|
||||
# Not a slot, skip it
|
||||
continue
|
||||
cdata[atype][ind] = self.__eval_slot(arg)
|
||||
|
||||
def verify_retry_data(self, retry_data):
|
||||
'''
|
||||
|
@ -281,6 +281,60 @@ class StateFormatSlotsTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': ['fun_return'], 'kwargs': {'key': 'val'}})
|
||||
|
||||
def test_format_slots_dict_arg(self):
|
||||
'''
|
||||
Test the format slots is calling a slot specified in dict arg.
|
||||
'''
|
||||
cdata = {
|
||||
'args': [
|
||||
{'subarg': '__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)'},
|
||||
],
|
||||
'kwargs': {
|
||||
'key': 'val',
|
||||
}
|
||||
}
|
||||
mock = MagicMock(return_value='fun_return')
|
||||
with patch.dict(self.state_obj.functions, {'mod.fun': mock}):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': [{'subarg': 'fun_return'}], 'kwargs': {'key': 'val'}})
|
||||
|
||||
def test_format_slots_listdict_arg(self):
|
||||
'''
|
||||
Test the format slots is calling a slot specified in list containing a dict.
|
||||
'''
|
||||
cdata = {
|
||||
'args': [[
|
||||
{'subarg': '__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)'},
|
||||
]],
|
||||
'kwargs': {
|
||||
'key': 'val',
|
||||
}
|
||||
}
|
||||
mock = MagicMock(return_value='fun_return')
|
||||
with patch.dict(self.state_obj.functions, {'mod.fun': mock}):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': [[{'subarg': 'fun_return'}]], 'kwargs': {'key': 'val'}})
|
||||
|
||||
def test_format_slots_liststr_arg(self):
|
||||
'''
|
||||
Test the format slots is calling a slot specified in list containing a dict.
|
||||
'''
|
||||
cdata = {
|
||||
'args': [[
|
||||
'__slot__:salt:mod.fun(fun_arg, fun_key=fun_val)',
|
||||
]],
|
||||
'kwargs': {
|
||||
'key': 'val',
|
||||
}
|
||||
}
|
||||
mock = MagicMock(return_value='fun_return')
|
||||
with patch.dict(self.state_obj.functions, {'mod.fun': mock}):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': [['fun_return']], 'kwargs': {'key': 'val'}})
|
||||
|
||||
def test_format_slots_kwarg(self):
|
||||
'''
|
||||
Test the format slots is calling a slot specified in kwargs with corresponding arguments.
|
||||
@ -360,3 +414,41 @@ class StateFormatSlotsTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_not_called()
|
||||
self.assertEqual(cdata, sls_data)
|
||||
|
||||
def test_slot_traverse_dict(self):
|
||||
'''
|
||||
Test the slot parsing of dict response.
|
||||
'''
|
||||
cdata = {
|
||||
'args': [
|
||||
'arg',
|
||||
],
|
||||
'kwargs': {
|
||||
'key': '__slot__:salt:mod.fun(fun_arg, fun_key=fun_val).key1',
|
||||
}
|
||||
}
|
||||
return_data = {'key1': 'value1'}
|
||||
mock = MagicMock(return_value=return_data)
|
||||
with patch.dict(self.state_obj.functions, {'mod.fun': mock}):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': ['arg'], 'kwargs': {'key': 'value1'}})
|
||||
|
||||
def test_slot_append(self):
|
||||
'''
|
||||
Test the slot parsing of dict response.
|
||||
'''
|
||||
cdata = {
|
||||
'args': [
|
||||
'arg',
|
||||
],
|
||||
'kwargs': {
|
||||
'key': '__slot__:salt:mod.fun(fun_arg, fun_key=fun_val).key1 ~ thing~',
|
||||
}
|
||||
}
|
||||
return_data = {'key1': 'value1'}
|
||||
mock = MagicMock(return_value=return_data)
|
||||
with patch.dict(self.state_obj.functions, {'mod.fun': mock}):
|
||||
self.state_obj.format_slots(cdata)
|
||||
mock.assert_called_once_with('fun_arg', fun_key='fun_val')
|
||||
self.assertEqual(cdata, {'args': ['arg'], 'kwargs': {'key': 'value1thing~'}})
|
||||
|
Loading…
Reference in New Issue
Block a user