Run multilingual audit via serverpillar salt
This commit is contained in:
7
Jenkinsfile
vendored
7
Jenkinsfile
vendored
@@ -9,10 +9,9 @@ pipeline {
|
|||||||
environment {
|
environment {
|
||||||
PYENVPIPELINE_VIRTUALENV = '1'
|
PYENVPIPELINE_VIRTUALENV = '1'
|
||||||
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||||
STAGING_AUDIT_HOST = 'root@welkombij.mandelblog.com'
|
STAGING_AUDIT_MINION = 'welkombij.mandelblog.com'
|
||||||
STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio'
|
STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio'
|
||||||
STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
||||||
STAGING_AUDIT_SSH_CREDENTIALS_ID = 'staging-root-ssh'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
@@ -108,9 +107,7 @@ pipeline {
|
|||||||
deleteDir()
|
deleteDir()
|
||||||
checkout scm
|
checkout scm
|
||||||
sh 'mkdir -p artifacts && chmod +x scripts/run_remote_multilingual_audit.sh'
|
sh 'mkdir -p artifacts && chmod +x scripts/run_remote_multilingual_audit.sh'
|
||||||
withCredentials([sshUserPrivateKey(credentialsId: env.STAGING_AUDIT_SSH_CREDENTIALS_ID, keyFileVariable: 'STAGING_SSH_KEYFILE')]) {
|
sh './scripts/run_remote_multilingual_audit.sh'
|
||||||
sh './scripts/run_remote_multilingual_audit.sh'
|
|
||||||
}
|
|
||||||
script {
|
script {
|
||||||
int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json', returnStatus: true)
|
int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json', returnStatus: true)
|
||||||
if (status == 2) {
|
if (status == 2) {
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ pipeline {
|
|||||||
skipDefaultCheckout(true)
|
skipDefaultCheckout(true)
|
||||||
}
|
}
|
||||||
environment {
|
environment {
|
||||||
STAGING_AUDIT_HOST = 'root@welkombij.mandelblog.com'
|
STAGING_AUDIT_MINION = 'welkombij.mandelblog.com'
|
||||||
STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio'
|
STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio'
|
||||||
STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
||||||
STAGING_AUDIT_SSH_CREDENTIALS_ID = 'staging-root-ssh'
|
|
||||||
}
|
}
|
||||||
stages {
|
stages {
|
||||||
stage('Checkout') {
|
stage('Checkout') {
|
||||||
@@ -42,9 +41,7 @@ pipeline {
|
|||||||
checkout scm
|
checkout scm
|
||||||
sh 'mkdir -p artifacts && [ -f artifacts/multilingual-audit.json ] && cp artifacts/multilingual-audit.json artifacts/previous-multilingual-audit.json || true'
|
sh 'mkdir -p artifacts && [ -f artifacts/multilingual-audit.json ] && cp artifacts/multilingual-audit.json artifacts/previous-multilingual-audit.json || true'
|
||||||
sh 'chmod +x scripts/run_remote_multilingual_audit.sh'
|
sh 'chmod +x scripts/run_remote_multilingual_audit.sh'
|
||||||
withCredentials([sshUserPrivateKey(credentialsId: env.STAGING_AUDIT_SSH_CREDENTIALS_ID, keyFileVariable: 'STAGING_SSH_KEYFILE')]) {
|
sh './scripts/run_remote_multilingual_audit.sh'
|
||||||
sh './scripts/run_remote_multilingual_audit.sh'
|
|
||||||
}
|
|
||||||
script {
|
script {
|
||||||
int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json --previous-json artifacts/previous-multilingual-audit.json', returnStatus: true)
|
int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json --previous-json artifacts/previous-multilingual-audit.json', returnStatus: true)
|
||||||
if (status == 2) {
|
if (status == 2) {
|
||||||
|
|||||||
@@ -55,18 +55,13 @@ The audit summary is interpreted as follows:
|
|||||||
|
|
||||||
This keeps deploys safe without making warning-level cleanup a hard blocker.
|
This keeps deploys safe without making warning-level cleanup a hard blocker.
|
||||||
|
|
||||||
## Required Jenkins credential
|
## Jenkins requirements
|
||||||
Credential location:
|
No dedicated staging SSH credential is required for the multilingual audit stage.
|
||||||
- `Manage Jenkins -> Credentials -> System -> Global credentials`
|
|
||||||
|
|
||||||
Credential to add:
|
The audit runs from the Jenkins built-in node through the same `serverpillar` / Salt transport used by staging deployment.
|
||||||
- `Kind`: `SSH Username with private key`
|
|
||||||
- `ID`: `staging-root-ssh`
|
|
||||||
- `Username`: `root`
|
|
||||||
- `Private key`: staging SSH key
|
|
||||||
|
|
||||||
Current implementation uses the following environment defaults:
|
Current implementation uses the following environment defaults:
|
||||||
- `STAGING_AUDIT_HOST=root@welkombij.mandelblog.com`
|
- `STAGING_AUDIT_MINION=welkombij.mandelblog.com`
|
||||||
- `STAGING_AUDIT_PROJECT_DIR=/home/www-mandelstudio/mandelstudio`
|
- `STAGING_AUDIT_PROJECT_DIR=/home/www-mandelstudio/mandelstudio`
|
||||||
- `STAGING_AUDIT_MANAGE=/var/lib/virtualenv/mandelstudio/bin/manage.py`
|
- `STAGING_AUDIT_MANAGE=/var/lib/virtualenv/mandelstudio/bin/manage.py`
|
||||||
|
|
||||||
@@ -106,7 +101,7 @@ This happens when the remote audit times out or fails, and is intentional so Jen
|
|||||||
## Local rerun
|
## Local rerun
|
||||||
To rerun the same remote audit flow locally:
|
To rerun the same remote audit flow locally:
|
||||||
```bash
|
```bash
|
||||||
export STAGING_AUDIT_HOST='root@welkombij.mandelblog.com'
|
export STAGING_AUDIT_MINION='welkombij.mandelblog.com'
|
||||||
export STAGING_AUDIT_PROJECT_DIR='/home/www-mandelstudio/mandelstudio'
|
export STAGING_AUDIT_PROJECT_DIR='/home/www-mandelstudio/mandelstudio'
|
||||||
export STAGING_AUDIT_MANAGE='/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
export STAGING_AUDIT_MANAGE='/var/lib/virtualenv/mandelstudio/bin/manage.py'
|
||||||
./scripts/run_remote_multilingual_audit.sh
|
./scripts/run_remote_multilingual_audit.sh
|
||||||
|
|||||||
@@ -1,72 +1,97 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
: "${STAGING_AUDIT_HOST:?STAGING_AUDIT_HOST is required}"
|
: "${STAGING_AUDIT_MINION:?STAGING_AUDIT_MINION is required}"
|
||||||
: "${STAGING_AUDIT_PROJECT_DIR:?STAGING_AUDIT_PROJECT_DIR is required}"
|
: "${STAGING_AUDIT_PROJECT_DIR:?STAGING_AUDIT_PROJECT_DIR is required}"
|
||||||
: "${STAGING_AUDIT_MANAGE:?STAGING_AUDIT_MANAGE is required}"
|
: "${STAGING_AUDIT_MANAGE:?STAGING_AUDIT_MANAGE is required}"
|
||||||
|
|
||||||
mkdir -p artifacts
|
|
||||||
SSH_OPTS=${SSH_OPTS:-"-o StrictHostKeyChecking=accept-new"}
|
|
||||||
if [[ -n "${STAGING_SSH_KEYFILE:-}" ]]; then
|
|
||||||
SSH_OPTS="$SSH_OPTS -i ${STAGING_SSH_KEYFILE}"
|
|
||||||
fi
|
|
||||||
AUDIT_TIMEOUT_SECONDS=${AUDIT_TIMEOUT_SECONDS:-300}
|
AUDIT_TIMEOUT_SECONDS=${AUDIT_TIMEOUT_SECONDS:-300}
|
||||||
OUT_FILE="artifacts/multilingual-audit.json"
|
ARTIFACT_DIR=${ARTIFACT_DIR:-artifacts}
|
||||||
TMP_FILE="${OUT_FILE}.tmp"
|
OUTPUT_JSON=${OUTPUT_JSON:-${ARTIFACT_DIR}/multilingual-audit.json}
|
||||||
|
mkdir -p "${ARTIFACT_DIR}"
|
||||||
write_failure_json() {
|
TMP_FILE=$(mktemp)
|
||||||
python3 - <<PY > "$OUT_FILE"
|
trap 'rm -f "$TMP_FILE"' EXIT
|
||||||
import json
|
|
||||||
print(json.dumps({
|
|
||||||
"run_id": None,
|
|
||||||
"total_urls_checked": 0,
|
|
||||||
"issues_found": 0,
|
|
||||||
"summary": {},
|
|
||||||
"issues": {},
|
|
||||||
"error": ${1@Q}
|
|
||||||
}, indent=2))
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' audit_locales --format=json"
|
REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' audit_locales --format=json"
|
||||||
set +e
|
|
||||||
SSH_OPTS="$SSH_OPTS" STAGING_AUDIT_HOST="$STAGING_AUDIT_HOST" REMOTE_CMD="$REMOTE_CMD" AUDIT_TIMEOUT_SECONDS="$AUDIT_TIMEOUT_SECONDS" python3 - <<'PY' > "$TMP_FILE"
|
STAGING_AUDIT_MINION="$STAGING_AUDIT_MINION" REMOTE_CMD="$REMOTE_CMD" AUDIT_TIMEOUT_SECONDS="$AUDIT_TIMEOUT_SECONDS" python3 - <<'PY2' > "$TMP_FILE"
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
ssh_opts = shlex.split(os.environ["SSH_OPTS"])
|
minion = os.environ["STAGING_AUDIT_MINION"]
|
||||||
cmd = ["ssh", *ssh_opts, os.environ["STAGING_AUDIT_HOST"], os.environ["REMOTE_CMD"]]
|
remote_cmd = os.environ["REMOTE_CMD"]
|
||||||
|
timeout_seconds = int(os.environ["AUDIT_TIMEOUT_SECONDS"])
|
||||||
|
cmd = [
|
||||||
|
"sudo", "-n", "-u", "mandel", "-g", "www-data",
|
||||||
|
"/usr/bin/salt", "--out=json", minion,
|
||||||
|
"cmd.run_all", remote_cmd, "python_shell=True",
|
||||||
|
]
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
check=True,
|
capture_output=True,
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
text=True,
|
||||||
timeout=int(os.environ["AUDIT_TIMEOUT_SECONDS"]),
|
timeout=timeout_seconds,
|
||||||
|
check=False,
|
||||||
)
|
)
|
||||||
sys.stdout.write(proc.stdout)
|
except subprocess.TimeoutExpired:
|
||||||
sys.stderr.write(proc.stderr)
|
print(json.dumps({
|
||||||
except subprocess.TimeoutExpired as exc:
|
"run_id": None,
|
||||||
sys.stderr.write(exc.stderr or "")
|
"total_urls_checked": 0,
|
||||||
raise SystemExit(124)
|
"issues_found": 0,
|
||||||
except subprocess.CalledProcessError as exc:
|
"summary": {},
|
||||||
sys.stdout.write(exc.stdout or "")
|
"issues": {},
|
||||||
sys.stderr.write(exc.stderr or "")
|
"error": f"Salt multilingual audit timed out after {timeout_seconds} seconds",
|
||||||
raise SystemExit(exc.returncode)
|
}, indent=2))
|
||||||
PY
|
sys.exit(2)
|
||||||
rc=$?
|
|
||||||
set -e
|
if result.returncode != 0:
|
||||||
if [[ $rc -eq 0 ]]; then
|
print(json.dumps({
|
||||||
mv "$TMP_FILE" "$OUT_FILE"
|
"run_id": None,
|
||||||
exit 0
|
"total_urls_checked": 0,
|
||||||
fi
|
"issues_found": 0,
|
||||||
rm -f "$TMP_FILE"
|
"summary": {},
|
||||||
if [[ $rc -eq 124 ]]; then
|
"issues": {},
|
||||||
write_failure_json "Remote multilingual audit timed out after ${AUDIT_TIMEOUT_SECONDS}s"
|
"error": f"Salt audit transport failed with exit status {result.returncode}: {(result.stderr or result.stdout).strip()}",
|
||||||
else
|
}, indent=2))
|
||||||
write_failure_json "Remote multilingual audit failed with exit status ${rc}"
|
sys.exit(2)
|
||||||
fi
|
|
||||||
exit $rc
|
try:
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
if not isinstance(payload, dict) or minion not in payload:
|
||||||
|
raise ValueError("Missing minion payload")
|
||||||
|
minion_payload = payload[minion]
|
||||||
|
if not isinstance(minion_payload, dict):
|
||||||
|
raise ValueError("Unexpected minion payload type")
|
||||||
|
retcode = int(minion_payload.get("retcode", 1))
|
||||||
|
stdout = minion_payload.get("stdout", "")
|
||||||
|
stderr = minion_payload.get("stderr", "")
|
||||||
|
if retcode != 0:
|
||||||
|
print(json.dumps({
|
||||||
|
"run_id": None,
|
||||||
|
"total_urls_checked": 0,
|
||||||
|
"issues_found": 0,
|
||||||
|
"summary": {},
|
||||||
|
"issues": {},
|
||||||
|
"error": f"Remote multilingual audit failed with exit status {retcode}: {(stderr or stdout).strip()}",
|
||||||
|
}, indent=2))
|
||||||
|
sys.exit(2)
|
||||||
|
audit = json.loads(stdout)
|
||||||
|
except Exception as exc:
|
||||||
|
print(json.dumps({
|
||||||
|
"run_id": None,
|
||||||
|
"total_urls_checked": 0,
|
||||||
|
"issues_found": 0,
|
||||||
|
"summary": {},
|
||||||
|
"issues": {},
|
||||||
|
"error": f"Unable to parse salt audit response: {exc}",
|
||||||
|
}, indent=2))
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
print(json.dumps(audit, indent=2, sort_keys=True))
|
||||||
|
PY2
|
||||||
|
status=$?
|
||||||
|
cp "$TMP_FILE" "$OUTPUT_JSON"
|
||||||
|
exit $status
|
||||||
|
|||||||
Reference in New Issue
Block a user