Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 222 additions & 1 deletion .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ permissions:
contents: read
actions: write
statuses: write
checks: write


jobs:
Expand Down Expand Up @@ -157,6 +158,126 @@ jobs:
echo "Maven test failed after retries."
exit 1

- name: Detect whether module produced coverage data
id: coverage-detect
shell: bash
run: |
set -euo pipefail
service='${{ matrix.service }}'

has_coverage_exec=false
if [[ -s "$service/target/jacoco.exec" ]]; then
has_coverage_exec=true
fi

echo "has_coverage_exec=$has_coverage_exec" >> "$GITHUB_OUTPUT"

if [[ "$has_coverage_exec" != "true" ]]; then
echo "No JaCoCo execution data ($service/target/jacoco.exec); skipping report + threshold enforcement."
fi

- name: Generate JaCoCo report for ${{ matrix.service }}
if: steps.coverage-detect.outputs.has_coverage_exec == 'true'
shell: bash
run: |
set -euo pipefail
for attempt in 1 2 3; do
echo "Attempt $attempt: mvn jacoco:report (${{ matrix.service }})"
if mvn -B -ntp -pl ${{ matrix.service }} jacoco:report; then
break
fi
sleep $((attempt * 10))
done

if [[ ! -f "${{ matrix.service }}/target/site/jacoco/jacoco.xml" ]]; then
echo "JaCoCo report did not generate jacoco.xml; failing to avoid false green." >&2
exit 1
fi

- name: Enforce coverage threshold (70%) for ${{ matrix.service }}
if: steps.coverage-detect.outputs.has_coverage_exec == 'true'
env:
SERVICE: ${{ matrix.service }}
COVERAGE_THRESHOLD: '0.70'
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

service = os.environ['SERVICE']
threshold = float(os.environ.get('COVERAGE_THRESHOLD', '0.70'))
report_path = Path(service) / 'target' / 'site' / 'jacoco' / 'jacoco.xml'

if not report_path.exists():
print(f"ERROR: Missing JaCoCo XML report: {report_path}")
sys.exit(1)

root = ET.parse(report_path).getroot()
line_counter = None
for counter in root.findall('counter'):
if counter.get('type') == 'LINE':
line_counter = counter
break

if line_counter is None:
print("ERROR: No LINE counter found in JaCoCo XML.")
sys.exit(1)

missed = int(line_counter.get('missed', '0'))
covered = int(line_counter.get('covered', '0'))
total = missed + covered
ratio = (covered / total) if total else 0.0

print(f"Coverage (LINE) for {service}: {covered}/{total} = {ratio:.2%}")
print(f"Threshold: {threshold:.2%}")

if ratio + 1e-12 < threshold:
print("ERROR: Coverage below threshold.")
sys.exit(1)
PY

- name: Upload JaCoCo coverage report for ${{ matrix.service }}
if: always() && steps.coverage-detect.outputs.has_coverage_exec == 'true'
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.service }}
path: |
${{ matrix.service }}/target/site/jacoco/jacoco.xml
${{ matrix.service }}/target/site/jacoco/index.html
${{ matrix.service }}/target/site/jacoco/jacoco.csv
if-no-files-found: warn

- name: Upload placeholder coverage note (no tests/coverage)
if: always() && steps.coverage-detect.outputs.has_coverage_exec != 'true'
shell: bash
run: |
set -euo pipefail
service='${{ matrix.service }}'
out_dir="_coverage_placeholders/$service"
mkdir -p "$out_dir"
cat > "$out_dir/NO_COVERAGE.md" <<'EOF'
No JaCoCo coverage data was produced for this module in this run.

Common reasons:
- The module has no unit tests yet (Surefire: "No tests to run").
- Tests were skipped.

Action:
- Implement unit tests in Task 4; once tests execute, JaCoCo will generate jacoco.xml and CI will enforce the 70% threshold.
EOF

- name: Upload placeholder coverage artifact
if: always() && steps.coverage-detect.outputs.has_coverage_exec != 'true'
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.service }}
path: _coverage_placeholders/${{ matrix.service }}/NO_COVERAGE.md
if-no-files-found: warn

- name: Upload test results for ${{ matrix.service }}
if: always()
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -199,10 +320,110 @@ jobs:
echo "Maven build failed after retries."
exit 1

