G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
"""
|
|
GUARDiA ITSM — 데이터베이스 엔진 설정.
|
|
|
|
환경변수 DATABASE_URL 로 DB를 자동 선택한다:
|
|
- 미설정(기본): SQLite + aiosqlite (개발/단독 실행)
|
|
- postgresql+asyncpg://user:pass@host:5432/db: PostgreSQL (운영)
|
|
|
|
PostgreSQL 사용 시 asyncpg, alembic 패키지가 필요하다:
|
|
pip install asyncpg alembic
|
|
|
|
마이그레이션:
|
|
alembic init migrations
|
|
alembic revision --autogenerate -m "initial_schema"
|
|
alembic upgrade head
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
from sqlalchemy.ext.asyncio import (
|
|
AsyncSession,
|
|
async_sessionmaker,
|
|
create_async_engine,
|
|
)
|
|
from sqlalchemy.orm import DeclarativeBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ── 환경변수 기반 DB URL 자동 선택 ───────────────────────────────────────────
|
|
DATABASE_URL: str = os.getenv(
|
|
"DATABASE_URL",
|
|
"sqlite+aiosqlite:///./guardia_itsm.db", # 기본: SQLite (개발)
|
|
)
|
|
|
|
# URL 정규화: postgres:// → postgresql+asyncpg:// (Heroku/Gitpod 호환)
|
|
if DATABASE_URL.startswith("postgres://"):
|
|
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql+asyncpg://", 1)
|
|
|
|
_is_postgres = DATABASE_URL.startswith("postgresql")
|
|
|
|
# ── 엔진 생성 ────────────────────────────────────────────────────────────────
|
|
_engine_kwargs: dict = {
|
|
"echo": os.getenv("DB_ECHO", "false").lower() == "true",
|
|
"pool_pre_ping": True,
|
|
}
|
|
|
|
if _is_postgres:
|
|
# PostgreSQL: MVCC 기반 연결 풀링
|
|
_engine_kwargs["pool_size"] = int(os.getenv("DB_POOL_SIZE", "10"))
|
|
_engine_kwargs["max_overflow"] = int(os.getenv("DB_MAX_OVERFLOW", "20"))
|
|
logger.info("GUARDiA DB: PostgreSQL 모드 (pool_size=%s)", _engine_kwargs["pool_size"])
|
|
else:
|
|
# SQLite: 파일 잠금 방식, 오버플로 불가
|
|
_engine_kwargs["connect_args"] = {"check_same_thread": False}
|
|
logger.info("GUARDiA DB: SQLite 모드")
|
|
|
|
engine = create_async_engine(DATABASE_URL, **_engine_kwargs)
|
|
|
|
SessionLocal = async_sessionmaker(
|
|
engine,
|
|
class_=AsyncSession,
|
|
expire_on_commit=False,
|
|
)
|
|
|
|
|
|
# ── Base ─────────────────────────────────────────────────────────────────────
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
# ── 의존성 주입 ───────────────────────────────────────────────────────────────
|
|
|
|
async def get_db():
|
|
"""FastAPI Depends용 — DB 세션 제공 후 자동 정리."""
|
|
async with SessionLocal() as session:
|
|
yield session
|
|
|
|
|
|
# ── 테이블 초기화 ─────────────────────────────────────────────────────────────
|
|
|
|
async def init_db() -> None:
|
|
"""
|
|
개발 환경에서 테이블 자동 생성.
|
|
운영(PostgreSQL)에서는 Alembic migration을 사용하므로
|
|
create_all은 이미 존재하는 테이블을 건드리지 않는다.
|
|
"""
|
|
from models import Base as ModelBase # noqa: F401 — 모델 등록용 임포트
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(ModelBase.metadata.create_all)
|
|
logger.info("DB 테이블 초기화 완료")
|