Blame containers/osbuild-composer/entrypoint.py

Packit Service 3a6627
"""entrypoint - Containerized OSBuild Composer
Packit Service 3a6627
Packit Service 3a6627
This provides the entrypoint for a containerized osbuild-composer image. It
Packit Service 3a6627
spawns `osbuild-composer` on start and manages it until it exits. The main
Packit Service 3a6627
purpose of this entrypoint is to prepare everything to be usable from within
Packit Service 3a6627
a container.
Packit Service 3a6627
"""
Packit Service 3a6627
Packit Service 3a6627
import argparse
Packit Service 3a6627
import contextlib
Packit Service 3a6627
import os
Packit Service 3a6627
import socket
Packit Service 3a6627
import subprocess
Packit Service 3a6627
import sys
Packit Service 3a6627
Packit Service 3a6627
Packit Service 3a6627
class Cli(contextlib.AbstractContextManager):
Packit Service 3a6627
    """Command Line Interface"""
Packit Service 3a6627
Packit Service 3a6627
    def __init__(self, argv):
Packit Service 3a6627
        self.args = None
Packit Service 3a6627
        self._argv = argv
Packit Service 3a6627
        self._exitstack = None
Packit Service 3a6627
        self._parser = None
Packit Service 3a6627
Packit Service 3a6627
    def _parse_args(self):
Packit Service 3a6627
        self._parser = argparse.ArgumentParser(
Packit Service 3a6627
            add_help=True,
Packit Service 3a6627
            allow_abbrev=False,
Packit Service 3a6627
            argument_default=None,
Packit Service 3a6627
            description="Containerized OSBuild Composer",
Packit Service 3a6627
            prog="container/osbuild-composer",
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        # --[no-]composer-api
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--composer-api",
Packit Service 3a6627
            action="store_true",
Packit Service 3a6627
            dest="composer_api",
Packit Service 3a6627
            help="Enable the composer-API",
Packit Service 3a6627
        )
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--no-composer-api",
Packit Service 3a6627
            action="store_false",
Packit Service 3a6627
            dest="composer_api",
Packit Service 3a6627
            help="Disable the composer-API",
Packit Service 3a6627
        )
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--composer-api-port",
Packit Service 3a6627
            type=int,
Packit Service 3a6627
            default=443,
Packit Service 3a6627
            dest="composer_api_port",
Packit Service 3a6627
            help="Port which the composer-API listens on",
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        # --[no-]local-worker-api
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--local-worker-api",
Packit Service 3a6627
            action="store_true",
Packit Service 3a6627
            dest="local_worker_api",
Packit Service 3a6627
            help="Enable the local-worker-API",
Packit Service 3a6627
        )
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--no-local-worker-api",
Packit Service 3a6627
            action="store_false",
Packit Service 3a6627
            dest="local_worker_api",
Packit Service 3a6627
            help="Disable the local-worker-API",
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        # --[no-]remote-worker-api
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--remote-worker-api",
Packit Service 3a6627
            action="store_true",
Packit Service 3a6627
            dest="remote_worker_api",
Packit Service 3a6627
            help="Enable the remote-worker-API",
Packit Service 3a6627
        )
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--no-remote-worker-api",
Packit Service 3a6627
            action="store_false",
Packit Service 3a6627
            dest="remote_worker_api",
Packit Service 3a6627
            help="Disable the remote-worker-API",
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        # --[no-]weldr-api
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--weldr-api",
Packit Service 3a6627
            action="store_true",
Packit Service 3a6627
            dest="weldr_api",
Packit Service 3a6627
            help="Enable the weldr-API",
Packit Service 3a6627
        )
