You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
9.6 KiB
YAML
276 lines
9.6 KiB
YAML
version: '3'
|
|
|
|
tasks:
|
|
security-scan:
|
|
desc: Run full CI quality and security checks
|
|
cmds:
|
|
- task: prepare-reports
|
|
- task: backend-lint
|
|
- task: frontend-lint
|
|
- task: unit-tests
|
|
- task: integration-tests
|
|
- task: enforce-backend-coverage
|
|
- task: docker-build-validate
|
|
- task: k6-smoke
|
|
- task: gosec-scan
|
|
- task: trivy-fs-scan
|
|
- task: trivy-image-scan
|
|
|
|
prepare-reports:
|
|
internal: true
|
|
cmds:
|
|
- mkdir -p reports/security reports/tests reports/tests/coverage reports/docker reports/perf
|
|
|
|
backend-lint:
|
|
cmds:
|
|
- |
|
|
set -eu
|
|
for module in \
|
|
services/admin-service \
|
|
services/game-session-service \
|
|
services/gateway-service \
|
|
services/leaderboard-service \
|
|
services/question-bank-service \
|
|
services/user-service \
|
|
shared
|
|
do
|
|
(cd "backend/${module}" && golangci-lint run ./...)
|
|
done
|
|
|
|
frontend-lint:
|
|
cmds:
|
|
- cd frontend && yarn lint
|
|
- cd frontend && yarn format:check
|
|
|
|
unit-tests:
|
|
cmds:
|
|
- |
|
|
bash -o pipefail -c '
|
|
set -eu
|
|
mkdir -p reports/tests reports/tests/coverage
|
|
: > reports/tests/backend-unit.log
|
|
ROOT="$(pwd)"
|
|
mkdir -p "${ROOT}/.cache/go-build"
|
|
for module in \
|
|
services/admin-service \
|
|
services/game-session-service \
|
|
services/gateway-service \
|
|
services/leaderboard-service \
|
|
services/question-bank-service \
|
|
services/user-service \
|
|
shared
|
|
do
|
|
profile="${ROOT}/reports/tests/coverage/${module//\//-}-unit.out"
|
|
(
|
|
cd "backend/${module}"
|
|
pkgs="$(GOCACHE="${ROOT}/.cache/go-build" go list ./...)"
|
|
pkgs="$(echo "${pkgs}" | grep -v '/tests$' || true)"
|
|
if [ -z "${pkgs}" ]; then
|
|
echo "No unit-test packages for backend/${module}"
|
|
exit 0
|
|
fi
|
|
GOCACHE="${ROOT}/.cache/go-build" go test -v -race -covermode=atomic -coverpkg=./... -coverprofile="${profile}" ${pkgs}
|
|
) | tee -a "${ROOT}/reports/tests/backend-unit.log"
|
|
done
|
|
'
|
|
- bash -o pipefail -c 'cd frontend && CI=1 yarn test | tee ../reports/tests/frontend-unit.log'
|
|
|
|
integration-tests:
|
|
cmds:
|
|
- |
|
|
bash -o pipefail -c '
|
|
set -eu
|
|
mkdir -p reports/tests reports/tests/coverage
|
|
: > reports/tests/backend-integration.log
|
|
ROOT="$(pwd)"
|
|
mkdir -p "${ROOT}/.cache/go-build"
|
|
for module in \
|
|
services/admin-service \
|
|
services/game-session-service \
|
|
services/gateway-service \
|
|
services/leaderboard-service \
|
|
services/question-bank-service \
|
|
services/user-service
|
|
do
|
|
if [ ! -d "backend/${module}/tests" ]; then
|
|
continue
|
|
fi
|
|
profile="${ROOT}/reports/tests/coverage/${module//\//-}-integration.out"
|
|
(
|
|
cd "backend/${module}"
|
|
GOCACHE="${ROOT}/.cache/go-build" go test -v -covermode=atomic -coverpkg=./... -coverprofile="${profile}" ./tests/...
|
|
) | tee -a "${ROOT}/reports/tests/backend-integration.log"
|
|
done
|
|
'
|
|
|
|
enforce-backend-coverage:
|
|
desc: Aggregate backend service coverage and enforce stepwise thresholds up to 80%
|
|
cmds:
|
|
- |
|
|
bash -o pipefail -c '
|
|
set -eu
|
|
mkdir -p reports/tests reports/tests/coverage
|
|
COVERAGE_DIR="reports/tests/coverage"
|
|
COMBINED="${COVERAGE_DIR}/backend-combined.out"
|
|
SUMMARY="reports/tests/backend-coverage-summary.txt"
|
|
THRESHOLDS="${BACKEND_COVERAGE_THRESHOLDS:-60 70 80}"
|
|
|
|
rm -f "${COMBINED}"
|
|
echo "mode: atomic" > "${COMBINED}"
|
|
|
|
merged_input=""
|
|
for profile in "${COVERAGE_DIR}"/*.out; do
|
|
[ -f "${profile}" ] || continue
|
|
[ "${profile}" = "${COMBINED}" ] && continue
|
|
case "${profile}" in
|
|
*shared-unit.out) continue ;;
|
|
esac
|
|
merged_input="${merged_input} ${profile}"
|
|
done
|
|
|
|
if [ -z "${merged_input}" ]; then
|
|
echo "No backend coverage profiles were generated." | tee "${SUMMARY}"
|
|
exit 1
|
|
fi
|
|
|
|
# Deduplicate statement blocks across unit/integration profiles by keeping max count per block.
|
|
# shellcheck disable=SC2086
|
|
tail -n +2 ${merged_input} | sort -k1,1 | awk "
|
|
{
|
|
if (NR == 1) {
|
|
prev = \$1
|
|
stmt = \$2
|
|
max = \$3
|
|
next
|
|
}
|
|
if (\$1 != prev) {
|
|
print prev, stmt, max
|
|
prev = \$1
|
|
stmt = \$2
|
|
max = \$3
|
|
next
|
|
}
|
|
if ((\$3 + 0) > (max + 0)) {
|
|
max = \$3
|
|
}
|
|
}
|
|
END {
|
|
if (NR > 0) {
|
|
print prev, stmt, max
|
|
}
|
|
}
|
|
" >> "${COMBINED}"
|
|
|
|
total_stats="$(awk "
|
|
NR > 1 {
|
|
split(\$1, loc, \":\")
|
|
path = loc[1]
|
|
pkg = path
|
|
sub(/\\/[^\\/]*$/, \"\", pkg)
|
|
|
|
# Focus on service application/domain and HTTP interface logic.
|
|
if (pkg !~ /^knowfoolery\\/backend\\/services\\//) {
|
|
next
|
|
}
|
|
if (pkg !~ /\\/internal\\/(application|domain)\\// &&
|
|
pkg !~ /\\/internal\\/interfaces\\/http($|\\/)/) {
|
|
next
|
|
}
|
|
|
|
total += \$2
|
|
if ((\$3 + 0) > 0) {
|
|
covered += \$2
|
|
}
|
|
}
|
|
END {
|
|
if (total == 0) {
|
|
printf \"0.00 0 0\"
|
|
} else {
|
|
printf \"%.2f %d %d\", (covered/total)*100, covered, total
|
|
}
|
|
}
|
|
" "${COMBINED}")"
|
|
total_pct="$(echo "${total_stats}" | awk "{print \$1}")"
|
|
covered_stmt="$(echo "${total_stats}" | awk "{print \$2}")"
|
|
total_stmt="$(echo "${total_stats}" | awk "{print \$3}")"
|
|
|
|
{
|
|
echo "Backend service (application/domain/interfaces-http) coverage: ${total_pct}%"
|
|
echo "Covered statements: ${covered_stmt}/${total_stmt}"
|
|
echo "Threshold progression: ${THRESHOLDS}%"
|
|
} | tee "${SUMMARY}"
|
|
|
|
for threshold in ${THRESHOLDS}; do
|
|
awk -v coverage="${total_pct}" -v threshold="${threshold}" "BEGIN { exit((coverage+0) < (threshold+0)) }"
|
|
done
|
|
'
|
|
|
|
docker-build-validate:
|
|
cmds:
|
|
- bash -o pipefail -c 'set -eu; for service in gateway game-session question-bank user leaderboard admin; do docker build -f "infrastructure/services/${service}.Dockerfile" -t "knowfoolery/${service}:ci" . | tee "reports/docker/${service}-build.log"; done'
|
|
|
|
k6-smoke:
|
|
desc: Run k6 smoke profile for critical gateway paths
|
|
cmds:
|
|
- |
|
|
bash -o pipefail -c '
|
|
set -eu
|
|
mkdir -p reports/perf
|
|
if ! command -v k6 >/dev/null 2>&1; then
|
|
echo "k6 is not installed; skipping smoke load tests."
|
|
exit 0
|
|
fi
|
|
if [ -z "${K6_BASE_URL:-}" ]; then
|
|
echo "K6_BASE_URL is not set; skipping k6 smoke load tests."
|
|
exit 0
|
|
fi
|
|
k6 run \
|
|
--quiet \
|
|
--env K6_PROFILE=smoke \
|
|
--env K6_BASE_URL="${K6_BASE_URL}" \
|
|
--env K6_AUTH_TOKEN="${K6_AUTH_TOKEN:-}" \
|
|
--out json=reports/perf/k6-smoke.json \
|
|
tests/load/k6/gateway-critical.js | tee reports/perf/k6-smoke.log
|
|
'
|
|
|
|
k6-load:
|
|
desc: Run k6 local load profile for critical gateway paths
|
|
cmds:
|
|
- |
|
|
bash -o pipefail -c '
|
|
set -eu
|
|
mkdir -p reports/perf
|
|
: "${K6_BASE_URL:?K6_BASE_URL is required}"
|
|
if ! command -v k6 >/dev/null 2>&1; then
|
|
echo "k6 is not installed."
|
|
exit 1
|
|
fi
|
|
k6 run \
|
|
--env K6_PROFILE=load \
|
|
--env K6_BASE_URL="${K6_BASE_URL}" \
|
|
--env K6_AUTH_TOKEN="${K6_AUTH_TOKEN:-}" \
|
|
--out json=reports/perf/k6-load.json \
|
|
tests/load/k6/gateway-critical.js | tee reports/perf/k6-load.log
|
|
'
|
|
|
|
gosec-scan:
|
|
cmds:
|
|
- bash -o pipefail -c 'set -eu; mkdir -p reports/security; set +e; gosec -fmt sarif -out reports/security/gosec.sarif ./backend/services/admin-service/... ./backend/services/game-session-service/... ./backend/services/gateway-service/... ./backend/services/leaderboard-service/... ./backend/services/question-bank-service/... ./backend/services/user-service/... ./backend/shared/... 2>&1 | tee reports/security/gosec.log; status=${PIPESTATUS[0]}; set -e; if grep -q "Panic when running SSA analyzer" reports/security/gosec.log || grep -q "file requires newer Go version" reports/security/gosec.log; then echo "gosec runtime/toolchain panic detected; treating as non-blocking tool failure."; exit 0; fi; exit "${status}"'
|
|
|
|
trivy-fs-scan:
|
|
cmds:
|
|
- trivy fs --format json --output reports/security/trivy-fs.json --severity HIGH,CRITICAL --exit-code 1 .
|
|
|
|
trivy-image-scan:
|
|
cmds:
|
|
- |
|
|
set -eu
|
|
for service in gateway game-session question-bank user leaderboard admin; do
|
|
trivy image \
|
|
--format json \
|
|
--output "reports/security/trivy-image-${service}.json" \
|
|
--severity HIGH,CRITICAL \
|
|
--exit-code 1 \
|
|
"knowfoolery/${service}:ci"
|
|
done
|