| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- """Environment parsing and dependency wiring for the standalone Runner."""
- from __future__ import annotations
- import os
- from dataclasses import dataclass, field
- from app.core.data_source.runtime import (
- DataSourceRuntimeConfig,
- build_standalone_data_source_runtime,
- )
- from app.runner.api import create_runner_app
- from app.runner.auth import TaskTokenVerifier
- from app.runner.ledger import PostgresTaskLedger
- from app.runner.nodes import (
- GovernedHttpExecutor,
- NodeRegistry,
- RestrictedPythonExecutor,
- SqlExecuteExecutor,
- SqlQueryExecutor,
- )
- def _required(name):
- value = str(os.environ.get(name, "")).strip()
- if not value:
- raise ValueError(f"{name} is required")
- return value
- def _integer(name, default, minimum, maximum):
- try:
- value = int(os.environ.get(name, str(default)))
- except (TypeError, ValueError) as exc:
- raise ValueError(f"{name} must be an integer") from exc
- if value < minimum or value > maximum:
- raise ValueError(f"{name} must be between {minimum} and {maximum}")
- return value
- @dataclass(frozen=True)
- class RunnerSettings:
- runtime: DataSourceRuntimeConfig = field(repr=False)
- task_token_secret: str = field(repr=False)
- allowed_http_hosts: frozenset = field(default_factory=frozenset)
- task_token_ttl_seconds: int = 60
- max_query_rows: int = 1000
- def runner_settings_from_env():
- task_token_secret = _required("RUNNER_TASK_TOKEN_SECRET")
- worker_count = _integer("RUNNER_WORKERS", 2, 1, 8)
- runtime = DataSourceRuntimeConfig.from_mapping(
- {
- "platform_database_url": _required("DATABASE_URL"),
- "neo4j_uri": _required("NEO4J_URI"),
- "neo4j_user": _required("NEO4J_USER"),
- "neo4j_password": _required("NEO4J_PASSWORD"),
- "credential_master_key": _required(
- "DATASOURCE_CREDENTIAL_MASTER_KEY"
- ),
- "credential_key_version": _required(
- "DATASOURCE_CREDENTIAL_KEY_VERSION"
- ),
- "certificate_dir": os.environ.get(
- "DATASOURCE_CERT_DIR",
- "/etc/dataops-platform/datasource-certs",
- ),
- "pool_size": _integer("RUNNER_DATASOURCE_POOL_SIZE", 1, 1, 3),
- "max_overflow": _integer(
- "RUNNER_DATASOURCE_MAX_OVERFLOW", 1, 0, 3
- ),
- "pool_timeout": _integer(
- "RUNNER_DATASOURCE_POOL_TIMEOUT", 10, 1, 60
- ),
- "pool_recycle": _integer(
- "RUNNER_DATASOURCE_POOL_RECYCLE", 1800, 60, 86400
- ),
- "idle_ttl": _integer(
- "RUNNER_DATASOURCE_POOL_IDLE_TTL", 900, 60, 86400
- ),
- "max_idle_pools": _integer(
- "RUNNER_DATASOURCE_MAX_IDLE_POOLS", 4, 1, 20
- ),
- "query_timeout": _integer(
- "RUNNER_DATASOURCE_QUERY_TIMEOUT", 30, 1, 300
- ),
- "worker_count": worker_count,
- "connection_budget": _integer(
- "RUNNER_DATASOURCE_CONNECTION_BUDGET", 32, 1, 200
- ),
- }
- )
- allowed_hosts = frozenset(
- host.strip().lower()
- for host in os.environ.get("RUNNER_HTTP_ALLOWED_HOSTS", "").split(",")
- if host.strip()
- )
- return RunnerSettings(
- runtime=runtime,
- task_token_secret=task_token_secret,
- allowed_http_hosts=allowed_hosts,
- task_token_ttl_seconds=_integer(
- "RUNNER_TASK_TOKEN_TTL_SECONDS", 60, 1, 300
- ),
- max_query_rows=_integer("RUNNER_MAX_QUERY_ROWS", 1000, 1, 10000),
- )
- def build_runner_application(settings=None):
- settings = settings or runner_settings_from_env()
- runtime = build_standalone_data_source_runtime(settings.runtime)
- registry = NodeRegistry(
- {
- "sql.query": SqlQueryExecutor(
- runtime.manager,
- max_rows=settings.max_query_rows,
- ),
- "sql.execute": SqlExecuteExecutor(runtime.manager),
- "python": RestrictedPythonExecutor({}),
- "http": GovernedHttpExecutor(
- allowed_hosts=settings.allowed_http_hosts
- ),
- }
- )
- application = create_runner_app(
- verifier=TaskTokenVerifier(settings.task_token_secret),
- ledger=PostgresTaskLedger(runtime.platform_engine),
- registry=registry,
- )
- application.extensions["dataops_runner_runtime"] = runtime
- application.extensions["dataops_runner_settings"] = settings
- return application
|