Repository navigation
113 lines (111 loc) · 8.31 KB
/
Copy pathpython-ci.yml
File metadata and controls
113 lines (111 loc) · 8.31 KB
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
name: Python CI
on:
# `scripts/integration-coverage.*` — integration 잡의 리포트가 그 둘로 돈다. 리포트 로직·핀만 고친 PR 에서도
# 실제 데이터로 재야 한다(자가테스트는 픽스처만 본다).
push: { branches: [main], paths: ['python/**', '.github/workflows/python-ci.yml', 'scripts/integration-coverage.mjs', 'scripts/integration-coverage.json', 'scripts/check-hp-attest.mjs'] }
pull_request: { branches: [main], paths: ['python/**', '.github/workflows/python-ci.yml', 'scripts/integration-coverage.mjs', 'scripts/integration-coverage.json', 'scripts/check-hp-attest.mjs'] }
# 주간 레인 건강검진(환경 드리프트) — 근거는 .claude/rules/ci.md, 슬롯 표는 ci.yml 옆 주석.
workflow_dispatch:
schedule:
- cron: '27 5 * * 1'
# 최소권한: 이 워크플로의 잡은 소스를 읽고 빌드/검사만 한다(체크·코멘트·릴리스 쓰기 없음).
permissions:
contents: read
jobs:
test:
name: Lint + unit test (Python ${{ matrix.python }})
runs-on: ubuntu-latest
strategy:
# ⚠️ 하한 레그가 취소되면 그날 하한 검증이 사라진다 — 근거는 node-ci.yml 주석.
fail-fast: false
# 3.14 는 현 최신 안정판. 하한 3.10 은 그대로 둔다 — 상류 EOL 이 가까워도 소비자를 자르지 않는다.
matrix: { python: ['3.10', '3.11', '3.12', '3.13', '3.14'] }
defaults: { run: { working-directory: python } }
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
with: { python-version: '${{ matrix.python }}' }
# ⚠️ `--only-binary :all:` 는 **index 후보에만** 걸린다(pip FormatControl). `-e .` 는 로컬 경로
# 요구라 finder 를 건너뛰고 PEP 660 으로 빌드되므로 둘은 충돌하지 않는다 — "editable 과 안
# 맞는다"고 지우지 말 것. 막는 대상은 **다운로드된 서드파티 sdist** 의 setup.py 실행이고
# (sonar `githubactions:S8541`), 휠 없는 전이가 생기면 조용히 sdist 를 빌드하는 대신
# **fail-closed** 로 죽는다 — 그것이 의도다.
# 실측(pip 26.1.2 · 빈 venv 에서 index 왕복): 39 패키지 전부 휠 해석 · exit 0.
# ⚠️ **작은따옴표를 지우지 말 것** — `:all:` 뒤의 `: `(콜론+공백)이 YAML 평문 스칼라를 끊어
# `mapping values are not allowed here` 로 워크플로가 파싱조차 안 된다(실측). 블록
# 스칼라(`run: |`) 안에서는 리터럴이라 인용이 필요 없다 — 그래서 두 형태가 섞여 있다.
- run: 'python -m pip install --only-binary :all: -e ".[dev]"'
- name: ruff (lint — 보안 S/bandit 포함 확장 룰셋)
run: ruff check src tests examples
- name: ruff (format check)
run: ruff format --check src tests examples
- name: pytest (unit + 커버리지 게이트 100%)
# attest — 적대 경로 행렬은 마지막 assert 뒤에만 $HP_ATTEST_DIR/python.json 을 쓴다(근거: ci.yml 의 같은 단계).
env:
HP_ATTEST_DIR: ${{ runner.temp }}/hp-attest
HP_ATTEST_NONCE: ${{ github.run_id }}.${{ github.run_attempt }}.${{ github.job }}.${{ strategy.job-index }}
run: pytest -m "not integration" --cov=keycloak_sdk # 임계값은 pyproject [tool.coverage.report] fail_under=100
- name: 적대 경로 행렬 attest — 이 실행에서 돌았는가(scripts/check-hp-attest.mjs)
working-directory: .
env:
HP_ATTEST_DIR: ${{ runner.temp }}/hp-attest
HP_ATTEST_NONCE: ${{ github.run_id }}.${{ github.run_attempt }}.${{ github.job }}.${{ strategy.job-index }}
run: node scripts/check-hp-attest.mjs "$HP_ATTEST_DIR" --nonce "$HP_ATTEST_NONCE" --lang python
- name: mypy (strict)
run: mypy src
- name: pip-audit (의존성 취약점 감사)
# editable 로컬 패키지(keycloak-sdk, PyPI 미배포)는 감사 불가로 skip되고, pip-audit는
# skip이 하나라도 있으면 종료코드 1(불완전 감사 신호)을 낸다(--skip-editable도 라벨만 바꿈).
# 환경 대신 editable을 제외한 frozen requirements를 감사해 실 의존성만 커버한다.
# ⚠️ freeze 는 pip-audit 를 깔기 **전**에 — 뒤에 뜨면 감사 도구 자신의 의존성까지 감사한다.
# 실측(2026-10-26, PR #836): Python 3.10 레인이 pip-audit 의 filelock 4.1.0(4.2.1 은 3.11+)에
# 공개된 CVE 여섯으로 빨갰고, 그 filelock 은 pip-audit 를 깔 때 새로 들어온 것이었다.
run: |
python -m pip freeze --exclude-editable > audit-reqs.txt
python -m pip install --only-binary :all: pip-audit
pip-audit -r audit-reqs.txt
integration:
name: Integration tests (testcontainers, 실제 Keycloak)
runs-on: ubuntu-latest # ubuntu 러너는 Docker 내장 → testcontainers 동작
defaults: { run: { working-directory: python } }
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
with: { python-version: '3.12' }
- run: 'python -m pip install --only-binary :all: -e ".[dev]"'
# 같은 `pytest -m integration` 을 coverage.py 밑에서 돌린다(단위 omit 없이 · `source` 는 파생 파일의 공통
# 디렉터리 — 한 번도 import 안 된 경계 파일도 0% 로 든다). 명령은 `scripts/integration-coverage.mjs` 의
# run 이 소유한다 — 로컬과 CI 가 같은 명령을 쓰게 여기 다시 적지 않는다. 종료코드는 테스트의 것이다.
- name: pytest (integration, Docker) + 경계 커버리지 수집
working-directory: .
run: node scripts/integration-coverage.mjs run python --out "$RUNNER_TEMP/integration-coverage"
# ⚠️ 보고 모드 — 낮은 수치로는 실패하지 않는다. 하한은 CI 관측 세 번 뒤 사람이 정한다(등록부
# `integration-coverage-never-measured` 4 걸음째). exit 2(파생 실패 · 핀과 다름 · 데이터에서 경계 파일
# 빠짐 · 아무것도 안 돈 실행 · 함수 영역 회계 불일치)만 잡을 실패시킨다. exit 1(소유 문제)은 경고다.
- name: 통합 전용 경계 커버리지 리포트 (보고 모드 — 하한 없음)
working-directory: .
run: |
rc=0
node scripts/integration-coverage.mjs report python --out "$RUNNER_TEMP/integration-coverage" || rc=$?
if [ "$rc" -eq 1 ]; then echo "::warning::통합 미실행 함수의 소유 문제 — 위 DRIFT 줄(보고 모드라 게이트가 아니다)"; fi
[ "$rc" -le 1 ]
api-compat:
name: griffe (공개 API 파괴적 변경)
runs-on: ubuntu-latest
# ⚠️ **이 잡만 `working-directory: python` 을 쓰지 않는다.** griffe 는 기준선 태그를 임시
# 워크트리로 꺼내고 `--search` 를 **그 워크트리 루트** 기준으로 해석한다 — `python` 안에서
# 돌리며 `--search src` 를 주면 `<워크트리>/src` 를 뒤져 패키지를 못 찾는다(실측:
# `ModuleNotFoundError: No module named 'keycloak_sdk'`). 양쪽 다 저장소 루트 기준이어야 맞다.
# ⚠️ 1.0 은 「파괴적 변경 없이는 major 를 올리지 않는다」는 약속이고, 지킬 수단 없이 하는
# 약속은 이 저장소가 거절하는 부류다(「기계만 셀 수 있다」). rust 의 cargo-semver-checks 와
# 같은 자리다 — 기준선은 **직전 릴리스 태그**이고, 거기서 공개 시그니처가 사라지거나
# 바뀌면 실패한다. ⚠️ griffe 는 아티팩트가 아니라 **git ref** 를 기준선으로 받으므로
# `fetch-depth: 0` 이 필요하다(얕은 체크아웃이면 태그를 못 찾고 조용히 비교를 건너뛴다).
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with: { fetch-depth: 0 }
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
with: { python-version: '3.12' }
- run: 'python -m pip install --only-binary :all: griffe'
- name: griffe check (직전 릴리스 태그 대비)
run: griffe check keycloak_sdk --search python/src --against py-v1.0.7