guardia-itsm/routers/shell_scripts.py
DESKTOP-TKLFCPRython 64c27c3509 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
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>
2026-05-29 18:18:52 +09:00

140 lines
4.8 KiB
Python

"""쉘 스크립트 라이브러리 CRUD 엔드포인트."""
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth import get_current_user
from database import get_db
from models import (
ShellScript, ShellScriptCreate, ShellScriptOut, ShellScriptUpdate,
User, UserRole,
)
router = APIRouter(prefix="/api/shell-scripts", tags=["shell-scripts"])
@router.get("", response_model=List[ShellScriptOut])
async def list_scripts(
category: Optional[str] = Query(None),
target_layer: Optional[str] = Query(None),
os_type: Optional[str] = Query(None),
keyword: Optional[str] = Query(None),
inst_id: Optional[int] = Query(None),
include_public: bool = Query(True),
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db),
_u: User = Depends(get_current_user),
):
q = select(ShellScript).where(ShellScript.is_active == True)
# inst_id 필터: 해당 기관 전용 + 공용(NULL) 포함 가능
if inst_id is not None:
if include_public:
q = q.where(or_(ShellScript.inst_id == inst_id, ShellScript.inst_id == None))
else:
q = q.where(ShellScript.inst_id == inst_id)
if category:
q = q.where(ShellScript.category == category)
if target_layer:
q = q.where(or_(ShellScript.target_layer == target_layer, ShellScript.target_layer == "ALL"))
if os_type:
q = q.where(or_(ShellScript.os_type == os_type, ShellScript.os_type == "ALL"))
if keyword:
q = q.where(
or_(
ShellScript.script_name.contains(keyword),
ShellScript.description.contains(keyword),
ShellScript.tags.contains(keyword),
)
)
q = q.order_by(ShellScript.category, ShellScript.script_name).offset(skip).limit(limit)
result = await db.execute(q)
return result.scalars().all()
@router.get("/{script_id}", response_model=ShellScriptOut)
async def get_script(
script_id: int,
db: AsyncSession = Depends(get_db),
_u: User = Depends(get_current_user),
):
r = await db.execute(select(ShellScript).where(ShellScript.id == script_id))
script = r.scalars().first()
if not script:
raise HTTPException(404, "스크립트를 찾을 수 없습니다.")
return script
@router.post("", response_model=ShellScriptOut, status_code=201)
async def create_script(
payload: ShellScriptCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
if current_user.role not in (UserRole.ADMIN, UserRole.PM, UserRole.ENGINEER):
raise HTTPException(403, "권한이 없습니다.")
data = payload.model_dump()
data["author"] = data.get("author") or current_user.username
script = ShellScript(**data)
db.add(script)
await db.commit()
await db.refresh(script)
return script
@router.patch("/{script_id}", response_model=ShellScriptOut)
async def update_script(
script_id: int,
payload: ShellScriptUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
if current_user.role not in (UserRole.ADMIN, UserRole.PM, UserRole.ENGINEER):
raise HTTPException(403, "권한이 없습니다.")
r = await db.execute(select(ShellScript).where(ShellScript.id == script_id))
script = r.scalars().first()
if not script:
raise HTTPException(404, "스크립트를 찾을 수 없습니다.")
for k, v in payload.model_dump(exclude_unset=True).items():
setattr(script, k, v)
script.updated_at = datetime.now()
await db.commit()
await db.refresh(script)
return script
@router.delete("/{script_id}", status_code=204)
async def delete_script(
script_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
if current_user.role not in (UserRole.ADMIN, UserRole.PM):
raise HTTPException(403, "ADMIN 또는 PM 권한이 필요합니다.")
r = await db.execute(select(ShellScript).where(ShellScript.id == script_id))
script = r.scalars().first()
if not script:
raise HTTPException(404, "스크립트를 찾을 수 없습니다.")
script.is_active = False
script.updated_at = datetime.now()
await db.commit()
@router.post("/{script_id}/increment-use", status_code=204)
async def increment_use_count(
script_id: int,
db: AsyncSession = Depends(get_db),
_u: User = Depends(get_current_user),
):
"""스크립트 사용 횟수 증가 (타임테이블 연계 시 호출)."""
r = await db.execute(select(ShellScript).where(ShellScript.id == script_id))
script = r.scalars().first()
if script:
script.use_count = (script.use_count or 0) + 1
await db.commit()