Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft support for parsing slot results and appending text #52187

Merged
merged 13 commits into from
Apr 9, 2019
25 changes: 25 additions & 0 deletions doc/topics/releases/neon.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,31 @@ editing XML IDs.
- xpath: .//actor[@id='1']
- value: William Shatner

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:

State Changes
=============
Expand Down
19 changes: 18 additions & 1 deletion doc/topics/slots/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
39 changes: 38 additions & 1 deletion salt/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2114,7 +2114,44 @@ def __eval_slot(self, slot):
'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):
'''
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,41 @@ def test_format_slots_malformed(self):
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~'}})