From 3ffb94055063cd6e847e04c0435aedeaced7b120 Mon Sep 17 00:00:00 2001 From: Hayden Garvey <154503486+groupthinking@users.noreply.github.com> Date: Tue, 4 Aug 2026 06:08:12 +0000 Subject: [PATCH] fix(deploy): repair one-click-deploy.sh prechecks and frontend probe paths one-click-deploy.sh failed at its first gate: REQUIRED_FILES listed two files that do not exist anywhere in the repo (mcp_server.py, learning_app_processor.py) and three pre-move k8s/... paths. Even past that gate, it built the Python backend image (infrastructure/docker/Dockerfile.production) for a Deployment container configured as a Node app on port 3000, so the rollout could never pass readiness. enhanced-framework is the Next.js frontend: the manifest sets NODE_ENV/PORT=3000, both Services target port 3000, and the Python backend is already deployed as the separate mcp-server container. Make script and manifest agree on that topology: - Build the image from apps/web/Dockerfile instead of the Python Dockerfile.production. - Point liveness/readiness probes (and the script's post-deploy health check) at GET /api, the health endpoint the web app actually serves; it served neither /health nor /ready. - Refresh REQUIRED_FILES to paths that exist and fix all kubectl manifest paths to infrastructure/k8s/.... - cd to the repo root so relative paths work from any invocation dir, and run integration tests via pytest (tests/integration/test_runner.py does not exist). - Add tests/unit/test_one_click_deploy_precheck.py, run by CI's unit suite, asserting every REQUIRED_FILES entry and every -f path in the script resolves, so the list cannot rot again. Generated with [Linear](https://linear.app/myxstack/issue/GRV-195/one-click-deploysh-is-non-functional-stale-precheck-paths-and-a#agent-session-3eb72d16) Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com> --- infrastructure/k8s/production/deployment.yaml | 5 +- scripts/deployment/one-click-deploy.sh | 37 ++++++++------ tests/unit/test_one_click_deploy_precheck.py | 49 +++++++++++++++++++ 3 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 tests/unit/test_one_click_deploy_precheck.py diff --git a/infrastructure/k8s/production/deployment.yaml b/infrastructure/k8s/production/deployment.yaml index 517fd81e7..1ec8fb160 100644 --- a/infrastructure/k8s/production/deployment.yaml +++ b/infrastructure/k8s/production/deployment.yaml @@ -47,7 +47,8 @@ spec: cpu: "500m" livenessProbe: httpGet: - path: /health + # The Next.js app's health endpoint (apps/web/src/app/api/route.ts) + path: /api port: 3000 initialDelaySeconds: 30 periodSeconds: 10 @@ -55,7 +56,7 @@ spec: failureThreshold: 3 readinessProbe: httpGet: - path: /ready + path: /api port: 3000 initialDelaySeconds: 5 periodSeconds: 5 diff --git a/scripts/deployment/one-click-deploy.sh b/scripts/deployment/one-click-deploy.sh index c8b7c77e1..0f25efb65 100755 --- a/scripts/deployment/one-click-deploy.sh +++ b/scripts/deployment/one-click-deploy.sh @@ -5,6 +5,9 @@ set -euo pipefail +# All paths below are relative to the repository root +cd "$(dirname "${BASH_SOURCE[0]}")/../.." + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' @@ -63,15 +66,15 @@ if ! kubectl cluster-info &> /dev/null; then fi success "Kubernetes cluster is accessible" -# Check if required files exist +# Check if required files exist. +# Every entry must resolve from the repo root; this is enforced by +# tests/unit/test_one_click_deploy_precheck.py so the list cannot go stale. REQUIRED_FILES=( - "Dockerfile.production" + "apps/web/Dockerfile" "package.json" - "k8s/production/deployment.yaml" - "k8s/production/service.yaml" - "k8s/monitoring/monitoring.yaml" - "mcp_server.py" - "learning_app_processor.py" + "infrastructure/k8s/production/deployment.yaml" + "infrastructure/k8s/production/service.yaml" + "infrastructure/k8s/monitoring/monitoring.yaml" ) for file in "${REQUIRED_FILES[@]}"; do @@ -86,7 +89,7 @@ log "Step 2: Running Integration Tests" if [[ -d "venv" ]]; then source venv/bin/activate - if python3 tests/integration/test_runner.py; then + if python3 -m pytest tests/integration -v; then success "Integration tests passed" else error "Integration tests failed. Please fix issues before deployment." @@ -101,7 +104,9 @@ log "Step 3: Building Docker Image" DOCKER_TAG="enhanced-framework:$(date +%Y%m%d-%H%M%S)" LATEST_TAG="enhanced-framework:latest" -if docker build -f Dockerfile.production -t "$DOCKER_TAG" -t "$LATEST_TAG" .; then +# enhanced-framework is the Next.js frontend (apps/web); the Python backend is +# deployed separately as the mcp-server container in the production manifest. +if docker build -f apps/web/Dockerfile -t "$DOCKER_TAG" -t "$LATEST_TAG" .; then success "Docker image built successfully: $DOCKER_TAG" else error "Docker image build failed" @@ -111,21 +116,21 @@ fi log "Step 4: Validating Kubernetes Manifests" # Validate deployment manifest -if kubectl apply --dry-run=client -f k8s/production/deployment.yaml; then +if kubectl apply --dry-run=client -f infrastructure/k8s/production/deployment.yaml; then success "Deployment manifest is valid" else error "Deployment manifest validation failed" fi # Validate service manifest -if kubectl apply --dry-run=client -f k8s/production/service.yaml; then +if kubectl apply --dry-run=client -f infrastructure/k8s/production/service.yaml; then success "Service manifest is valid" else error "Service manifest validation failed" fi # Validate monitoring manifest -if kubectl apply --dry-run=client -f k8s/monitoring/monitoring.yaml; then +if kubectl apply --dry-run=client -f infrastructure/k8s/monitoring/monitoring.yaml; then success "Monitoring manifest is valid" else error "Monitoring manifest validation failed" @@ -144,14 +149,14 @@ fi log "Step 6: Deploying to Kubernetes" # Deploy application -if kubectl apply -f k8s/production/ -n "$NAMESPACE"; then +if kubectl apply -f infrastructure/k8s/production/ -n "$NAMESPACE"; then success "Application deployed successfully" else error "Application deployment failed" fi # Deploy monitoring -if kubectl apply -f k8s/monitoring/ -n "$NAMESPACE"; then +if kubectl apply -f infrastructure/k8s/monitoring/ -n "$NAMESPACE"; then success "Monitoring stack deployed successfully" else error "Monitoring deployment failed" @@ -199,9 +204,9 @@ if [[ "$SERVICE_IP" == "localhost" ]]; then kubectl port-forward -n "$NAMESPACE" service/"$DEPLOYMENT_NAME" 8080:80 & PORT_FORWARD_PID=$! sleep 5 - HEALTH_URL="http://localhost:8080/health" + HEALTH_URL="http://localhost:8080/api" else - HEALTH_URL="http://$SERVICE_IP/health" + HEALTH_URL="http://$SERVICE_IP/api" fi # Perform health check diff --git a/tests/unit/test_one_click_deploy_precheck.py b/tests/unit/test_one_click_deploy_precheck.py new file mode 100644 index 000000000..b83cf3703 --- /dev/null +++ b/tests/unit/test_one_click_deploy_precheck.py @@ -0,0 +1,49 @@ +"""Guard against rot in scripts/deployment/one-click-deploy.sh. + +The script's precheck aborts the whole deployment if any entry in its +REQUIRED_FILES array is missing, so a stale path makes the script +non-functional (see #1127, where two entries pointed at files that had +never existed and three more used pre-move ``k8s/...`` paths). This test +parses the array straight out of the script and asserts every entry +resolves from the repository root. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +PROJECT_ROOT = Path(__file__).parent.parent.parent +DEPLOY_SCRIPT = PROJECT_ROOT / "scripts" / "deployment" / "one-click-deploy.sh" + + +def _required_files(script_text: str) -> list[str]: + match = re.search(r"REQUIRED_FILES=\((.*?)\)", script_text, re.DOTALL) + assert match, "REQUIRED_FILES array not found in one-click-deploy.sh" + return re.findall(r'"([^"]+)"', match.group(1)) + + +def test_required_files_all_exist(): + assert DEPLOY_SCRIPT.exists(), f"{DEPLOY_SCRIPT} not found" + + entries = _required_files(DEPLOY_SCRIPT.read_text()) + assert entries, "REQUIRED_FILES is empty; the precheck validates nothing" + + missing = [entry for entry in entries if not (PROJECT_ROOT / entry).is_file()] + assert not missing, ( + "REQUIRED_FILES in one-click-deploy.sh lists paths that do not exist " + f"relative to the repo root: {missing}. The script exits on the first " + "missing entry, so every path must resolve." + ) + + +def test_manifest_paths_in_script_exist(): + """Every ``-f `` the script passes to kubectl/docker must resolve.""" + script_text = DEPLOY_SCRIPT.read_text() + paths = re.findall(r"-f\s+((?:infrastructure|apps|k8s)/[\w./-]+)", script_text) + assert paths, "No manifest/Dockerfile paths found in one-click-deploy.sh" + + missing = [p for p in paths if not (PROJECT_ROOT / p).exists()] + assert not missing, ( + f"one-click-deploy.sh references nonexistent paths: {missing}" + )