#!/usr/bin/env groovy pipeline { agent { label 'external_pool' } options { disableConcurrentBuilds() skipDefaultCheckout(true) } parameters { booleanParam( name: 'RUN_DEMO_PURGE', defaultValue: false, description: 'Run a one-time demo catalogue purge before the normal idea marketplace seed and launch prep.' ) } environment { PYENVPIPELINE_VIRTUALENV = '1' GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new' STAGING_AUDIT_PROJECT_NAME = 'mandelstudio' STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio' STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py' } stages { stage('Checkout') { steps { withCredentials([sshUserPrivateKey(credentialsId: 'gitea-ssh', keyFileVariable: 'GIT_KEYFILE')]) { sh ''' export GIT_SSH_COMMAND="ssh -i $GIT_KEYFILE -o StrictHostKeyChecking=accept-new" if [ -d .git ]; then if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git else git remote add origin ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git fi git fetch --tags --force --progress origin +refs/heads/master:refs/remotes/origin/master else git clone ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git . git fetch --tags --force --progress origin +refs/heads/master:refs/remotes/origin/master fi git checkout -f refs/remotes/origin/master ''' } } } stage('Build') { steps { sh ''' STABLE_INDEX_URL=${STABLE_INDEX_URL:-https://pypi.mandelblog.com/mandel/stable/+simple/} TESTING_INDEX_URL=${TESTING_INDEX_URL:-https://pypi.mandelblog.com/mandel/testing/+simple/} ROOT_INDEX_URL=${PIP_EXTRA_INDEX_URL:-https://pypi.mandelblog.com/root/pypi/+simple/} export STABLE_INDEX_URL if python3 - <<'PY' import os import sys from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError url = os.environ["STABLE_INDEX_URL"] try: req = Request(url, method='HEAD') with urlopen(req, timeout=10) as response: sys.exit(0 if response.status < 400 else 1) except HTTPError as exc: sys.exit(0 if exc.code < 400 else 1) except URLError: sys.exit(1) PY then echo "devpi stable index available, but stable-first install is not enabled yet" else echo "devpi stable index not available, using testing as production source" fi if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then sudo apt-get update -y sudo apt-get install -y python3-venv python3-pip make build-essential libpq-dev \ libpango-1.0-0 libpangocairo-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 libffi-dev shared-mime-info fi python3 -m venv .venv || { python3 -m pip --version >/dev/null 2>&1 || { curl -fsSL https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py python3 /tmp/get-pip.py --user } python3 -m pip install --user virtualenv python3 -m virtualenv .venv } . .venv/bin/activate pip install coverage pip install --upgrade pip "setuptools==69.5.1" wheel PIP_INDEX_URL="$TESTING_INDEX_URL" \ PIP_EXTRA_INDEX_URL="$ROOT_INDEX_URL" \ pip install --no-build-isolation --pre --editable . setuptools wheel --upgrade --upgrade-strategy=eager --use-deprecated=legacy-resolver cp "${JOB_BASE_NAME}/ocyan.json" "${JOB_BASE_NAME}/${JOB_BASE_NAME}.json" pip install ruff vdt.versionplugin.wheel pip install --upgrade "setuptools==69.5.1" wheel python3 scripts/validate_payment_provider_config.py manage.py migrate --no-input --skip-checks if [ "${RUN_DEMO_PURGE}" = "true" ]; then manage.py purge_demo_data fi manage.py seed_idea_marketplace manage.py prepare_idea_marketplace_launch --apply-homepage-copy --purge-demo-pages manage.py validate_idea_marketplace_launch manage.py collectstatic --no-input --verbosity=0 pip install "httpx<0.28" ''' } } stage('Lint') { steps { sh ''' . .venv/bin/activate pip install coverage ruff make lint ''' } } stage('Test') { steps { sh ''' . .venv/bin/activate python -m compileall -q setup.py mandelstudio mandelblog_content_guard ''' } post { always { junit allowEmptyResults: true, testResults: '**/nosetests.xml' } success { echo "Coverage step skipped" } } } stage('Sync Staging Source') { agent { label 'built-in' } options { timeout(time: 5, unit: 'MINUTES') } steps { sh ''' set -e REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && if [ -d .git ]; then git fetch --prune origin && git reset --hard origin/master && git rev-parse --short HEAD; else echo 'NO_GIT_REPO'; fi" sudo -n -u mandel -g www-data /srv/apps/mandel-dashboard/.venv/bin/python /srv/apps/mandel-dashboard/bin/deploy_stg_from_jenkins.py "${STAGING_AUDIT_PROJECT_NAME}" --command "$REMOTE_CMD" ''' } } stage('Deploy Staging') { steps { echo 'Triggering staging deploy for mandelstudio after successful CI build.' build job: 'deploy-project-stg', wait: true, propagate: true, parameters: [string(name: 'PROJECT_NAME', value: 'mandelstudio')] } } stage('Normalize Services Menu') { agent { label 'built-in' } options { timeout(time: 5, unit: 'MINUTES') } steps { sh ''' set -e REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' normalize_services_menu" sudo -n -u mandel -g www-data /srv/apps/mandel-dashboard/.venv/bin/python /srv/apps/mandel-dashboard/bin/deploy_stg_from_jenkins.py "${STAGING_AUDIT_PROJECT_NAME}" --command "$REMOTE_CMD" ''' } } stage('Fix Capabilities FAQ') { agent { label 'built-in' } options { timeout(time: 5, unit: 'MINUTES') } steps { sh ''' set -e REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' fix_capabilities_faq --apply" sudo -n -u mandel -g www-data /srv/apps/mandel-dashboard/.venv/bin/python /srv/apps/mandel-dashboard/bin/deploy_stg_from_jenkins.py "${STAGING_AUDIT_PROJECT_NAME}" --command "$REMOTE_CMD" ''' } } stage('Fix No Credit Card Copy') { agent { label 'built-in' } options { timeout(time: 5, unit: 'MINUTES') } steps { sh ''' set -e REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' fix_no_credit_card_text --apply" sudo -n -u mandel -g www-data /srv/apps/mandel-dashboard/.venv/bin/python /srv/apps/mandel-dashboard/bin/deploy_stg_from_jenkins.py "${STAGING_AUDIT_PROJECT_NAME}" --command "$REMOTE_CMD" ''' } } stage('Recompress Staging Assets') { agent { label 'built-in' } options { timeout(time: 10, unit: 'MINUTES') } steps { sh ''' set -e REMOTE_CMD="cd '${STAGING_AUDIT_PROJECT_DIR}' && '${STAGING_AUDIT_MANAGE}' compress --force" sudo -n -u mandel -g www-data /srv/apps/mandel-dashboard/.venv/bin/python /srv/apps/mandel-dashboard/bin/deploy_stg_from_jenkins.py "${STAGING_AUDIT_PROJECT_NAME}" --command "$REMOTE_CMD" ''' } } stage('Wait For Staging Health') { agent { label 'built-in' } options { timeout(time: 5, unit: 'MINUTES') } steps { sh ''' set -e for i in $(seq 1 30); do code_nl=$(curl -sS -o /dev/null -w "%{http_code}" https://mandelstudio.welkombij.mandelblog.com/ || true) code_en=$(curl -sS -o /dev/null -w "%{http_code}" https://mandelstudio.welkombij.mandelblog.com/en/ || true) echo "healthcheck attempt=$i nl=$code_nl en=$code_en" if [ "$code_nl" = "200" ] && [ "$code_en" = "200" ]; then exit 0 fi sleep 10 done echo "staging did not become healthy in time" exit 1 ''' } } stage('Post-Deploy Multilingual Audit') { agent { label 'built-in' } options { timeout(time: 10, unit: 'MINUTES') } steps { withCredentials([sshUserPrivateKey(credentialsId: 'gitea-ssh', keyFileVariable: 'GIT_KEYFILE')]) { sh ''' export GIT_SSH_COMMAND="ssh -i $GIT_KEYFILE -o StrictHostKeyChecking=accept-new" if [ -d .git ]; then if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git else git remote add origin ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git fi git fetch --tags --force --progress origin +refs/heads/master:refs/remotes/origin/master else git clone ssh://git@git.mandelblog.com:2222/salt/mandelstudio.git . git fetch --tags --force --progress origin +refs/heads/master:refs/remotes/origin/master fi git checkout -f refs/remotes/origin/master mkdir -p artifacts chmod +x scripts/run_remote_multilingual_audit.sh ./scripts/run_remote_multilingual_audit.sh ''' } script { int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json', returnStatus: true) if (status == 2) { error('Block-level multilingual issues detected or audit execution failed.') } if (status == 1) { unstable('Warn-level multilingual issues detected.') } } } post { always { archiveArtifacts artifacts: 'artifacts/multilingual-audit.json', onlyIfSuccessful: false } } } } post { always { echo 'This will always run' } success { echo 'This will run only if successful' sh ''' . .venv/bin/activate pip install coverage ''' } failure { emailext subject: "JENKINS-NOTIFICATION: ${currentBuild.currentResult}: Job '${env.JOB_NAME} #${env.BUILD_NUMBER}'", body: '${SCRIPT, template="groovy-text.template"}', recipientProviders: [culprits(), brokenBuildSuspects(), brokenTestsSuspects()] } unstable { echo 'This will run only if the run was marked as unstable' } changed { echo 'This will run only if the state of the Pipeline has changed' echo 'For example, if the Pipeline was previously failing but is now successful' } } }