From 1db807591d9bf6cd2f9faf616316e4a0ee2944f6 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Thu, 28 Mar 2019 22:24:31 +0000 Subject: [PATCH 1/4] support slot parsing inside dict and lists of dict or strings --- salt/state.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/salt/state.py b/salt/state.py index 883d022d2c98..23f4d748defe 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2120,18 +2120,45 @@ 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(): + if value.startswith(SLOT_TEXT): + log.debug("Slot processsing dict value %s", value) + cdata[atype][ind][key] = self.__eval_slot(value) + elif isinstance(arg, list): + for idx, listvalue in enumerate(arg): + log.debug("Slot processing list value: %s", listvalue) + if isinstance(listvalue, dict): + # Search dict values in list for __slot__: + for key, value in listvalue.items(): + if value.startswith(SLOT_TEXT): + log.debug("Slot processsing nested dict value %s", value) + cdata[atype][ind][idx][key] = self.__eval_slot(value) + if isinstance(listvalue, six.text_type): + # Search strings in a list for __slot__: + if listvalue.startswith(SLOT_TEXT): + log.debug("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.debug("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): ''' From 5354f2437fe1a25e2a84894e248ad2756fcad693 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Fri, 29 Mar 2019 17:54:42 +0000 Subject: [PATCH 2/4] only process strings --- salt/state.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/salt/state.py b/salt/state.py index 23f4d748defe..f1dbd00af012 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2134,9 +2134,10 @@ def format_slots(self, cdata): if isinstance(arg, dict): # Search dictionary values for __slot__: for key, value in arg.items(): - if value.startswith(SLOT_TEXT): - log.debug("Slot processsing dict value %s", value) - cdata[atype][ind][key] = self.__eval_slot(value) + if isinstance(value, six.text_type): + if value.startswith(SLOT_TEXT): + log.debug("Slot processsing dict value %s", value) + cdata[atype][ind][key] = self.__eval_slot(value) elif isinstance(arg, list): for idx, listvalue in enumerate(arg): log.debug("Slot processing list value: %s", listvalue) From 87b6843339d000e2df169a8547a8704e36f5bb65 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Sat, 30 Mar 2019 05:31:56 +0000 Subject: [PATCH 3/4] handle not string objects --- salt/state.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/salt/state.py b/salt/state.py index f1dbd00af012..6bfac7881327 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2134,19 +2134,26 @@ def format_slots(self, cdata): if isinstance(arg, dict): # Search dictionary values for __slot__: for key, value in arg.items(): - if isinstance(value, six.text_type): + try: if value.startswith(SLOT_TEXT): log.debug("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.debug("Slot processing list value: %s", listvalue) if isinstance(listvalue, dict): # Search dict values in list for __slot__: for key, value in listvalue.items(): - if value.startswith(SLOT_TEXT): - log.debug("Slot processsing nested dict value %s", value) - cdata[atype][ind][idx][key] = self.__eval_slot(value) + try: + if value.startswith(SLOT_TEXT): + log.debug("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): From 408942d5766fb37428855963d6674fa04f894db9 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Thu, 4 Apr 2019 08:04:40 +0100 Subject: [PATCH 4/4] change log level and add tests --- salt/state.py | 10 ++++---- tests/unit/test_state.py | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/salt/state.py b/salt/state.py index 6bfac7881327..3a70d62ecf08 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2136,20 +2136,20 @@ def format_slots(self, cdata): for key, value in arg.items(): try: if value.startswith(SLOT_TEXT): - log.debug("Slot processsing dict value %s", value) + 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.debug("Slot processing list value: %s", listvalue) + 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.debug("Slot processsing nested dict value %s", value) + log.trace("Slot processsing nested dict value %s", value) cdata[atype][ind][idx][key] = self.__eval_slot(value) except AttributeError: # Not a string/slot @@ -2157,12 +2157,12 @@ def format_slots(self, cdata): if isinstance(listvalue, six.text_type): # Search strings in a list for __slot__: if listvalue.startswith(SLOT_TEXT): - log.debug("Slot processsing nested string %s", listvalue) + 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.debug("Slot processsing %s", arg) + log.trace("Slot processsing %s", arg) cdata[atype][ind] = self.__eval_slot(arg) else: # Not a slot, skip it diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index ed56cbae135e..a84771ccb52c 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -337,6 +337,60 @@ def test_format_slots_arg(self): 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.