From 8b1c5037bcd37c2c5785eb2d8a170f70438c622f Mon Sep 17 00:00:00 2001 From: AI DevOps Orchestrator Team Date: Fri, 1 May 2026 01:46:55 +0000 Subject: [PATCH 1/2] chore: add CI workflow (ruff + compose + yaml + patterns validation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo had no CI — tests/ exists with 348 lines but had never been exercised in PR review. Add a focused gate so future PRs don't silently ship broken Python or malformed pattern JSON. Jobs: - python-lint: ruff check + ruff format --check on the repo - compose-validate: docker compose -f docker-compose.yml config - yaml-lint: yaml.safe_load on every .yml/.yaml in the tree - patterns-validate: json.load on every patterns/*.json (pattern parsing failures crash server startup, so catching them at PR time is high value) All jobs PR-only with timeout-minutes set, mirroring the cost-defense rules used in the gwangcheon-shop pipeline. Also includes the ruff auto-fixes that the new lint job would catch on first run, so the workflow goes green from PR #1: - Drop unused `main.PATTERNS` import in tests/test_pattern_matching.py - Apply ruff format to main.py (one block was off) No behavior change in the analyzer itself. Pytest is intentionally deferred — running tests requires loading sentence-transformers and ChromaDB which is heavy for CI; will land in a follow-up PR with proper caching. --- .github/workflows/ci.yml | 91 ++++++++++++++++++++++++++++++++++ main.py | 20 ++++++-- tests/test_pattern_matching.py | 2 +- 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34a8a86 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: CI + +# PR 트리거 한정 — push마다 분당 비용을 누적하지 않는다. +on: + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + python-lint: + name: Python Lint (ruff) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: ruff 설치 + run: pip install ruff==0.5.7 + + - name: ruff 검사 + # main.py, learning_store.py, pattern_index.py, tests/ 전체 + run: ruff check . + + - name: ruff 포맷 검사 + run: ruff format --check . + + compose-validate: + name: docker-compose 구문 검증 + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - name: docker compose config 검증 + # 시크릿 미설정 상태에서도 schema/필수 키 검사 + run: docker compose -f docker-compose.yml config --quiet + + yaml-lint: + name: YAML 구문 검증 + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: 모든 YAML 파일 syntax 검증 + run: | + python3 -c " + import yaml, glob, sys + ok = True + for f in glob.glob('**/*.yml', recursive=True) + glob.glob('**/*.yaml', recursive=True): + try: + yaml.safe_load(open(f)) + print(f'OK {f}') + except yaml.YAMLError as e: + ok = False + print(f'FAIL {f}: {e}') + sys.exit(0 if ok else 1) + " + + # 패턴 JSON 파일 + tests 폴더 무결성 — 패턴 라이브러리가 깨지면 서버 기동 실패하므로 + # PR 단계에서 catch. + patterns-validate: + name: 패턴 JSON 검증 + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - name: patterns/*.json syntax 검증 + run: | + python3 -c " + import json, glob, sys + ok = True + for f in glob.glob('patterns/*.json'): + try: + with open(f) as fp: + json.load(fp) + print(f'OK {f}') + except json.JSONDecodeError as e: + ok = False + print(f'FAIL {f}: {e}') + sys.exit(0 if ok else 1) + " diff --git a/main.py b/main.py index 120e942..c873909 100644 --- a/main.py +++ b/main.py @@ -84,28 +84,33 @@ def _learned_case_to_result(semantic: Dict[str, Any]) -> "AnalysisResult": match_method="semantic", ) + # FastAPI 앱 초기화 app = FastAPI( title="AI DevOps Orchestrator API", description="LangChain 기반 자동 트러블슈팅 및 배포 파이프라인", - version="1.0.0" + version="1.0.0", ) # CORS 설정 app.add_middleware( CORSMiddleware, - allow_origins=os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:5678").split(","), + allow_origins=os.getenv( + "CORS_ORIGINS", "http://localhost:3000,http://localhost:5678" + ).split(","), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) + # 데이터 모델 class AnalysisRequest(BaseModel): error_log: str project_config: Dict[str, Any] framework: str = "unknown" + class AnalysisResult(BaseModel): pattern_type: str confidence: float @@ -114,6 +119,7 @@ class AnalysisResult(BaseModel): estimated_time_saved: str match_method: str = "none" # "exact" | "semantic" | "none" + # 헬스체크 엔드포인트 @app.get("/health") async def health_check(): @@ -132,6 +138,7 @@ async def health_check(): "note": "정확 매칭 + 의미 매칭(큐레이션 패턴 + 학습 케이스). LangChain/Redis 미구현.", } + # 에러 분석 엔드포인트 @app.post("/analyze", response_model=AnalysisResult) async def analyze_error(request: AnalysisRequest): @@ -200,7 +207,9 @@ class SearchRequest(BaseModel): async def semantic_search(request: SearchRequest): """ChromaDB 기반 패턴 의미 검색 (정확 매칭 없이 직접 호출).""" if not SEMANTIC_INDEX_READY: - raise HTTPException(status_code=503, detail="ChromaDB 의미 검색이 비활성화돼 있습니다") + raise HTTPException( + status_code=503, detail="ChromaDB 의미 검색이 비활성화돼 있습니다" + ) result = pattern_index.search(request.query, max_distance=request.max_distance) if result is None: return {"matched": False} @@ -216,6 +225,7 @@ async def semantic_search(request: SearchRequest): }, } + # 학습 엔드포인트 class LearnSuccessRequest(BaseModel): error_log: Optional[str] = None @@ -314,6 +324,8 @@ async def get_learning_cases(case_type: Optional[str] = None, limit: int = 50): "cases": learning_store.list_cases(case_type=case_type, limit=limit), } + if __name__ == "__main__": import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file + + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/tests/test_pattern_matching.py b/tests/test_pattern_matching.py index 3a41156..e088c0e 100644 --- a/tests/test_pattern_matching.py +++ b/tests/test_pattern_matching.py @@ -35,7 +35,7 @@ def test_exact_match_no_match(): def test_semantic_match_pure_rand_variant(): """정확 토큰 없이도 의미 기반으로 매칭되어야 한다.""" import pattern_index - from main import PATTERNS, SEMANTIC_INDEX_READY + from main import SEMANTIC_INDEX_READY if not SEMANTIC_INDEX_READY: import pytest From 5e555298fb3160e79e598b84230c07dabd7dd6b0 Mon Sep 17 00:00:00 2001 From: AI DevOps Orchestrator Team Date: Fri, 1 May 2026 01:52:29 +0000 Subject: [PATCH 2/2] fix(ci): install PyYAML before yaml-lint job setup-python only provides the interpreter; PyYAML is a separate pip package. Without it, the yaml-lint job hits ModuleNotFoundError on import. Add a pip install pyyaml step before running the validator. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34a8a86..b670829 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,9 @@ jobs: with: python-version: '3.11' + - name: PyYAML 설치 + run: pip install pyyaml + - name: 모든 YAML 파일 syntax 검증 run: | python3 -c "