- name: Upload build artifact for ${{ matrix.service }}
if: always()
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.service }}
path: |
${{ matrix.service }}/target/*.jar
${{ matrix.service }}/target/*.war
!${{ matrix.service }}/target/*.jar.original
!${{ matrix.service }}/target/*-tests.jar
if-no-files-found: warn

test-summary:
name: Test Summary
needs: [detect-changes, test]
if: always() && needs.detect-changes.outputs.has_services == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Download test result artifacts
uses: actions/download-artifact@v4
with:
pattern: test-results-*
path: artifacts/test-results
merge-multiple: true

- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
pattern: coverage-*
path: artifacts/coverage
merge-multiple: false

- name: Publish JUnit test report
uses: dorny/test-reporter@v1
with:
name: Unit Tests
path: artifacts/test-results/**/*.xml
reporter: java-junit
fail-on-error: 'false'

- name: Publish coverage summary
env:
COVERAGE_THRESHOLD: '0.70'
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
import xml.etree.ElementTree as ET
from pathlib import Path

threshold = float(os.environ.get('COVERAGE_THRESHOLD', '0.70'))
root = Path('artifacts/coverage')

lines = []
lines.append('## Coverage Summary')
lines.append('')
lines.append(f'Threshold: **{threshold:.0%}** (LINE)')
lines.append('')
lines.append('| Module | LINE coverage | Status |')
lines.append('|---|---:|---|')

if not root.exists():
lines.append('| (none) | - | No coverage artifacts found |')
else:
entries = sorted([p for p in root.iterdir() if p.is_dir()])
for module_dir in entries:
module = module_dir.name.removeprefix('coverage-') if module_dir.name.startswith('coverage-') else module_dir.name
jacoco_xml = next(iter(module_dir.rglob('jacoco.xml')), None)
no_cov = next(iter(module_dir.rglob('NO_COVERAGE.md')), None)

if jacoco_xml and jacoco_xml.exists():
doc = ET.parse(jacoco_xml).getroot()
line_counter = None
for counter in doc.findall('counter'):
if counter.get('type') == 'LINE':
line_counter = counter
break
if line_counter is None:
lines.append(f'| {module} | (missing LINE counter) | ⚠️ invalid report |')
continue

missed = int(line_counter.get('missed', '0'))
covered = int(line_counter.get('covered', '0'))
total = missed + covered
ratio = (covered / total) if total else 0.0
status = 'PASS' if ratio + 1e-12 >= threshold else 'FAIL'
lines.append(f'| {module} | {ratio:.2%} ({covered}/{total}) | {status} |')
elif no_cov and no_cov.exists():
lines.append(f'| {module} | - | NO DATA (no tests/coverage) |')
else:
lines.append(f'| {module} | - | NO DATA (artifact missing expected files) |')

summary_path = Path(os.environ['GITHUB_STEP_SUMMARY'])
summary_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
PY

ci-gate:
name: CI Gate
runs-on: ubuntu-latest
needs: [detect-changes, test, build]
needs: [detect-changes, test, build, test-summary]
if: always()
steps:
- name: Evaluate results
Comment on lines 425 to 429
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Gitleaks Scan

on:
pull_request:
branches:
- main
- dev

jobs:
scan:
name: gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115 changes: 115 additions & 0 deletions WORKLOAD_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# WORKLOAD CHECKLIST — YAS CI/CD (Simple)

> Mục tiêu: checklist đơn giản để tick tiến độ theo các task trong PROJECT_INSTRUCTION.md.

## Task 1 — Setup repo + CI nền

### 1.1 Fork & repo settings
- [x] Fork repository từ https://github.com/nashtech-garage/yas
- [x] Clone repo về local
- [ ] Tạo branch `develop` từ `main`
- [x] Cấu hình rules/branch protection cho `main`:
- [x] Require a pull request before merging
- [x] Require 2 approvals before merging
- [x] Require status checks to pass: `CI Gate`
- [ ] Block force-push / delete branch (nếu đề yêu cầu)

### 1.2 Workflow CI ban đầu
- [x] Có workflow CI tập trung: `.github/workflows/ci-pipeline.yml`
- [x] Trigger cho push + pull_request
- [x] Permissions đủ cho artifacts + publish status

