Skip to content

Commit 921c1f3

Browse files
authored
Merge pull request #6 from kc3hack/chore/ci-setup
chore: Setup CI workflows for Backend and Frontend
2 parents f114a0b + 6a182db commit 921c1f3

File tree

7 files changed

+458
-2
lines changed

7 files changed

+458
-2
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# ADR 001: CI Strategy & Tool Selection
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Context
8+
9+
プロジェクトの初期段階において、Backend (FastAPI) と Frontend (Next.js) のコード品質とデプロイ可能性を保証するためのCIパイプラインが必要でした。
10+
特に、Python環境におけるリンター/フォーマッターの選定と、Next.jsビルド時の環境変数依存性が課題となっていました。
11+
12+
## Decision
13+
14+
### 1. Backend: Ruff & Pytest
15+
16+
- **Decision**: Pythonのリンター/フォーマッターとして `Ruff` を採用し、テストランナーとして `pytest` を採用する。
17+
- **Reason**:
18+
- `Ruff` は Rust製で極めて高速であり、従来の `Flake8`, `Black`, `isort` などの機能を単体でカバーできるため、CI時間を短縮し設定を簡素化できる。
19+
- `pytest` はPythonエコシステムのデファクトスタンダードであり、将来的な拡張性が高い。
20+
21+
### 2. Frontend: npm ci & Mock Env
22+
23+
- **Decision**: 依存関係インストールに `npm ci` を使用し、ビルド時にダミーの環境変数を注入する。
24+
- **Reason**:
25+
- `npm ci``package-lock.json` に厳密に基づいたインストールを行うため、CI環境での再現性が保証される。
26+
- Next.jsのビルドプロセス(Static Generation等)でAPI URLなどの環境変数が参照される可能性があるため、CI上ではダミー値を設定してビルドエラーを防ぐ戦略をとる。
27+
28+
### 3. Workflow Separation
29+
30+
- **Decision**: `backend.yml``frontend.yml` にワークフローを分離し、`paths` フィルタを設定する。
31+
- **Reason**:
32+
- モノレポ構成において、関連しない変更(例:Frontendのみの修正)でBackendのCIが走ることを防ぎ、リソース消費とフィードバック時間を最適化するため。
33+
34+
### 4. Dependency Version Pinning (Supply Chain Security)
35+
36+
- **Decision**: Python依存関係を `*` (any version) から具体的なバージョン範囲 (例: `^0.115.0`) に固定する。
37+
- **Reason**:
38+
- `*` 指定では、PyPIから常に最新版が取得されるため、パッケージが乗っ取られた場合(supply-chain attack)、悪意あるコードがCI環境や本番環境で実行されるリスクがある。
39+
- バージョン範囲を固定することで、依存関係の更新を意図的・管理的に行い、セキュリティリスクを低減する。
40+
41+
### 5. GitHub Actions Version Pinning
42+
43+
- **Decision**: `trufflesecurity/trufflehog@main` をタグバージョン(例: `@v3.82.13`)に固定する。
44+
- **Reason**:
45+
- `@main` ブランチ参照は上流の変更によってCI動作が予期せず変わる可能性があり、サプライチェーンリスクが高い。
46+
- タグやコミットSHAに固定することで、CI実行内容の不変性と再現性を担保する。
47+
48+
### 6. Test Exit Code Handling
49+
50+
- **Decision**: `pytest || echo ...` の代わりに、exit code 5(テスト未検出)のみを許容し、実際のテスト失敗は検知する。
51+
- **Reason**:
52+
- `|| echo` は全てのエラーを握りつぶしてしまい、テストが実際に失敗してもCIが成功扱いになる。
53+
- exit code を判定することで、「テストがまだない状態」と「テストが失敗した状態」を正確に区別できる。
54+
55+
### 7. Event-Specific Git References in Security Scan
56+
57+
- **Decision**: `github.head_ref` / `github.event.repository.default_branch` の代わりに、イベント種別に応じた適切なSHA参照を使用する。
58+
- **Reason**:
59+
- `github.head_ref` は push イベントでは空文字になるため、差分スキャンが成立しない。
60+
- PR時は `github.event.pull_request.{base,head}.sha`、push時は `github.event.before` / `github.sha` を使用することで、正確な差分スキャンを実現する。
61+
62+
### 8. Lightweight Security Strategy (3-Layer Approach)
63+
64+
- **Decision**: 重量級スキャン(Syft+Grype)を週次実行に変更し、PR時は軽量ツールを使用する。
65+
- **Reason**:
66+
- Syft+Grypeは3〜5分かかり、PR体験を損なう。
67+
- ビルトインツール(pip-audit / npm audit)は数秒で完了。
68+
- Dependabotが常時監視し、自動PR作成。
69+
- **3層構造**:
70+
1. **Dependabot(常時)**: GitHub標準機能、CI負荷ゼロ、自動PR作成
71+
2. **pip-audit / npm audit(PR時)**: 秒単位、PRをブロックしない(continue-on-error)
72+
3. **Syft+Grype(週次)**: 詳細スキャン、結果をIssue化して追跡
73+
74+
### 9. Vulnerability Exception Management
75+
76+
- **Decision**: `.grype.yaml` でCritical脆弱性の除外設定を許可する。
77+
- **Reason**:
78+
- すべての脆弱性が実際のリスクとなるわけではない(未使用機能、誤検知等)。
79+
- 修正が存在しない場合や、他の制御で緩和されている場合の対応が必要。
80+
- 除外には文書化された正当な理由(notes)を必須とし、四半期ごとにレビューする運用を前提とする。
81+
82+
## Consequences
83+
84+
- Backend開発者はローカルでも `ruff` を使用してコード規約を遵守する必要がある。
85+
- FrontendビルドがCIで成功しても、実行時エラー(環境変数設定ミスなど)は検知できないため、別途E2Eテストなどの検討が必要になる可能性がある。
86+
- 依存関係のバージョンを固定したため、定期的なアップデート戦略(Dependabotなど)が必要になる。
87+
- TruffleHogなどのツールバージョンを固定したため、新機能や修正を取り込むには手動更新が必要。
88+
- **PR CI時間が大幅短縮**: 重量級スキャンを週次に移行したことで、PRのCI時間が5分短縮される。
89+
- **Dependabotが常時監視**: GitHub標準機能により、CI負荷なしで脆弱性を検出し自動PR作成。
90+
- **週次スキャンは追跡**: Grypeの結果はIssueとして記録され、チームで対応を追跡できる。
91+
- `.grype.yaml` での除外管理には厳格な運用ルール(文書化、定期レビュー)が必須。

