api: new host side API to be used by the container

Introduce an osbuild API that can be used by the container to talk
to the osbuild host. It currently supports one method 'setup-stdio'
which should be used by the container to setup its standard input/
output so the stages can transparently do i/o with the osbuild host
via stdio.
The input data (args) is written to a temp-file backed buffer. The
output is either the host's stdout directly or another temp-file
backed buffer; the latter is re-opened (via /proc/self/fd) to get
another file-descriptor for the container, so in theory the host
and the container could do i/o to the same buffer independently.
This commit is contained in:
Christian Kellner 2019-10-29 19:19:32 +01:00 committed by Lars Karlitski
parent 76518db26b
commit 93e1c60460

75
osbuild/api.py Normal file
View file

@ -0,0 +1,75 @@
import array
import asyncio
import json
import os
import tempfile
import threading
import sys
from . import remoteloop
class API:
def __init__(self, sock, args, interactive):
self.sock = sock
self.input = args
self.interactive = interactive
self._output = None
self.event_loop = asyncio.new_event_loop()
self.event_loop.add_reader(self.sock, self._dispatch)
self.thread = threading.Thread(target=self._run_event_loop)
@property
def output(self):
return self._output and self._output.read()
def _prepare_input(self):
with tempfile.TemporaryFile() as fd:
fd.write(json.dumps(self.input).encode('utf-8'))
# re-open the file to get a read-only file descriptor
return open(f"/proc/self/fd/{fd.fileno()}", "r")
def _prepare_output(self):
if self.interactive:
return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
out = tempfile.TemporaryFile(mode="wb")
fd = os.open(f"/proc/self/fd/{out.fileno()}", os.O_RDONLY|os.O_CLOEXEC)
self._output = os.fdopen(fd)
return out
def _setup_stdio(self, addr):
with self._prepare_input() as stdin, \
self._prepare_output() as stdout:
msg = {}
fds = array.array("i")
fds.append(stdin.fileno())
msg['stdin'] = 0
fds.append(stdout.fileno())
msg['stdout'] = 1
fds.append(stdout.fileno())
msg['stderr'] = 2
remoteloop.dump_fds(self.sock, msg, fds, addr=addr)
def __del__(self):
self.sock.close()
def _dispatch(self):
msg, addr = self.sock.recvfrom(1024)
args = json.loads(msg)
if args["method"] == 'setup-stdio':
self._setup_stdio(addr)
def _run_event_loop(self):
# Set the thread-local event loop
asyncio.set_event_loop(self.event_loop)
# Run event loop until stopped
self.event_loop.run_forever()
def __enter__(self):
self.thread.start()
return self
def __exit__(self, *args):
self.event_loop.call_soon_threadsafe(self.event_loop.stop)
self.thread.join()