Packit Service 3a6627
        self._parser.add_argument(
Packit Service 3a6627
            "--no-weldr-api",
Packit Service 3a6627
            action="store_false",
Packit Service 3a6627
            dest="weldr_api",
Packit Service 3a6627
            help="Disable the weldr-API",
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        self._parser.set_defaults(
Packit Service 3a6627
            builtin_worker=False,
Packit Service 3a6627
            composer_api=False,
Packit Service 3a6627
            local_worker_api=False,
Packit Service 3a6627
            remote_worker_api=False,
Packit Service 3a6627
            weldr_api=False,
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
        return self._parser.parse_args(self._argv[1:])
Packit Service 3a6627
Packit Service 3a6627
    def __enter__(self):
Packit Service 3a6627
        self._exitstack = contextlib.ExitStack()
Packit Service 3a6627
        self.args = self._parse_args()
Packit Service 3a6627
        return self
Packit Service 3a6627
Packit Service 3a6627
    def __exit__(self, exc_type, exc_value, exc_tb):
Packit Service 3a6627
        self._exitstack.close()
Packit Service 3a6627
        self._exitstack = None
Packit Service 3a6627
Packit Service 3a6627
    def _prepare_sockets(self):
Packit Service 3a6627
        # Prepare all the API sockets that osbuild-composer expectes, and make
Packit Service 3a6627
        # sure to pass them according to the systemd socket-activation API.
Packit Service 3a6627
        #
Packit Service 3a6627
        # Note that we rely on this being called early, so we get the correct
Packit Service 3a6627
        # FD numbers assigned. We need FD-#3 onwards for compatibility with
Packit Service 3a6627
        # socket activation (because python `subprocess.Popen` does not support
Packit Service 3a6627
        # renumbering the sockets we pass down).
Packit Service 3a6627
Packit Service 3a6627
        index = 3
Packit Service 3a6627
        sockets = []
Packit Service 3a6627
        names = []
Packit Service 3a6627
Packit Service 3a6627
        # osbuild-composer.socket
Packit Service 3a6627
        if self.args.weldr_api:
Packit Service 3a6627
            print("Create weldr-api socket", file=sys.stderr)
Packit Service 3a6627
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Packit Service 3a6627
            self._exitstack.enter_context(contextlib.closing(sock))
Packit Service 3a6627
            sock.bind("/run/weldr/api.socket")
Packit Service 3a6627
            sock.listen()
Packit Service 3a6627
            sockets.append(sock)
Packit Service 3a6627
            names.append("osbuild-composer.socket")
Packit Service 3a6627
Packit Service 3a6627
            assert(sock.fileno() == index)
Packit Service 3a6627
            index += 1
Packit Service 3a6627
Packit Service 3a6627
        # osbuild-composer-api.socket
Packit Service 3a6627
        if self.args.composer_api:
Packit Service 3a6627
            print("Create composer-api socket on port {}".format(self.args.composer_api_port) , file=sys.stderr)
Packit Service 3a6627
            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
Packit Service 3a6627
            self._exitstack.enter_context(contextlib.closing(sock))
Packit Service 3a6627
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Packit Service 3a6627
            sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
Packit Service 3a6627
            sock.bind(("::", self.args.composer_api_port))
Packit Service 3a6627
            sock.listen()
Packit Service 3a6627
            sockets.append(sock)
Packit Service 3a6627
            names.append("osbuild-composer-api.socket")
Packit Service 3a6627
Packit Service 3a6627
            assert(sock.fileno() == index)
Packit Service 3a6627
            index += 1
Packit Service 3a6627
Packit Service 3a6627
        # osbuild-local-worker.socket
Packit Service 3a6627
        if self.args.local_worker_api:
Packit Service 3a6627
            print("Create local-worker-api socket", file=sys.stderr)
Packit Service 3a6627
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Packit Service 3a6627
            self._exitstack.enter_context(contextlib.closing(sock))
Packit Service 3a6627
            sock.bind("/run/osbuild-composer/job.socket")
Packit Service 3a6627
            sock.listen()
Packit Service 3a6627
            sockets.append(sock)
Packit Service 3a6627
            names.append("osbuild-local-worker.socket")
Packit Service 3a6627
Packit Service 3a6627
            assert(sock.fileno() == index)
Packit Service 3a6627
            index += 1
Packit Service 3a6627
Packit Service 3a6627
        # osbuild-remote-worker.socket
Packit Service 3a6627
        if self.args.remote_worker_api:
Packit Service 3a6627
            print("Create remote-worker-api socket", file=sys.stderr)
Packit Service 3a6627
            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
Packit Service 3a6627
            self._exitstack.enter_context(contextlib.closing(sock))
Packit Service 3a6627
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Packit Service 3a6627
            sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
Packit Service 3a6627
            sock.bind(("::", 8700))
Packit Service 3a6627
            sock.listen(256)
Packit Service 3a6627
            sockets.append(sock)
Packit Service 3a6627
            names.append("osbuild-remote-worker.socket")
Packit Service 3a6627
Packit Service 3a6627
            assert(sock.fileno() == index)
Packit Service 3a6627
            index += 1
Packit Service 3a6627
Packit Service 3a6627
        # Prepare FD environment for the child process.
Packit Service 3a6627
        os.environ["LISTEN_FDS"] = str(len(sockets))
Packit Service 3a6627
        os.environ["LISTEN_FDNAMES"] = ":".join(names)
Packit Service 3a6627
Packit Service 3a6627
        return sockets
Packit Service 3a6627
Packit Service 3a6627
    @staticmethod
Packit Service 3a6627
    def _spawn_worker():
Packit Service 3a6627
        cmd = [
Packit Service 3a6627
            "/usr/libexec/osbuild-composer/osbuild-worker",
Packit Service 3a6627
            "-unix",
Packit Service 3a6627
            "/run/osbuild-composer/job.socket",
Packit Service 3a6627
        ]
Packit Service 3a6627
Packit Service 3a6627
        env = os.environ.copy()
Packit Service 3a6627
        env["CACHE_DIRECTORY"] = "/var/cache/osbuild-worker"
Packit Service 3a6627
        env["STATE_DIRECTORY"] = "/var/lib/osbuild-worker"
Packit Service 3a6627
Packit Service 3a6627
        return subprocess.Popen(
Packit Service 3a6627
            cmd,
Packit Service 3a6627
            cwd="/",
Packit Service 3a6627
            env=env,
Packit Service 3a6627
            stdin=subprocess.DEVNULL,
Packit Service 3a6627
            stderr=subprocess.STDOUT,
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
    @staticmethod
Packit Service 3a6627
    def _spawn_composer(sockets):
Packit Service 3a6627
        cmd = [
Packit Service 3a6627
            "/usr/libexec/osbuild-composer/osbuild-composer",
Packit Service 3a6627
            "-v",
Packit Service 3a6627
        ]
Packit Service 3a6627
Packit Service 3a6627
        # Prepare the environment for osbuild-composer. Note that we cannot use
Packit Service 3a6627
        # the `env` parameter of `subprocess.Popen()`, because it conflicts
Packit Service 3a6627
        # with the `preexec_fn=` parameter. Therefore, we have to modify the
Packit Service 3a6627
        # caller's environment.
Packit Service 3a6627
        os.environ["CACHE_DIRECTORY"] = "/var/cache/osbuild-composer"
Packit Service 3a6627
        os.environ["STATE_DIRECTORY"] = "/var/lib/osbuild-composer"
Packit Service 3a6627
Packit Service 3a6627
        # We need to set `LISTEN_PID=` to the target PID. The only way python
Packit Service 3a6627
        # allows us to do this is to hook into `preexec_fn=`, which is executed
Packit Service 3a6627
        # by `subprocess.Popen()` after forking, but before executing the new
Packit Service 3a6627
        # executable.
Packit Service 3a6627
        preexec_setenv = lambda: os.putenv("LISTEN_PID", str(os.getpid()))
Packit Service 3a6627
Packit Service 3a6627
        return subprocess.Popen(
Packit Service 3a6627
            cmd,
Packit Service 3a6627
            cwd="/usr/libexec/osbuild-composer",
Packit Service 3a6627
            stdin=subprocess.DEVNULL,
Packit Service 3a6627
            stderr=subprocess.STDOUT,
Packit Service 3a6627
            pass_fds=[sock.fileno() for sock in sockets],
Packit Service 3a6627
            preexec_fn=preexec_setenv,
Packit Service 3a6627
        )
Packit Service 3a6627
Packit Service 3a6627
    def run(self):
Packit Service 3a6627
        """Program Runtime"""
Packit Service 3a6627
Packit Service 3a6627
        proc_composer = None
Packit Service 3a6627
        proc_worker = None
Packit Service 3a6627
        res = 0
Packit Service 3a6627
        sockets = self._prepare_sockets()
Packit Service 3a6627
Packit Service 3a6627
        try:
Packit Service 3a6627
            if self.args.builtin_worker:
Packit Service 3a6627
                proc_worker = self._spawn_worker()
Packit Service 3a6627
Packit Service 3a6627
            proc_composer = self._spawn_composer(sockets)
Packit Service 3a6627
Packit Service 3a6627
            res = proc_composer.wait()
Packit Service 3a6627
            if proc_worker:
Packit Service 3a6627
                proc_worker.terminate()
Packit Service 3a6627
                proc_worker.wait()
Packit Service 3a6627
Packit Service 3a6627
            return res
Packit Service 3a6627
        except KeyboardInterrupt:
Packit Service 3a6627
            if proc_worker:
Packit Service 3a6627
                proc_worker.terminate()
Packit Service 3a6627
                proc_worker.wait()
Packit Service 3a6627
            if proc_composer:
Packit Service 3a6627
                proc_composer.terminate()
Packit Service 3a6627
                res = proc_composer.wait()
Packit Service 3a6627
        except:
Packit Service 3a6627
            if proc_worker:
Packit Service 3a6627
                proc_worker.kill()
Packit Service 3a6627
            if proc_composer:
Packit Service 3a6627
                proc_composer.kill()
Packit Service 3a6627
            raise
Packit Service 3a6627
Packit Service 3a6627
        return res
Packit Service 3a6627
Packit Service 3a6627
Packit Service 3a6627
if __name__ == "__main__":
Packit Service 3a6627
    with Cli(sys.argv) as global_main:
Packit Service 3a6627
        sys.exit(global_main.run())