diff --git a/doc/topics/releases/neon.rst b/doc/topics/releases/neon.rst index f3e1a7fa2036..60d41782b448 100644 --- a/doc/topics/releases/neon.rst +++ b/doc/topics/releases/neon.rst @@ -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 ============= diff --git a/doc/topics/slots/index.rst b/doc/topics/slots/index.rst index 30f796ea11e6..3b71f04c1b73 100644 --- a/doc/topics/slots/index.rst +++ b/doc/topics/slots/index.rst @@ -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:.(, ..., , ...) -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:.(..., , ...).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 ` 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 diff --git a/salt/state.py b/salt/state.py index 883d022d2c98..60954f45b996 100644 --- a/salt/state.py +++ b/salt/state.py @@ -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): ''' diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index ed56cbae135e..84f7707db20f 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -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~'}})