### 1.3 Monorepo build matrix
- [x] Xác định danh sách module theo `<modules>` trong root `pom.xml`
- [x] common-library
- [x] backoffice-bff
- [x] cart
- [x] customer
- [x] inventory
- [x] location
- [x] media
- [x] order
- [x] payment-paypal
- [x] payment
- [x] product
- [x] promotion
- [x] rating
- [x] search
- [x] storefront-bff
- [x] tax
- [x] webhook
- [x] sampledata
- [x] recommendation
- [x] delivery
- [x] Detect thay đổi và chỉ build/test module bị ảnh hưởng
- [ ] (Tuỳ chọn) `paths-ignore` / path filters cho docs-only

## Task 2 — Hoàn thiện CI (Test/Build/Artifacts/Coverage)

### 2.1 Chuẩn hoá test & build
- [x] Phase 1 (Test) chạy ổn (Maven): `mvn test` theo module
- [x] Upload test results (Surefire/Failsafe reports) làm artifact
- [x] Phase 2 (Build) chạy ổn (Maven): `mvn package` theo module (có upload build artifact)
- [ ] (Tuỳ chọn theo đề) Docker build/push image

### 2.2 Coverage (JaCoCo)
- [x] Bật JaCoCo để sinh coverage report (XML/HTML)
- [x] Upload coverage report làm artifact
- [x] Enforce coverage threshold ≥ 70% (hoặc theo đề)
- [x] Có Coverage Summary theo từng module (PASS/FAIL/NO DATA) trong job `Test Summary`
- [ ] (Tuỳ chọn) Coverage badge trên README

### 2.3 Test summary trong PR
- [x] Có PR-visible test summary (JUnit report)

Ghi chú trạng thái hiện tại:
- CI có thể fail do một số module coverage < 70% (xử lý ở Task 4 bằng cách viết thêm unit tests).

## Task 3 — Security / Quality gates

### 3.1 Gitleaks
- [ ] Thêm workflow scan secrets bằng Gitleaks
- [ ] Fail PR nếu phát hiện secret
- [ ] Có screenshot: secrets scan pass/fail + cấu hình (nếu có)

### 3.2 Sonar (SonarQube hoặc SonarCloud)
- [ ] Chọn nền tảng: SonarQube (self-host) hoặc SonarCloud (hosted)
- [ ] Cấu hình token/secret (`SONAR_TOKEN`)
- [ ] Sonar scan chạy được trên PR
- [ ] Quality Gate bật và chặn merge nếu fail
- [ ] Có screenshot: project settings + PR check

### 3.3 Snyk (optional)
- [ ] Cấu hình `SNYK_TOKEN`
- [ ] Snyk scan chạy được trên PR
- [ ] Có screenshot: PR check

## Task 4 — Unit tests theo service (nâng coverage)

### 4.1 Branch theo service
- [ ] Tạo branch `feature/add-unit-tests-<service>` cho từng service được phân công

### 4.2 Viết unit tests
- [ ] Thêm test cases vào `src/test/java/**`
- [ ] Coverage đạt ≥ 70% cho service được phân công
- [ ] CI pass: `CI Gate` + coverage + security (nếu đã bật)

### 4.3 PR & review flow
- [ ] Tạo PR vào `develop`/`main` (theo quy ước nhóm)
- [ ] Có đủ 2 approvals
- [ ] Merge khi CI pass

## Task 5 — Tổng hợp + tối ưu + báo cáo

### 5.1 Path filter / tối ưu monorepo
- [ ] (Tuỳ chọn) Thêm path filters để giảm CI runs khi chỉ đổi docs
- [ ] (Tuỳ chọn) Tối ưu caching/reusable workflow

### 5.2 Badge / notification
- [ ] (Tuỳ chọn) CI badge trên README
- [ ] (Tuỳ chọn) Notification (Slack/Teams)

### 5.3 Báo cáo + tài liệu nộp bài
- [ ] Tổng hợp link PRs + workflow runs
- [ ] Chụp ảnh: rulesets/branch protection + checks + sonar/gitleaks/snyk
- [ ] Viết báo cáo cuối (mô tả kiến trúc CI/CD, quyết định kỹ thuật, cách chạy, minh chứng)
Loading
Loading