chore: standardize security and quality foundations
This commit is contained in:
@@ -6,13 +6,21 @@ cd "$repo_root"
|
||||
|
||||
python_bin="${PYTHON_BIN:-python3}"
|
||||
|
||||
echo "Installing backend Python requirements"
|
||||
"$python_bin" -m pip install -r backend/requirements.txt
|
||||
echo "Installing backend Python requirements and quality tools"
|
||||
"$python_bin" -m pip install -r backend/requirements-dev.txt
|
||||
|
||||
echo "Running Python dependency integrity check"
|
||||
"$python_bin" -m pip check
|
||||
|
||||
echo "Running backend unit tests"
|
||||
"$python_bin" -m unittest discover -s backend/tests -p "test_*.py" -v
|
||||
echo "Auditing Python production dependencies"
|
||||
"$python_bin" -m pip_audit -r backend/requirements.txt --progress-spinner off
|
||||
|
||||
echo "Linting backend application code"
|
||||
"$python_bin" -m ruff check backend/app
|
||||
|
||||
echo "Running backend unit tests with coverage"
|
||||
"$python_bin" -m coverage erase
|
||||
"$python_bin" -m coverage run -m unittest discover -s backend/tests -p "test_*.py" -v
|
||||
"$python_bin" -m coverage report
|
||||
|
||||
echo "Backend quality gate passed"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
container_name="magent-ci-${GITHUB_RUN_ID:-local}-$$"
|
||||
data_dir="$(mktemp -d)"
|
||||
chmod 0777 "$data_dir"
|
||||
cleanup() {
|
||||
docker rm -f "$container_name" >/dev/null 2>&1 || true
|
||||
rm -rf "$data_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
docker build --tag magent:ci .
|
||||
docker run --detach --name "$container_name" \
|
||||
--read-only --cap-drop ALL --security-opt no-new-privileges:true \
|
||||
--tmpfs /tmp:rw,noexec,nosuid,size=64m,uid=1000,gid=1000 \
|
||||
--tmpfs /app/frontend/.next/cache:rw,noexec,nosuid,size=128m,uid=1000,gid=1000 \
|
||||
--volume "$data_dir:/app/data" \
|
||||
--env JWT_SECRET=ci-only-secret-with-at-least-32-characters \
|
||||
--env SETTINGS_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= \
|
||||
--env ADMIN_PASSWORD=ci-only-bootstrap-password-123 \
|
||||
magent:ci >/dev/null
|
||||
|
||||
deadline=$((SECONDS + 120))
|
||||
until [ "$(docker inspect --format '{{.State.Health.Status}}' "$container_name")" = "healthy" ]; do
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
docker logs "$container_name"
|
||||
echo "Container did not become healthy within 120 seconds" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker exec "$container_name" curl --fail --silent --show-error http://127.0.0.1:8000/health >/dev/null
|
||||
docker exec "$container_name" curl --fail --silent --show-error http://127.0.0.1:3000/login >/dev/null
|
||||
@@ -6,7 +6,6 @@ cd "$repo_root"
|
||||
|
||||
deploy_host="${DEPLOY_HOST:-AMS-DEV01}"
|
||||
deploy_user="${DEPLOY_USER:-zak}"
|
||||
prod_path="${PROD_DEPLOY_PATH:-/home/${deploy_user}/magent}"
|
||||
deploy_path="${BETA_DEPLOY_PATH:-/home/${deploy_user}/magent-beta}"
|
||||
beta_frontend_bind="${BETA_FRONTEND_BIND:-10.30.1.32}"
|
||||
ssh_opts="${DEPLOY_SSH_OPTS:-"-o StrictHostKeyChecking=yes"}"
|
||||
@@ -32,19 +31,14 @@ git archive --format=tar HEAD | ssh ${ssh_opts} "${remote}" "
|
||||
done
|
||||
tar -xf - -C '${deploy_path}'
|
||||
|
||||
if [ ! -f '${deploy_path}/.env' ] && [ -f '${prod_path}/.env' ]; then
|
||||
cp '${prod_path}/.env' '${deploy_path}/.env'
|
||||
fi
|
||||
if [ -f '${deploy_path}/.env' ]; then
|
||||
chmod 600 '${deploy_path}/.env'
|
||||
if [ ! -f '${deploy_path}/.env' ]; then
|
||||
echo 'Beta .env is missing. Provision independent beta secrets before deploying.' >&2
|
||||
exit 1
|
||||
fi
|
||||
chmod 600 '${deploy_path}/.env'
|
||||
|
||||
mkdir -p '${deploy_path}/data'
|
||||
chmod 700 '${deploy_path}/data'
|
||||
if [ ! -f '${deploy_path}/data/magent.db' ] && [ -d '${prod_path}/data' ]; then
|
||||
cp -a '${prod_path}/data/.' '${deploy_path}/data/'
|
||||
fi
|
||||
|
||||
cd '${deploy_path}'
|
||||
docker compose -p magent-beta -f docker-compose.beta.yml build
|
||||
if ! grep -Eq '^[[:space:]]*SETTINGS_ENCRYPTION_KEY=' .env; then
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Fail when generated build metadata has drifted from .build_number."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def backend_build_number() -> str:
|
||||
tree = ast.parse((ROOT / "backend/app/build_info.py").read_text(encoding="utf-8-sig"))
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign) and any(
|
||||
isinstance(target, ast.Name) and target.id == "BUILD_NUMBER" for target in node.targets
|
||||
):
|
||||
return str(ast.literal_eval(node.value))
|
||||
raise RuntimeError("backend/app/build_info.py does not define BUILD_NUMBER")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
expected = (ROOT / ".build_number").read_text(encoding="utf-8-sig").strip()
|
||||
package = json.loads((ROOT / "frontend/package.json").read_text(encoding="utf-8"))
|
||||
package_lock = json.loads((ROOT / "frontend/package-lock.json").read_text(encoding="utf-8"))
|
||||
values = {
|
||||
"backend/app/build_info.py": backend_build_number(),
|
||||
"frontend/package.json": str(package.get("version") or ""),
|
||||
"frontend/package-lock.json": str(package_lock.get("version") or ""),
|
||||
"frontend/package-lock.json root package": str(package_lock.get("packages", {}).get("", {}).get("version") or ""),
|
||||
}
|
||||
drifted = {name: value for name, value in values.items() if value != expected}
|
||||
if drifted:
|
||||
details = ", ".join(f"{name}={value!r}" for name, value in drifted.items())
|
||||
raise SystemExit(f"Build metadata differs from .build_number ({expected!r}): {details}")
|
||||
print(f"Build metadata is synchronized: {expected}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user