.github/dependabot.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
version: 2
2+
updates:
3+
# Backend: Python dependencies
4+
- package-ecosystem: "pip"
5+
directory: "/backend"
6+
schedule:
7+
interval: "weekly"
8+
day: "monday"
9+
time: "09:00"
10+
open-pull-requests-limit: 5
11+
labels:
12+
- "dependencies"
13+
- "security"
14+
commit-message:
15+
prefix: "chore"
16+
include: "scope"
17+
18+
# Frontend: npm dependencies
19+
- package-ecosystem: "npm"
20+
directory: "/frontend"
21+
schedule:
22+
interval: "weekly"
23+
day: "monday"
24+
time: "09:00"
25+
open-pull-requests-limit: 5
26+
labels:
27+
- "dependencies"
28+
- "security"
29+
commit-message:
30+
prefix: "chore"
31+
include: "scope"
32+
33+
# Docker dependencies
34+
- package-ecosystem: "docker"
35+
directory: "/backend"
36+
schedule:
37+
interval: "weekly"
38+
day: "monday"
39+
time: "09:00"
40+
labels:
41+
- "dependencies"
42+
- "docker"
43+
44+
- package-ecosystem: "docker"
45+
directory: "/frontend"
46+
schedule:
47+
interval: "weekly"
48+
day: "monday"
49+
time: "09:00"
50+
labels:
51+
- "dependencies"
52+
- "docker"
53+
54+
# GitHub Actions
55+
- package-ecosystem: "github-actions"
56+
directory: "/"
57+
schedule:
58+
interval: "weekly"
59+
day: "monday"
60+
time: "09:00"
61+
labels:
62+
- "dependencies"
63+
- "github-actions"

