Updated Jenkinsfile
All checks were successful
Tests / Declarative: Post Actions passed: 206
expenses_manager/pipeline/head This commit looks good

This commit is contained in:
JKuijperM 2026-09-30 13:45:20 +02:00
parent b52d3c8b3b
commit 80f56245dd

View File

@ -2,18 +2,30 @@ pipeline {
agent any agent any
environment { environment {
// settings.py demands SECRET_KEY unless DEBUG is exactly "True". // DEBUG=True keeps settings.py from enabling SECURE_SSL_REDIRECT, which
// A throwaway value lets CI run with DEBUG unset — that is, False, as // makes Django's test client receive a 301 on every request.
// in production — instead of falling back to the insecure dev key.
DEBUG = 'True' DEBUG = 'True'
SECRET_KEY = 'ci-only-value-never-used-in-production' SECRET_KEY = 'ci-only-value-never-used-in-production'
DJANGO_SETTINGS_MODULE = 'expenses_manager.settings' DJANGO_SETTINGS_MODULE = 'expenses_manager.settings'
PYTHONUNBUFFERED = '1' PYTHONUNBUFFERED = '1'
// Jenkins has no Docker daemon, so images are built on the NAS over SSH.
// Absolute paths are required: the remote non-interactive PATH is
// /usr/bin:/bin:/usr/sbin:/sbin and does not include /usr/local/bin.
NAS_HOST = '192.168.1.140'
NAS_USER = 'jenkins-ci'
NAS_DOCKER = '/usr/local/bin/docker'
CI_DIR = '/volume1/docker/ci/expenses_manager'
SSH_OPTS = '-o StrictHostKeyChecking=accept-new'
// Deliberately not "finanzas-web": that name belongs to the image
// docker-compose builds for production, and CI must never overwrite it.
IMAGE = 'expenses-manager-ci'
} }
options { options {
buildDiscarder(logRotator(numToKeepStr: '30')) buildDiscarder(logRotator(numToKeepStr: '30'))
timeout(time: 20, unit: 'MINUTES') timeout(time: 30, unit: 'MINUTES')
} }
stages { stages {
@ -48,13 +60,60 @@ pipeline {
''' '''
} }
} }
stage('Build image') {
steps {
sshagent(credentials: ['nas-ssh']) {
sh '''
SHA=$(echo "${GIT_COMMIT}" | cut -c1-7)
TAG=$(echo "${BRANCH_NAME}" | tr '/' '-')
# Ship the workspace that was just tested, rather than
# cloning again on the NAS: what gets built is then
# provably the same code the tests ran against.
ssh ${SSH_OPTS} ${NAS_USER}@${NAS_HOST} \
"rm -rf ${CI_DIR} && mkdir -p ${CI_DIR}"
tar -czf - --exclude=./expenses_manager/venv \
--exclude=./.git . \
| ssh ${SSH_OPTS} ${NAS_USER}@${NAS_HOST} \
"tar -xzf - -C ${CI_DIR}"
ssh ${SSH_OPTS} ${NAS_USER}@${NAS_HOST} \
"cd ${CI_DIR} && sudo ${NAS_DOCKER} build \
-t ${IMAGE}:${SHA} -t ${IMAGE}:${TAG} ."
'''
}
}
}
// A successful build only proves the Dockerfile is syntactically sound.
// Running Django's own checks inside the image proves it can actually
// start: settings load, middleware resolves, static files are collected.
stage('Smoke test image') {
steps {
sshagent(credentials: ['nas-ssh']) {
sh '''
SHA=$(echo "${GIT_COMMIT}" | cut -c1-7)
ssh ${SSH_OPTS} ${NAS_USER}@${NAS_HOST} \
"sudo ${NAS_DOCKER} run --rm \
-e SECRET_KEY=smoke-test-only \
-e DEBUG=False \
-e ALLOWED_HOSTS=localhost \
-e DB_ENGINE=sqlite3 \
${IMAGE}:${SHA} python manage.py check"
'''
}
}
}
} }
post { post {
always { always {
// allowEmptyResults: false is deliberate. A build where pytest died // allowEmptyResults: false is deliberate. A build where pytest died
// before collecting anything produces no XML, and that must fail // before collecting anything produces no XML, and that must fail
// rather than pass quietly — as happened for months in 2026. // rather than pass quietly.
junit allowEmptyResults: false, testResults: 'expenses_manager/junit.xml' junit allowEmptyResults: false, testResults: 'expenses_manager/junit.xml'
} }
failure { failure {