whew, got partial reads working with named temporary files. lots of weird oddities were discovered in the process

This commit is contained in:
Ari Brown 2024-07-24 18:50:03 -04:00
parent d67e398a69
commit 4df9b04b2b
2 changed files with 40 additions and 42 deletions

View file

@ -46,15 +46,15 @@ class Docker:
res = self.machine.docker_run(self.uri, cmd=cmd, env=self.variables) res = self.machine.docker_run(self.uri, cmd=cmd, env=self.variables)
self.out["stdout"] = res.stdout self.out["stdout"] = res[0].read
self.out["stderr"] = res.stderr self.out["stderr"] = res[1].read
if self.stdout: if self.stdout:
self.stdout.write(res.stdout) self.stdout.write(res[0].read)
self.stdout.write("\n") self.stdout.write("\n")
if self.stderr: if self.stderr:
self.stderr.write(res.stderr) self.stderr.write(res[1].read)
self.stderr.write("\n") self.stderr.write("\n")
self.finished = True self.finished = True

View file

@ -2,6 +2,8 @@ from fabric import Connection
import os import os
import threading import threading
import select import select
import tempfile
import io
# Bare machine, not necessarily associated with AWS # Bare machine, not necessarily associated with AWS
class Remote: class Remote:
@ -41,21 +43,18 @@ class Remote:
# `disown` means it'll run in the background # `disown` means it'll run in the background
# #
# TODO switch this to use `self.ssh.client.exec_command()` # TODO switch this to use `self.ssh.client.exec_command()`
def cmd(self, command, hide=True, disown=False, watch=False): #def cmd(self, command, hide=True, disown=False, watch=False):
res = self.ssh.run(f"{self.prep_variables()}; {command}", # res = self.ssh.run(f"{self.prep_variables()}; {command}",
warn=True, # warn=True,
hide=hide, # hide=hide,
disown=disown) # disown=disown)
return res # return res
# https://github.com/paramiko/paramiko/issues/593#issuecomment-145377328 # https://github.com/paramiko/paramiko/issues/593#issuecomment-145377328
# https://stackoverflow.com/questions/23504126/do-you-have-to-check-exit-status-ready-if-you-are-going-to-check-recv-ready/32758464#32758464 # https://stackoverflow.com/questions/23504126/do-you-have-to-check-exit-status-ready-if-you-are-going-to-check-recv-ready/32758464#32758464
# #
def cmd(self, command, hide=True, disown=False, watch=False): def cmd(self, command, hide=True, disown=False, watch=False):
print(self.ssh)
print(self.ssh.client)
self.ssh.run("echo hello world", warn=True, hide=hide, disown=disown) self.ssh.run("echo hello world", warn=True, hide=hide, disown=disown)
stdin, stdout, stderr = self.ssh.client.exec_command(command) stdin, stdout, stderr = self.ssh.client.exec_command(command)
@ -63,12 +62,21 @@ class Remote:
# this is the same for all three inputs # this is the same for all three inputs
channel = stdin.channel channel = stdin.channel
out_r, out_w = os.pipe() # regular TemporaryFile doesn't work for some reason, even with
err_r, err_w = os.pipe() # explicit flush(). I think it's because it doesn't actually create
# a file on disk until enough input has been gathered.
os.write(out_w, b"hello world") #
# A flush is required after every write
# Leave the files so that the readers can work even after the writers
# are done
#
# Thanks to SirDonNick in #python for the help here
out = tempfile.NamedTemporaryFile(delete=False)
err = tempfile.NamedTemporaryFile(delete=False)
# Taken from
# https://stackoverflow.com/a/78765054 # https://stackoverflow.com/a/78765054
# and then improved/cleaned up
# we do not need stdin. # we do not need stdin.
stdin.close() stdin.close()
@ -76,9 +84,8 @@ class Remote:
channel.shutdown_write() channel.shutdown_write()
# read stdout/stderr to prevent read block hangs # read stdout/stderr to prevent read block hangs
stdout_chunks = [] out.write(channel.recv(len(channel.in_buffer)))
stderr_chunks = [] err.write(channel.recv_stderr(len(channel.in_stderr_buffer)))
stdout_chunks.append(channel.recv(len(channel.in_buffer)))
timeout = 60 timeout = 60
@ -96,20 +103,12 @@ class Remote:
break break
for c in readq: for c in readq:
if c.recv_ready(): if c.recv_ready():
#stdout_chunks.append(channel.recv(len(c.in_buffer))) out.write(channel.recv(len(c.in_buffer)))
data = channel.recv(len(c.in_buffer)) out.flush()
if data:
print("*******")
print(repr(data))
os.write(out, data)
got_chunk = True got_chunk = True
if c.recv_stderr_ready(): if c.recv_stderr_ready():
#stderr_chunks.append(channel.recv_stderr(len(c.in_stderr_buffer))) err.write(channel.recv_stderr(len(c.in_stderr_buffer)))
data = channel.recv_stderr(len(c.in_stderr_buffer)) err.flush()
if data:
print("*******")
print(repr(data))
os.write(err, data)
got_chunk = True got_chunk = True
# for c # for c
@ -131,24 +130,23 @@ class Remote:
# remote side is finished and our buffers are empty # remote side is finished and our buffers are empty
break break
# if # if
out.close()
err.close()
# while # while
# close the pseudofiles # close the pseudofiles
stdout.close() stdout.close()
stderr.close() stderr.close()
#thread = threading.Thread(target = fill_buffers, thread = threading.Thread(target = fill_buffers,
# args = (out_w, err_w)) args = (out, err))
#thread.start() thread.start()
fill_buffers(out_w, err_w)
#if not disown: if not disown:
# thread.join() print("joining")
thread.join()
#return (stdout_chunks, stderr_chunks) return (open(out.name, "rb"), open(err.name, "rb"), thread)
os.close(out_w)
os.close(err_w)
return (os.fdopen(out_r), os.fdopen(err_r)), thread)
def write_env_file(self, variables, fname="~/env.list"): def write_env_file(self, variables, fname="~/env.list"):