.github/workflows/backend.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Backend CI
2+
3+
on:
4+
push:
5+
branches: ["main", "develop"]
6+
paths:
7+
- "backend/**"
8+
- ".github/workflows/backend.yml"
9+
pull_request:
10+
types: [opened, synchronize, reopened]
11+
paths:
12+
- "backend/**"
13+
- ".github/workflows/backend.yml"
14+
15+
defaults:
16+
run:
17+
working-directory: ./backend
18+
19+
jobs:
20+
check:
21+
name: Lint & Test (Python)
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout code
26+
uses: actions/checkout@v4
27+
28+
- name: Install Poetry
29+
run: pipx install poetry
30+
31+
- name: Set up Python 3.13
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: "3.13"
35+
# Note: cache removed temporarily until poetry.lock is committed
36+
# Decision: Generate lock file during CI to avoid commit noise
37+
# Reason: In early development, lockfile can be generated on-the-fly
38+
39+
- name: Install Dependencies
40+
# Decision: Require poetry.lock for reproducible builds
41+
# Reason: Without lockfile, dependency resolution is non-deterministic
42+
run: poetry install --no-interaction --no-root
43+
44+
# Decision: Use Ruff for fast linting
45+
# Reason: Ruff is significantly faster than Flake8/Black and covers both roles.
46+
- name: Run Lint (Ruff)
47+
run: poetry run ruff check .
48+
49+
# Decision: Lightweight security check with pip-audit (pinned in pyproject.toml)
50+
# Reason: Fast vulnerability check (seconds) without blocking PR
51+
- name: Security Scan (pip-audit)
52+
run: poetry run pip-audit --require-hashes=false || echo "Vulnerabilities found, see Dependabot for details"
53+
continue-on-error: true
54+
55+
# Decision: Run Pytest even if no tests exist yet (ensure setup works)
56+
# Reason: Validates that the test runner environment is correctly configured.
57+
# Security: Only tolerate exit code 5 (no tests found), fail on actual test failures
58+
- name: Run Tests
59+
run: |
60+
poetry run pytest || EXIT_CODE=$?
61+
if [ "${EXIT_CODE:-0}" -eq 5 ]; then
62+
echo "No tests found, but runner works"
63+
exit 0
64+
elif [ "${EXIT_CODE:-0}" -ne 0 ]; then
65+
echo "Tests failed with exit code $EXIT_CODE"
66+
exit $EXIT_CODE
67+
fi

.github/workflows/frontend.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Frontend CI
2+
3+
on:
4+
push:
5+
branches: ["main", "develop"]
6+
paths:
7+
- "frontend/**"
8+
- ".github/workflows/frontend.yml"
9+
pull_request:
10+
types: [opened, synchronize, reopened]
11+
paths:
12+
- "frontend/**"
13+
- ".github/workflows/frontend.yml"
14+
15+
defaults:
16+
run:
17+
working-directory: ./frontend
18+
19+
jobs:
20+
build:
21+
name: Build & Lint (Next.js)
22+
runs-on: ubuntu-latest
23+
24+
env:
25+
# Decision: Mock environment variables for CI build
26+
# Reason: Next.js build phases might access env vars.
27+
# CI should not depend on real production secrets unless necessary for e2e.
28+
NEXT_PUBLIC_API_URL: "http://localhost:8000"
29+
30+
steps:
31+
- name: Checkout code
32+
uses: actions/checkout@v4
33+
34+
- name: Set up Node.js
35+
uses: actions/setup-node@v4
36+
with:
37+
node-version: "20"
38+
cache: "npm"
39+
cache-dependency-path: frontend/package-lock.json
40+
41+
- name: Install Dependencies
42+
# Decision: Use `npm ci` instead of `npm install`
43+
# Reason: Ensures clean, reproducible installs based on lockfile.
44+
run: npm ci
45+
46+
- name: Run Lint
47+
run: npm run lint
48+
49+
# Decision: Lightweight security check with npm audit (built-in tool)
50+
# Reason: Fast vulnerability check (seconds) without blocking PR
51+
- name: Security Scan (npm audit)
52+
run: npm audit --audit-level=critical || echo "Vulnerabilities found, see Dependabot for details"
53+
continue-on-error: true
54+
55+
- name: Run Build
56+
# This checks for type errors and build capability
57+
run: npm run build

0 commit comments

Comments
 (0)