script mode wip

This commit is contained in:
Edwin Eefting 2022-01-27 11:16:19 +01:00
parent 81d0bee7ae
commit 86706ca24f
2 changed files with 85 additions and 3 deletions

View File

@ -157,7 +157,15 @@ class TestExecuteNode(unittest2.TestCase):
self.assertEqual(nodeb.run(cmd=["cat", ExecuteNode.PIPE, "pwd"], cwd="/tmp/space test"), ["/tmp/space test"])
def test_script(self):
def stdout_handler(line):
print("handle: " + line)
nodea=ExecuteNode(debug_output=True, ssh_to="localhost")
cmd_pipe=nodea.script(lines=["echo line1", "echo line 2"])
cmd_pipe.execute(stdout_handler)

View File

@ -92,17 +92,18 @@ class ExecuteNode(LogStub):
return_stderr=False, pipe=False, return_all=False, cwd=None):
"""run a command on the node , checks output and parses/handle output and returns it
Takes care of proper quoting/escaping/ssh and logging of stdout/err/exit codes.
Either uses a local shell (sh -c) or remote shell (ssh) to execute the command.
Therefore the command can have stuff like actual pipes in it, if you dont want to use pipe=True to pipe stuff.
:param cmd: the actual command, should be a list, where the first item is the command
and the rest are parameters. use ExecuteNode.PIPE to add an unescaped |
(if you want to use system piping instead of python piping)
:param pipe: return CmdPipe instead of executing it.
:param pipe: return CmdPipe instead of executing it. (pipe this into anoter run() command via inp=...)
:param inp: Can be None, a string or a CmdPipe that was previously returned.
:param tab_split: split tabbed files in output into a list
:param valid_exitcodes: list of valid exit codes for this command (checks exit code of both sides of a pipe)
Use [] to accept all exit codes. Default [0]
:param valid_exitcodes: list of valid exit codes for this command. Use [] to accept all exit codes. Default [0]
:param readonly: make this True if the command doesn't make any changes and is safe to execute in testmode
:param hide_errors: don't show stderr output as error, instead show it as debugging output (use to hide expected errors)
:param return_stderr: return both stdout and stderr as a tuple. (normally only returns stdout)
@ -176,3 +177,76 @@ class ExecuteNode(LogStub):
return output_lines, error_lines
else:
return output_lines
def script(self, lines, inp=None, valid_exitcodes=None, readonly=False, hide_errors=False):
"""Run a multiline script on the node.
This is much more low level than run() and allows for finer grained control.
Either uses a local shell (sh -c) or remote shell (ssh) to execute the command.
It will always return a CmdPipe that you should call execute on, or pipe to another script. (via inp=...)
You need to do your own escaping/quoting.
It will do logging of stderr and exit codes, but you should
specify your stdout handler when calling CmdPipe.execute.
Also specify the optional stderr/exit code handlers if you need them.
Handlers are called for each line.
It wont collect lines internally like run() does, so streams of data can be of unlimited size.
:param lines: list of lines of the actual script.
:param inp: Can be None, a string or a CmdPipe that was previously returned.
:param readonly: make this True if the command doesn't make any changes and is safe to execute in testmode
:param valid_exitcodes: list of valid exit codes for this command. Use [] to accept all exit codes. Default [0]
:param hide_errors: don't show stderr output as error, instead show it as debugging output (use to hide expected errors)
"""
# create new pipe?
if not isinstance(inp, CmdPipe):
cmd_pipe = CmdPipe(self.readonly, inp)
else:
# add stuff to existing pipe
cmd_pipe = inp
def stderr_handler(line):
self._parse_stderr(line, hide_errors)
# exit code hanlder
if valid_exitcodes is None:
valid_exitcodes = [0]
def exit_handler(exit_code):
if self.debug_output:
self.debug("EXIT > {}".format(exit_code))
if (valid_exitcodes != []) and (exit_code not in valid_exitcodes):
self.error("Script returned exit code {} (valid codes: {})".format(cmd_item, exit_code, valid_exitcodes))
return False
return True
#build command
cmd=[]
#add remote shell
if not self.is_local():
#note: dont escape this part (executed directly without shell)
cmd.append("ssh")
if self.ssh_config is not None:
cmd.append(["-F", self.ssh_config])
cmd.append(self.ssh_to)
# convert to script
cmd.append("\n".join(lines))
# add shell command and handlers to pipe
cmd_item=CmdItem(cmd=cmd, readonly=readonly, stderr_handler=stderr_handler, exit_handler=exit_handler, shell=self.is_local())
cmd_pipe.add(cmd_item)
self.debug("SCRIPT > {}".format(cmd_pipe))
return cmd_pipe