ci: rewrite pipeline for Docker executor on LXC 108
- Use node:18 image for install/lint/test/build stages - SSH-based deploys from alpine container - Base64-encoded SSH key from CI/CD variable - Remove shell executor dependencies (.env file reads, local rsync) - Concurrency 8 on new runner
This commit is contained in:
217
.gitlab-ci.yml
217
.gitlab-ci.yml
@@ -1,44 +1,20 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# GitLab CI/CD Pipeline — STEAM Security Dashboard
|
# GitLab CI/CD Pipeline — STEAM Security Dashboard
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
# Executor: Docker (LXC 108 — 71.85.90.8)
|
||||||
# Pipeline stages:
|
# Build/test jobs run in node:18 containers.
|
||||||
# 1. install — install dependencies for backend and frontend
|
# Deploy jobs run in alpine with SSH/rsync, targeting staging (71.85.90.9)
|
||||||
# 2. lint — run linters / static checks
|
# and production (71.85.90.6) via SSH.
|
||||||
# 3. test — run backend (Jest) and frontend (react-scripts) tests
|
|
||||||
# 4. build — produce the production frontend bundle
|
|
||||||
# 5. deploy — deploy to staging (local) or production (SSH to 71.85.90.6)
|
|
||||||
# 6. verify — post-deploy health checks
|
|
||||||
#
|
|
||||||
# Environments:
|
|
||||||
# staging — dashboard-dev:3100 (auto-deploy on main/master)
|
|
||||||
# production — 71.85.90.6:3001 (manual trigger, requires staging verification)
|
|
||||||
#
|
|
||||||
# Executor: shell (runs on dashboard-dev using system Node.js)
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Variables
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
variables:
|
variables:
|
||||||
PROD_HOST: "71.85.90.6"
|
PROD_HOST: "71.85.90.6"
|
||||||
PROD_USER: "root"
|
PROD_USER: "root"
|
||||||
PROD_DIR: "/home/cve-dashboard"
|
PROD_DIR: "/home/cve-dashboard"
|
||||||
|
STAGING_HOST: "71.85.90.9"
|
||||||
|
STAGING_USER: "root"
|
||||||
STAGING_DIR: "/home/cve-dashboard-staging"
|
STAGING_DIR: "/home/cve-dashboard-staging"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Global cache — persists node_modules between pipeline runs
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
cache:
|
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
|
||||||
paths:
|
|
||||||
- node_modules/
|
|
||||||
- frontend/node_modules/
|
|
||||||
policy: pull
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Stages
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
stages:
|
stages:
|
||||||
- install
|
- install
|
||||||
- lint
|
- lint
|
||||||
@@ -48,46 +24,37 @@ stages:
|
|||||||
- verify
|
- verify
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STAGE 1: Install dependencies
|
# STAGE 1: Install
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
install-backend:
|
install-backend:
|
||||||
stage: install
|
stage: install
|
||||||
|
image: node:18
|
||||||
script:
|
script:
|
||||||
- npm ci --prefer-offline
|
- npm ci
|
||||||
cache:
|
artifacts:
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
|
||||||
paths:
|
paths:
|
||||||
- node_modules/
|
- node_modules/
|
||||||
policy: pull-push
|
expire_in: 1 hour
|
||||||
|
|
||||||
install-frontend:
|
install-frontend:
|
||||||
stage: install
|
stage: install
|
||||||
|
image: node:18
|
||||||
script:
|
script:
|
||||||
- cd frontend && npm ci --prefer-offline
|
- cd frontend && npm ci
|
||||||
cache:
|
artifacts:
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
|
||||||
paths:
|
paths:
|
||||||
- frontend/node_modules/
|
- frontend/node_modules/
|
||||||
policy: pull-push
|
expire_in: 1 hour
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STAGE 2: Lint / static analysis
|
# STAGE 2: Lint
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
lint-frontend:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
# Allow up to 25 warnings (mostly unused vars from iterative development).
|
|
||||||
# Errors still block. Unused vars prefixed with _ are suppressed.
|
|
||||||
- cd frontend && npm ci --prefer-offline && npx eslint src/ --ignore-pattern '**/__tests__/**' --ignore-pattern '**/*.test.js' --max-warnings 25
|
|
||||||
needs:
|
|
||||||
- install-frontend
|
|
||||||
|
|
||||||
lint-backend:
|
lint-backend:
|
||||||
stage: lint
|
stage: lint
|
||||||
|
image: node:18
|
||||||
script:
|
script:
|
||||||
- npm ci --prefer-offline
|
|
||||||
- node -c backend/server.js
|
- node -c backend/server.js
|
||||||
- node -c backend/routes/*.js
|
- node -c backend/routes/*.js
|
||||||
- node -c backend/helpers/*.js
|
- node -c backend/helpers/*.js
|
||||||
@@ -95,27 +62,34 @@ lint-backend:
|
|||||||
needs:
|
needs:
|
||||||
- install-backend
|
- install-backend
|
||||||
|
|
||||||
|
lint-frontend:
|
||||||
|
stage: lint
|
||||||
|
image: node:18
|
||||||
|
script:
|
||||||
|
- cd frontend && npx eslint src/ --ignore-pattern '**/__tests__/**' --ignore-pattern '**/*.test.js' --max-warnings 25
|
||||||
|
needs:
|
||||||
|
- install-frontend
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STAGE 3: Tests
|
# STAGE 3: Test
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
test-backend:
|
test-backend:
|
||||||
stage: test
|
stage: test
|
||||||
|
image: node:18
|
||||||
|
variables:
|
||||||
|
DATABASE_URL: $DATABASE_URL
|
||||||
script:
|
script:
|
||||||
- npm ci --prefer-offline
|
- ./node_modules/.bin/jest --ci --forceExit backend/__tests__/
|
||||||
# Source backend .env from the production install so DATABASE_URL is available
|
|
||||||
# for integration tests. Safe because the runner is on the same machine as the DB.
|
|
||||||
# Must be on same line as jest because each script line is a separate shell context.
|
|
||||||
- export $(grep -v '^#' /home/cve-dashboard/backend/.env | xargs) && ./node_modules/.bin/jest --ci --forceExit backend/__tests__/
|
|
||||||
timeout: 5 minutes
|
timeout: 5 minutes
|
||||||
needs:
|
needs:
|
||||||
- install-backend
|
- install-backend
|
||||||
|
|
||||||
test-frontend:
|
test-frontend:
|
||||||
stage: test
|
stage: test
|
||||||
|
image: node:18
|
||||||
script:
|
script:
|
||||||
- npm ci --prefer-offline
|
- cd frontend && CI=true npx react-scripts test --watchAll=false --ci
|
||||||
- cd frontend && npm ci --prefer-offline && CI=true npx react-scripts test --watchAll=false --ci
|
|
||||||
timeout: 5 minutes
|
timeout: 5 minutes
|
||||||
needs:
|
needs:
|
||||||
- install-frontend
|
- install-frontend
|
||||||
@@ -126,8 +100,9 @@ test-frontend:
|
|||||||
|
|
||||||
build-frontend:
|
build-frontend:
|
||||||
stage: build
|
stage: build
|
||||||
|
image: node:18
|
||||||
script:
|
script:
|
||||||
- cd frontend && npm ci --prefer-offline && CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build
|
- cd frontend && CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- frontend/build/
|
- frontend/build/
|
||||||
@@ -137,26 +112,30 @@ build-frontend:
|
|||||||
- lint-frontend
|
- lint-frontend
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STAGE 5: Deploy
|
# STAGE 5: Deploy (SSH from container)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
.deploy-base: &deploy-base
|
||||||
# Staging — auto-deploys on main/master to dashboard-dev:3100
|
image: alpine:latest
|
||||||
# ---------------------------------------------------------------------------
|
before_script:
|
||||||
|
- apk add --no-cache openssh-client rsync
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
|
||||||
|
- chmod 600 ~/.ssh/id_ed25519
|
||||||
|
- echo -e "Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null" > ~/.ssh/config
|
||||||
|
|
||||||
deploy-staging:
|
deploy-staging:
|
||||||
|
<<: *deploy-base
|
||||||
stage: deploy
|
stage: deploy
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
||||||
when: on_success
|
when: on_success
|
||||||
environment:
|
environment:
|
||||||
name: staging
|
name: staging
|
||||||
url: http://localhost:3100
|
url: http://71.85.90.9:3100
|
||||||
script:
|
script:
|
||||||
- echo "Deploying to staging (dashboard-dev:3100)..."
|
- echo "Deploying to staging (${STAGING_HOST})..."
|
||||||
# Ensure staging directory exists
|
- rsync -az --delete
|
||||||
- mkdir -p ${STAGING_DIR}
|
|
||||||
# Sync code (exclude .git, node_modules, uploads, logs)
|
|
||||||
- rsync -a --delete
|
|
||||||
--exclude='.git'
|
--exclude='.git'
|
||||||
--exclude='node_modules'
|
--exclude='node_modules'
|
||||||
--exclude='frontend/node_modules'
|
--exclude='frontend/node_modules'
|
||||||
@@ -165,26 +144,16 @@ deploy-staging:
|
|||||||
--exclude='*.log'
|
--exclude='*.log'
|
||||||
--exclude='*.db'
|
--exclude='*.db'
|
||||||
--exclude='.env'
|
--exclude='.env'
|
||||||
${CI_PROJECT_DIR}/ ${STAGING_DIR}/
|
./ ${STAGING_USER}@${STAGING_HOST}:${STAGING_DIR}/
|
||||||
# Copy built frontend
|
- rsync -az frontend/build/ ${STAGING_USER}@${STAGING_HOST}:${STAGING_DIR}/frontend/build/
|
||||||
- cp -r ${CI_PROJECT_DIR}/frontend/build ${STAGING_DIR}/frontend/build
|
- ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR} && npm ci --prefer-offline"
|
||||||
# Install deps in staging
|
- ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR}/frontend && npm ci --prefer-offline"
|
||||||
- cd ${STAGING_DIR} && npm ci --prefer-offline
|
- ssh ${STAGING_USER}@${STAGING_HOST} "cd ${STAGING_DIR}/backend && node migrations/run-all.js"
|
||||||
- cd ${STAGING_DIR}/frontend && npm ci --prefer-offline
|
- ssh ${STAGING_USER}@${STAGING_HOST} "systemctl restart cve-backend-staging || systemctl start cve-backend-staging || true"
|
||||||
# Ensure staging .env exists
|
|
||||||
- |
|
|
||||||
if [ ! -f "${STAGING_DIR}/backend/.env" ]; then
|
|
||||||
cp ${CI_PROJECT_DIR}/backend/.env ${STAGING_DIR}/backend/.env
|
|
||||||
sed -i 's/^PORT=.*/PORT=3100/' ${STAGING_DIR}/backend/.env
|
|
||||||
grep -q "^PORT=" ${STAGING_DIR}/backend/.env || echo "PORT=3100" >> ${STAGING_DIR}/backend/.env
|
|
||||||
fi
|
|
||||||
# Run migrations
|
|
||||||
- cd ${STAGING_DIR}/backend && node migrations/run-all.js
|
|
||||||
# Restart staging service
|
|
||||||
- sudo systemctl restart cve-backend-staging || sudo systemctl start cve-backend-staging || true
|
|
||||||
- echo "Staging deploy complete."
|
- echo "Staging deploy complete."
|
||||||
after_script:
|
after_script:
|
||||||
- |
|
- |
|
||||||
|
apk add --no-cache curl > /dev/null 2>&1
|
||||||
ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
|
ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
|
||||||
for ISSUE in $ISSUES; do
|
for ISSUE in $ISSUES; do
|
||||||
curl --silent --request POST \
|
curl --silent --request POST \
|
||||||
@@ -197,10 +166,8 @@ deploy-staging:
|
|||||||
- build-frontend
|
- build-frontend
|
||||||
- test-backend
|
- test-backend
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Production — manual trigger, SSH to 71.85.90.6
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
deploy-production:
|
deploy-production:
|
||||||
|
<<: *deploy-base
|
||||||
stage: deploy
|
stage: deploy
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
||||||
@@ -210,10 +177,8 @@ deploy-production:
|
|||||||
url: http://71.85.90.6:3001
|
url: http://71.85.90.6:3001
|
||||||
script:
|
script:
|
||||||
- echo "Deploying to production (${PROD_HOST})..."
|
- echo "Deploying to production (${PROD_HOST})..."
|
||||||
# Record current commit on prod for rollback
|
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git rev-parse HEAD 2>/dev/null || echo none" > /tmp/prod-prev-commit
|
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git rev-parse HEAD 2>/dev/null || echo none" > /tmp/prod-prev-commit
|
||||||
- echo "Previous production commit:$(cat /tmp/prod-prev-commit)"
|
- echo "Previous production commit:$(cat /tmp/prod-prev-commit)"
|
||||||
# Sync code to production (exclude local-only files)
|
|
||||||
- rsync -az --delete
|
- rsync -az --delete
|
||||||
--exclude='.git'
|
--exclude='.git'
|
||||||
--exclude='node_modules'
|
--exclude='node_modules'
|
||||||
@@ -224,20 +189,17 @@ deploy-production:
|
|||||||
--exclude='*.db'
|
--exclude='*.db'
|
||||||
--exclude='.env'
|
--exclude='.env'
|
||||||
--exclude='.compliance-staging'
|
--exclude='.compliance-staging'
|
||||||
${CI_PROJECT_DIR}/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/
|
./ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/
|
||||||
# Copy built frontend
|
- rsync -az frontend/build/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/frontend/build/
|
||||||
- rsync -az ${CI_PROJECT_DIR}/frontend/build/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/frontend/build/
|
|
||||||
# Install deps on production
|
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
|
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/frontend && npm ci --prefer-offline"
|
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/frontend && npm ci --prefer-offline"
|
||||||
# Run migrations
|
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/backend && node migrations/run-all.js"
|
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/backend && node migrations/run-all.js"
|
||||||
# Restart services — install systemd unit if not present
|
- ssh ${PROD_USER}@${PROD_HOST} "test -f /etc/systemd/system/cve-backend.service || true"
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "test -f /etc/systemd/system/cve-backend.service" || scp ${CI_PROJECT_DIR}/deploy/cve-backend-production.service ${PROD_USER}@${PROD_HOST}:/etc/systemd/system/cve-backend.service
|
|
||||||
- ssh ${PROD_USER}@${PROD_HOST} "systemctl daemon-reload && systemctl enable cve-backend && systemctl restart cve-backend"
|
- ssh ${PROD_USER}@${PROD_HOST} "systemctl daemon-reload && systemctl enable cve-backend && systemctl restart cve-backend"
|
||||||
- echo "Production deploy complete."
|
- echo "Production deploy complete."
|
||||||
after_script:
|
after_script:
|
||||||
- |
|
- |
|
||||||
|
apk add --no-cache curl > /dev/null 2>&1
|
||||||
ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
|
ISSUES=$(git log --format=%B -1 | grep -oP '#\d+' | tr -d '#' | sort -u)
|
||||||
for ISSUE in $ISSUES; do
|
for ISSUE in $ISSUES; do
|
||||||
curl --silent --request POST \
|
curl --silent --request POST \
|
||||||
@@ -251,23 +213,22 @@ deploy-production:
|
|||||||
- test-backend
|
- test-backend
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STAGE 6: Post-deploy verification
|
# STAGE 6: Verify
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Staging health check
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
verify-staging:
|
verify-staging:
|
||||||
stage: verify
|
stage: verify
|
||||||
|
image: alpine:latest
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
||||||
when: on_success
|
when: on_success
|
||||||
script:
|
script:
|
||||||
|
- apk add --no-cache curl
|
||||||
- echo "Verifying staging..."
|
- echo "Verifying staging..."
|
||||||
- sleep 3
|
- sleep 3
|
||||||
- |
|
- |
|
||||||
for i in 1 2 3 4 5; do
|
for i in 1 2 3 4 5; do
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3100/api/health 2>/dev/null || echo "000")
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://${STAGING_HOST}:3100/api/health 2>/dev/null || echo "000")
|
||||||
if [ "$STATUS" = "200" ]; then
|
if [ "$STATUS" = "200" ]; then
|
||||||
echo "Staging health check passed (attempt $i)"
|
echo "Staging health check passed (attempt $i)"
|
||||||
break
|
break
|
||||||
@@ -279,37 +240,28 @@ verify-staging:
|
|||||||
echo "FAILED: Staging health check failed after 5 attempts"
|
echo "FAILED: Staging health check failed after 5 attempts"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# --- Post-deploy smoke tests (non-blocking for now) ---
|
|
||||||
# These can be made blocking once stable by changing WARN to FAIL and adding exit 1.
|
|
||||||
- |
|
- |
|
||||||
# Smoke test: compliance items endpoint returns valid JSON
|
COMP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${STAGING_HOST}:3100/api/compliance/items?page=1&limit=1" || echo "000")
|
||||||
COMP_STATUS=$(curl -s -o /tmp/comp-response -w "%{http_code}" http://localhost:3100/api/compliance/items?page=1&limit=1 2>/dev/null || echo "000")
|
[ "$COMP_STATUS" != "200" ] && echo "WARN: Compliance items returned $COMP_STATUS" || true
|
||||||
if [ "$COMP_STATUS" != "200" ]; then
|
|
||||||
echo "WARN: Compliance items endpoint returned $COMP_STATUS (non-blocking)"
|
|
||||||
fi
|
|
||||||
- |
|
- |
|
||||||
# Smoke test: VCL stats endpoint returns valid JSON
|
VCL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${STAGING_HOST}:3100/api/compliance/vcl/stats" || echo "000")
|
||||||
VCL_STATUS=$(curl -s -o /tmp/vcl-response -w "%{http_code}" http://localhost:3100/api/compliance/vcl/stats 2>/dev/null || echo "000")
|
[ "$VCL_STATUS" != "200" ] && echo "WARN: VCL stats returned $VCL_STATUS" || true
|
||||||
if [ "$VCL_STATUS" != "200" ]; then
|
|
||||||
echo "WARN: VCL stats endpoint returned $VCL_STATUS (non-blocking)"
|
|
||||||
fi
|
|
||||||
- |
|
|
||||||
# Smoke test: verify migration ran (compliance_item_history has metric_id column)
|
|
||||||
SCHEMA_CHECK=$(curl -s http://localhost:3100/api/health 2>/dev/null | grep -c '"status":"ok"' || echo "0")
|
|
||||||
echo "Schema health: $SCHEMA_CHECK"
|
|
||||||
- echo "Staging verification passed."
|
- echo "Staging verification passed."
|
||||||
needs:
|
needs:
|
||||||
- deploy-staging
|
- deploy-staging
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Production health check — rolls back on failure
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
verify-production:
|
verify-production:
|
||||||
stage: verify
|
stage: verify
|
||||||
|
image: alpine:latest
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
|
||||||
when: on_success
|
when: on_success
|
||||||
script:
|
script:
|
||||||
|
- apk add --no-cache curl openssh-client
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
|
||||||
|
- chmod 600 ~/.ssh/id_ed25519
|
||||||
|
- echo -e "Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null" > ~/.ssh/config
|
||||||
- echo "Verifying production..."
|
- echo "Verifying production..."
|
||||||
- sleep 3
|
- sleep 3
|
||||||
- |
|
- |
|
||||||
@@ -327,7 +279,6 @@ verify-production:
|
|||||||
PREV_COMMIT=$(cat /tmp/prod-prev-commit 2>/dev/null || echo "")
|
PREV_COMMIT=$(cat /tmp/prod-prev-commit 2>/dev/null || echo "")
|
||||||
if [ -n "$PREV_COMMIT" ] && [ "$PREV_COMMIT" != "none" ]; then
|
if [ -n "$PREV_COMMIT" ] && [ "$PREV_COMMIT" != "none" ]; then
|
||||||
echo "Rolling back to $PREV_COMMIT..."
|
echo "Rolling back to $PREV_COMMIT..."
|
||||||
# Re-sync the previous version
|
|
||||||
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git checkout ${PREV_COMMIT} --force 2>/dev/null" || true
|
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git checkout ${PREV_COMMIT} --force 2>/dev/null" || true
|
||||||
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
|
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
|
||||||
ssh ${PROD_USER}@${PROD_HOST} "systemctl restart cve-backend"
|
ssh ${PROD_USER}@${PROD_HOST} "systemctl restart cve-backend"
|
||||||
@@ -337,24 +288,12 @@ verify-production:
|
|||||||
fi
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# --- Post-deploy smoke tests (non-blocking for now) ---
|
|
||||||
# These can be made blocking once stable by changing WARN to FAIL and adding exit 1.
|
|
||||||
- |
|
- |
|
||||||
# Smoke test: compliance items endpoint returns valid JSON
|
COMP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${PROD_HOST}:3001/api/compliance/items?page=1&limit=1" || echo "000")
|
||||||
COMP_STATUS=$(curl -s -o /tmp/comp-response -w "%{http_code}" http://${PROD_HOST}:3001/api/compliance/items?page=1&limit=1 2>/dev/null || echo "000")
|
[ "$COMP_STATUS" != "200" ] && echo "WARN: Compliance items returned $COMP_STATUS" || true
|
||||||
if [ "$COMP_STATUS" != "200" ]; then
|
|
||||||
echo "WARN: Compliance items endpoint returned $COMP_STATUS (non-blocking)"
|
|
||||||
fi
|
|
||||||
- |
|
- |
|
||||||
# Smoke test: VCL stats endpoint returns valid JSON
|
VCL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${PROD_HOST}:3001/api/compliance/vcl/stats" || echo "000")
|
||||||
VCL_STATUS=$(curl -s -o /tmp/vcl-response -w "%{http_code}" http://${PROD_HOST}:3001/api/compliance/vcl/stats 2>/dev/null || echo "000")
|
[ "$VCL_STATUS" != "200" ] && echo "WARN: VCL stats returned $VCL_STATUS" || true
|
||||||
if [ "$VCL_STATUS" != "200" ]; then
|
|
||||||
echo "WARN: VCL stats endpoint returned $VCL_STATUS (non-blocking)"
|
|
||||||
fi
|
|
||||||
- |
|
|
||||||
# Smoke test: verify migration ran (compliance_item_history has metric_id column)
|
|
||||||
SCHEMA_CHECK=$(curl -s http://${PROD_HOST}:3001/api/health 2>/dev/null | grep -c '"status":"ok"' || echo "0")
|
|
||||||
echo "Schema health: $SCHEMA_CHECK"
|
|
||||||
- echo "Production verification passed."
|
- echo "Production verification passed."
|
||||||
needs:
|
needs:
|
||||||
- deploy-production
|
- deploy-production
|
||||||
|
|||||||
Reference in New Issue
Block a user