From d9e9663f2fddca0e38e21114b8c411cef77ab4c1 Mon Sep 17 00:00:00 2001 From: lakshmj Date: Fri, 5 Dec 2025 23:43:01 +0000 Subject: [PATCH 1/2] Fix for running cli commands through ansible using ssh connection. Signed-off-by: lakshmj --- plugins/connection/ssh_netscaler_adc.py | 78 ++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/plugins/connection/ssh_netscaler_adc.py b/plugins/connection/ssh_netscaler_adc.py index 6fe6a620..5b980b66 100644 --- a/plugins/connection/ssh_netscaler_adc.py +++ b/plugins/connection/ssh_netscaler_adc.py @@ -391,18 +391,35 @@ def _return_tuple_manipulate(func): def wrapped(self, *args, **kwargs): return_tuple = func(self, *args, **kwargs) return_tuple = list(return_tuple) - subst = "" + + # Decode stdout + stdout = codecs.decode(return_tuple[1], errors='ignore') + + # Remove "Done" messages with surrounding newlines regex = r"(\r\n|\r|\n|)( Done)(\r\n|\r|\n)+" - return_tuple[1] = re.sub(regex, subst, codecs.decode(return_tuple[1]), 0, re.UNICODE) - - # Ansible needs some data from return_tuple[1](or stdout). So, we are returning the same to ansible + stdout = re.sub(regex, "", stdout, 0, re.UNICODE) + + # Remove Warning messages (multi-line) + warning_regex = r"Warning: \[[\s\S]*?\]\s*" + stdout = re.sub(warning_regex, "", stdout, 0, re.UNICODE) + + # Remove leading "]" characters that might remain + stdout = re.sub(r"^\]\s*", "", stdout, re.MULTILINE) + + # Remove extra blank lines + stdout = re.sub(r"\n\s*\n", "\n", stdout) + + # Try to extract JSON if present regex2 = r'{.*}' try: - return_tuple[1] = re.findall(regex2, str(return_tuple[1]))[0] - except IndexError: + json_match = re.findall(regex2, stdout) + if json_match: + stdout = json_match[0] + except (IndexError, AttributeError): pass - # If no match, return the old `return_tuple[1]` (i.e., stdout) - + # If no match, use the cleaned stdout + + return_tuple[1] = stdout.encode() if isinstance(return_tuple[1], bytes) else stdout return_tuple = tuple(return_tuple) return return_tuple return wrapped @@ -455,3 +472,48 @@ def exec_command(self, cmd, in_data=None, sudoable=True): ''' run a command on the remote host ''' return super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) + + @_return_tuple_manipulate + def _bare_run(self, cmd, in_data, sudoable=True, checkrc=True): + """ + Wrapper around parent _bare_run to clean NetScaler output + """ + return super(Connection, self)._bare_run(cmd, in_data, sudoable=sudoable, checkrc=checkrc) + + def put_file(self, in_path, out_path): + """ + Transfer a file from local to remote using piped method due to NetScaler limitations + """ + display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.host) + + # Force piped transfer for NetScaler + # Save original transfer method + original_method = self.get_option('ssh_transfer_method') + + try: + # Temporarily set to piped + self._options['ssh_transfer_method'] = 'piped' + return super(Connection, self).put_file(in_path, out_path) + finally: + # Restore original method + if original_method: + self._options['ssh_transfer_method'] = original_method + + def fetch_file(self, in_path, out_path): + """ + Fetch a file from remote to local using piped method due to NetScaler limitations + """ + display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.host) + + # Force piped transfer for NetScaler + # Save original transfer method + original_method = self.get_option('ssh_transfer_method') + + try: + # Temporarily set to piped + self._options['ssh_transfer_method'] = 'piped' + return super(Connection, self).fetch_file(in_path, out_path) + finally: + # Restore original method + if original_method: + self._options['ssh_transfer_method'] = original_method From 96405c622495a00fab9b0245700549c75329a338 Mon Sep 17 00:00:00 2001 From: Shiva Shankar Vaddepally Date: Mon, 8 Dec 2025 06:18:50 +0000 Subject: [PATCH 2/2] solving lint issues Signed-off-by: Shiva Shankar Vaddepally --- plugins/connection/ssh_netscaler_adc.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/connection/ssh_netscaler_adc.py b/plugins/connection/ssh_netscaler_adc.py index 5b980b66..f0b42897 100644 --- a/plugins/connection/ssh_netscaler_adc.py +++ b/plugins/connection/ssh_netscaler_adc.py @@ -391,24 +391,24 @@ def _return_tuple_manipulate(func): def wrapped(self, *args, **kwargs): return_tuple = func(self, *args, **kwargs) return_tuple = list(return_tuple) - + # Decode stdout stdout = codecs.decode(return_tuple[1], errors='ignore') - + # Remove "Done" messages with surrounding newlines regex = r"(\r\n|\r|\n|)( Done)(\r\n|\r|\n)+" stdout = re.sub(regex, "", stdout, 0, re.UNICODE) - + # Remove Warning messages (multi-line) warning_regex = r"Warning: \[[\s\S]*?\]\s*" stdout = re.sub(warning_regex, "", stdout, 0, re.UNICODE) - + # Remove leading "]" characters that might remain stdout = re.sub(r"^\]\s*", "", stdout, re.MULTILINE) - + # Remove extra blank lines stdout = re.sub(r"\n\s*\n", "\n", stdout) - + # Try to extract JSON if present regex2 = r'{.*}' try: @@ -418,7 +418,7 @@ def wrapped(self, *args, **kwargs): except (IndexError, AttributeError): pass # If no match, use the cleaned stdout - + return_tuple[1] = stdout.encode() if isinstance(return_tuple[1], bytes) else stdout return_tuple = tuple(return_tuple) return return_tuple @@ -426,7 +426,6 @@ def wrapped(self, *args, **kwargs): def _manipulate_cmd(func): - @wraps(func) def wrapped(self, cmd, *args, **kwargs): # Adding the 'shell' command for the citrix adc cli @@ -485,11 +484,11 @@ def put_file(self, in_path, out_path): Transfer a file from local to remote using piped method due to NetScaler limitations """ display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.host) - + # Force piped transfer for NetScaler # Save original transfer method original_method = self.get_option('ssh_transfer_method') - + try: # Temporarily set to piped self._options['ssh_transfer_method'] = 'piped' @@ -504,11 +503,11 @@ def fetch_file(self, in_path, out_path): Fetch a file from remote to local using piped method due to NetScaler limitations """ display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.host) - + # Force piped transfer for NetScaler # Save original transfer method original_method = self.get_option('ssh_transfer_method') - + try: # Temporarily set to piped self._options['ssh_transfer_method'] = 'piped'