Archived
Prepare KarlDNS production deployment
This commit is contained in:
1 parent
5a90bd9d87
commit
ce5b4e0ff2
101 files changed
+3364
-2018
No files matched your search
@@ -0,0 +1,234 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from ..models import UpdateResult
|
||||
from .types import UpdateEventRow
|
||||
|
||||
|
||||
class RecordsEventsMixin:
|
||||
def upsert(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
update_ipv4: bool = True,
|
||||
update_ipv6: bool = True,
|
||||
) -> UpdateResult:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
with self.connect() as conn:
|
||||
existing = conn.execute("SELECT ipv4, ipv6 FROM records WHERE hostname = ?", (hostname,)).fetchone()
|
||||
current_ipv4 = existing["ipv4"] if existing else None
|
||||
current_ipv6 = existing["ipv6"] if existing else None
|
||||
effective_ipv4 = ipv4 if update_ipv4 else current_ipv4
|
||||
effective_ipv6 = ipv6 if update_ipv6 else current_ipv6
|
||||
changed = existing is None or current_ipv4 != effective_ipv4 or current_ipv6 != effective_ipv6
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO records(hostname, ipv4, ipv6, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(hostname) DO UPDATE SET
|
||||
ipv4=excluded.ipv4,
|
||||
ipv6=excluded.ipv6,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(hostname, effective_ipv4, effective_ipv6, now),
|
||||
)
|
||||
return UpdateResult(
|
||||
hostname=hostname,
|
||||
ipv4=effective_ipv4,
|
||||
ipv6=effective_ipv6,
|
||||
changed=changed,
|
||||
update_ipv4=update_ipv4,
|
||||
update_ipv6=update_ipv6,
|
||||
)
|
||||
|
||||
def commit_update_result(self, result: UpdateResult) -> UpdateResult:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO records(hostname, ipv4, ipv6, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(hostname) DO UPDATE SET
|
||||
ipv4=excluded.ipv4,
|
||||
ipv6=excluded.ipv6,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(result.hostname, result.ipv4, result.ipv6, now),
|
||||
)
|
||||
return result
|
||||
|
||||
def preview_update(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
update_ipv4: bool = True,
|
||||
update_ipv6: bool = True,
|
||||
) -> UpdateResult:
|
||||
with self.connect() as conn:
|
||||
existing = conn.execute("SELECT ipv4, ipv6 FROM records WHERE hostname = ?", (hostname,)).fetchone()
|
||||
current_ipv4 = existing["ipv4"] if existing else None
|
||||
current_ipv6 = existing["ipv6"] if existing else None
|
||||
effective_ipv4 = ipv4 if update_ipv4 else current_ipv4
|
||||
effective_ipv6 = ipv6 if update_ipv6 else current_ipv6
|
||||
changed = existing is None or current_ipv4 != effective_ipv4 or current_ipv6 != effective_ipv6
|
||||
return UpdateResult(
|
||||
hostname=hostname,
|
||||
ipv4=effective_ipv4,
|
||||
ipv6=effective_ipv6,
|
||||
changed=changed,
|
||||
update_ipv4=update_ipv4,
|
||||
update_ipv6=update_ipv6,
|
||||
)
|
||||
|
||||
def list_records(self) -> list[dict[str, str | None]]:
|
||||
return self.list_records_page(limit=10_000, offset=0)
|
||||
|
||||
def list_records_page(self, limit: int = 100, offset: int = 0) -> list[dict[str, str | None]]:
|
||||
with self.connect() as conn:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT hostname, ipv4, ipv6, updated_at
|
||||
FROM records
|
||||
ORDER BY hostname
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
(limit, offset),
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def count_records(self) -> int:
|
||||
with self.connect() as conn:
|
||||
row = conn.execute("SELECT COUNT(*) FROM records").fetchone()
|
||||
return int(row[0])
|
||||
|
||||
def log_update_event(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
status: str,
|
||||
detail: str,
|
||||
source_ip: str,
|
||||
) -> None:
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO update_events(hostname, ipv4, ipv6, status, detail, source_ip, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(hostname, ipv4, ipv6, status, detail[:500], source_ip, datetime.now(UTC).isoformat()),
|
||||
)
|
||||
|
||||
def log_update_rollup(
|
||||
self,
|
||||
hostname: str,
|
||||
ipv4: str | None,
|
||||
ipv6: str | None,
|
||||
status: str,
|
||||
detail: str,
|
||||
source_ip: str,
|
||||
) -> None:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO update_event_rollups(
|
||||
hostname, ipv4, ipv6, status, detail, source_ip, first_seen_at, last_seen_at, count
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)
|
||||
ON CONFLICT(hostname, ipv4, ipv6, status, detail, source_ip) DO UPDATE SET
|
||||
last_seen_at=excluded.last_seen_at,
|
||||
count=count + 1
|
||||
""",
|
||||
(hostname, ipv4 or "", ipv6 or "", status, detail[:500], source_ip, now, now),
|
||||
)
|
||||
|
||||
def list_update_events(self, limit: int = 100, offset: int = 0) -> list[UpdateEventRow]:
|
||||
return self.list_update_events_page(limit=limit, offset=offset)
|
||||
|
||||
def list_update_events_page(self, limit: int = 100, offset: int = 0) -> list[UpdateEventRow]:
|
||||
with self.connect() as conn:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, created_at, count
|
||||
FROM (
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, created_at, 1 AS count
|
||||
FROM update_events
|
||||
UNION ALL
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, last_seen_at AS created_at, count
|
||||
FROM update_event_rollups
|
||||
)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
(limit, offset),
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def list_update_events_before(self, limit: int = 100, before: str | None = None) -> list[UpdateEventRow]:
|
||||
where_clause = "WHERE created_at < ?" if before else ""
|
||||
params: tuple[object, ...] = (before, limit) if before else (limit,)
|
||||
with self.connect() as conn:
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, created_at, count
|
||||
FROM (
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, created_at, 1 AS count
|
||||
FROM update_events
|
||||
UNION ALL
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, last_seen_at AS created_at, count
|
||||
FROM update_event_rollups
|
||||
)
|
||||
{where_clause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
params,
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def count_update_events(self) -> int:
|
||||
with self.connect() as conn:
|
||||
event_count = conn.execute("SELECT COUNT(*) FROM update_events").fetchone()[0]
|
||||
rollup_count = conn.execute("SELECT COUNT(*) FROM update_event_rollups").fetchone()[0]
|
||||
return int(event_count) + int(rollup_count)
|
||||
|
||||
def list_update_events_for_hostname(self, hostname: str, limit: int = 25) -> list[UpdateEventRow]:
|
||||
with self.connect() as conn:
|
||||
event_rows = conn.execute(
|
||||
"""
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, created_at, 1 AS count
|
||||
FROM update_events
|
||||
WHERE hostname = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(hostname, limit),
|
||||
).fetchall()
|
||||
rollup_rows = conn.execute(
|
||||
"""
|
||||
SELECT hostname, ipv4, ipv6, status, detail, source_ip, last_seen_at AS created_at, count
|
||||
FROM update_event_rollups
|
||||
WHERE hostname = ?
|
||||
ORDER BY last_seen_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(hostname, limit),
|
||||
).fetchall()
|
||||
return _merge_event_rows(event_rows, rollup_rows, limit, 0)
|
||||
|
||||
|
||||
def _merge_event_rows(
|
||||
event_rows: list[sqlite3.Row],
|
||||
rollup_rows: list[sqlite3.Row],
|
||||
limit: int,
|
||||
offset: int,
|
||||
) -> list[UpdateEventRow]:
|
||||
rows = [dict(row) for row in event_rows]
|
||||
rows.extend(dict(row) for row in rollup_rows)
|
||||
rows.sort(key=lambda row: str(row["created_at"]), reverse=True)
|
||||
return rows[offset : offset + limit]
|
||||
Reference in new issue
Block a user