"""Docker broker process entrypoint.""" from __future__ import annotations import json import os from collections.abc import Mapping from pathlib import Path from packages.hub_runtime import SecretFileError, read_secret_file from .broker import ( BrokerApp, BrokerError, DockerBackend, DockerEngineBackend, Registration, RegistrationStore, ServiceTokenVerifier, make_handler, ) def _read_secret() -> str: path = Path(os.getenv("BROKER_TOKEN_FILE", "/run/secrets/docker_broker_service_token")) try: return read_secret_file( path, expected_uid=( int(os.environ["SECRET_EXPECTED_UID"]) if os.getenv("SECRET_EXPECTED_UID") else None ), expected_gid=( int(os.environ["SECRET_EXPECTED_GID"]) if os.getenv("SECRET_EXPECTED_GID") else None ), ) except (SecretFileError, ValueError): raise SystemExit("required broker secret validation failed") from None def _labels(container: Mapping[str, object]) -> Mapping[str, object]: labels = container.get("Labels") if isinstance(labels, Mapping): return {str(key): value for key, value in labels.items()} config = container.get("Config") nested = config.get("Labels") if isinstance(config, Mapping) else None if isinstance(nested, Mapping): return {str(key): value for key, value in nested.items()} return {} def _resolve_container_id(backend: DockerBackend, compose_project: str, service: str) -> str: matches = [] for container in backend.containers(): labels = _labels(container) if ( labels.get("com.docker.compose.project") == compose_project and labels.get("com.docker.compose.service") == service ): matches.append(str(container.get("Id", ""))) if len(matches) != 1 or not matches[0]: raise ValueError("registration target must resolve to exactly one container") return matches[0] def _load_registrations(backend: DockerBackend) -> RegistrationStore: path = Path(os.getenv("BROKER_REGISTRATIONS_FILE", "/etc/itworx/registered-targets.json")) if not path.is_file(): return RegistrationStore(()) try: document = json.loads(path.read_text(encoding="utf-8")) if document.get("defaultDecision") != "deny": raise ValueError("broker registration default must deny") registrations = [] for item in document.get("containers", []): match = item["match"] compose_project = str(match["composeProject"]) service = str(match["service"]) registrations.append( Registration( project=str(item["project"]), environment=str(item["environment"]), registered_id=str(item["registeredId"]), container_id=_resolve_container_id(backend, compose_project, service), compose_project=compose_project, service=service, allowed_reads=frozenset(str(value) for value in item.get("allowedReads", [])), ) ) return RegistrationStore(registrations) except (BrokerError, json.JSONDecodeError, KeyError, OSError, TypeError, ValueError): raise SystemExit("broker registration validation failed") from None if __name__ == "__main__": from http.server import ThreadingHTTPServer backend = DockerEngineBackend() application = BrokerApp( backend, _load_registrations(backend), ServiceTokenVerifier(_read_secret()), ) ThreadingHTTPServer( ("0.0.0.0", int(os.getenv("BROKER_PORT", "8080"))), # noqa: S104 make_handler(application), # noqa: S104 ).serve_forever()