File size: 5,848 Bytes
11dde75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
r"""
bootstrap_db.py β€” one-shot fresh-database bootstrap, run at container start.

Purpose: let the agent Space target an EMPTY database that has the exact same
schema as the team's live database, without any manual DBA work. The sandbox
this Space was deployed from cannot reach Postgres, but the Space itself can β€”
so the clone happens here, on first boot.

Behaviour (idempotent, safe to run every boot):
  1. No-op unless BOOTSTRAP_CLONE_FROM is set (the SOURCE database name,
     e.g. "AIMDatabase"). DB_NAME is the TARGET (e.g. "aim_agent").
  2. If the target database does not exist: CREATE DATABASE.
  3. If the target already has the material tables: exit quietly (bootstrapped).
  4. Otherwise clone schema only (pg_dump --schema-only --no-owner
     --no-privileges | psql): every table, column, index and sequence β€”
     zero rows.
  5. Verify: material tables + sources present, all empty, and
     pg_mirror.check_schema() passes against the target.

Never fatal: any failure prints a loud warning and the app still starts β€”
the orchestrator independently refuses to write to an unmigrated schema.
"""

from __future__ import annotations

import os
import subprocess
import sys

import psycopg
from psycopg import sql

MATERIAL_TABLES = ("Polymers", "Fibers", "Composites_materials")


def env(key: str, default: str | None = None) -> str | None:
    v = os.environ.get(key)
    return v if v not in (None, "") else default


def connect(dbname: str) -> psycopg.Connection:
    return psycopg.connect(
        host=env("DB_HOST"),
        port=int(env("DB_PORT", "5432")),
        dbname=dbname,
        user=env("DB_USER"),
        password=env("DB_PASSWORD"),
        sslmode=env("DB_SSLMODE", "require"),
        connect_timeout=20,
    )


def has_material_tables(conn: psycopg.Connection) -> bool:
    with conn.cursor() as cur:
        cur.execute(
            "SELECT count(*) FROM information_schema.tables "
            "WHERE table_schema = 'public' AND table_name = ANY(%s)",
            (list(MATERIAL_TABLES),),
        )
        return cur.fetchone()[0] == len(MATERIAL_TABLES)


def pg_tool_env() -> dict[str, str]:
    e = dict(os.environ)
    e["PGPASSWORD"] = env("DB_PASSWORD") or ""
    e["PGSSLMODE"] = env("DB_SSLMODE", "require")
    return e


def main() -> int:
    source = env("BOOTSTRAP_CLONE_FROM")
    target = env("DB_NAME")
    if not source:
        print("[bootstrap] BOOTSTRAP_CLONE_FROM not set β€” skipping.")
        return 0
    if not target or target == source:
        print(f"[bootstrap] refusing: DB_NAME ({target!r}) must differ from "
              f"BOOTSTRAP_CLONE_FROM ({source!r}).")
        return 0

    host = env("DB_HOST")
    print(f"[bootstrap] target={target} source={source} host={host}")

    # 1. Does the target database exist? (ask via the source DB)
    # autocommit from the start: CREATE DATABASE cannot run inside a
    # transaction, and psycopg3 otherwise opens one implicitly on first query.
    with connect(source) as src:
        src.autocommit = True
        with src.cursor() as cur:
            cur.execute("SELECT 1 FROM pg_database WHERE datname = %s", (target,))
            target_exists = cur.fetchone() is not None
        if not target_exists:
            with src.cursor() as cur:
                cur.execute(
                    sql.SQL("CREATE DATABASE {}").format(sql.Identifier(target)))
            print(f"[bootstrap] created database {target}")

    # 2. Already bootstrapped?
    with connect(target) as tgt:
        if has_material_tables(tgt):
            print(f"[bootstrap] {target} already has material tables β€” done.")
            return 0

    # 3. Schema-only clone.
    print(f"[bootstrap] cloning schema {source} -> {target} ...")
    port = env("DB_PORT", "5432")
    user = env("DB_USER")
    dump = subprocess.run(
        ["pg_dump", "--schema-only", "--no-owner", "--no-privileges",
         "-h", host, "-p", port, "-U", user, "-d", source],
        env=pg_tool_env(), capture_output=True, text=True, timeout=300,
    )
    if dump.returncode != 0:
        print(f"[bootstrap] WARNING: pg_dump failed:\n{dump.stderr[-2000:]}")
        return 0
    restore = subprocess.run(
        ["psql", "-h", host, "-p", port, "-U", user, "-d", target,
         "-v", "ON_ERROR_STOP=0", "-q"],
        env=pg_tool_env(), input=dump.stdout,
        capture_output=True, text=True, timeout=300,
    )
    errors = [l for l in restore.stderr.splitlines() if "ERROR" in l]
    if errors:
        print(f"[bootstrap] psql reported {len(errors)} error(s), first few:")
        for line in errors[:5]:
            print(f"  {line}")

    # 4. Verify.
    with connect(target) as tgt:
        if not has_material_tables(tgt):
            print("[bootstrap] WARNING: clone finished but material tables "
                  "missing β€” agent will fail safe (no writes).")
            return 0
        with tgt.cursor() as cur:
            for t in MATERIAL_TABLES + ("sources",):
                try:
                    cur.execute(
                        sql.SQL("SELECT count(*) FROM {}").format(sql.Identifier(t)))
                    print(f"[bootstrap]   {t}: {cur.fetchone()[0]} rows")
                except psycopg.Error:
                    tgt.rollback()
                    print(f"[bootstrap]   {t}: MISSING")
        try:
            import pg_mirror
            pg_mirror.check_schema(tgt)
            print("[bootstrap] schema check PASSED β€” fresh empty DB ready.")
        except Exception as exc:  # noqa: BLE001
            print(f"[bootstrap] WARNING: schema check failed: {exc}")
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as exc:  # noqa: BLE001 β€” never block app start
        print(f"[bootstrap] WARNING: unexpected failure: {exc}")
        sys.exit(0)