pipeline { agent any environment { // Non-secret config injected from Jenkins Credentials (Secret Text) AWS_REGION = credentials('AWS_REGION') AWS_ACCOUNT_ID = credentials('AWS_ACCOUNT_ID') CODEART_DOMAIN = credentials('CODEART_DOMAIN') CODEART_REPO = credentials('CODEART_REPO') // Build configuration - Corporate-friendly non-root approach PYTHON_VERSION = '3.11-slim' BUILD_IMAGE = "python:${PYTHON_VERSION}" } options { // Prevent concurrent builds disableConcurrentBuilds() // Keep build logs for audit buildDiscarder(logRotator(numToKeepStr: '50')) // Timeout protection timeout(time: 30, unit: 'MINUTES') } stages { stage('Checkout') { steps { checkout scm } } stage('Authenticate & Configure') { steps { withAWS(credentials: 'jenkins-codeartifact', region: env.AWS_REGION) { script { // Fetch a short-lived CodeArtifact token env.CODEART_TOKEN = sh( script: """ aws codeartifact get-authorization-token \\ --domain ${env.CODEART_DOMAIN} \\ --domain-owner ${env.AWS_ACCOUNT_ID} \\ --query authorizationToken --output text """, returnStdout: true ).trim() // Store repository URL for Docker container env.CODEART_URL = "https://aws:${env.CODEART_TOKEN}@${env.CODEART_DOMAIN}-${env.AWS_ACCOUNT_ID}.d.codeartifact.${env.AWS_REGION}.amazonaws.com/pypi/${env.CODEART_REPO}/simple/" } } } } stage('Build') { steps { script { // Use Docker for consistent, isolated build environment docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp -e PIP_CACHE_DIR=/tmp/.pip') { sh ''' # Add local bin to PATH export PATH="/tmp/.local/bin:$PATH" # Corporate standard: CodeArtifact primary, PyPI fallback export PIP_INDEX_URL="${CODEART_URL}" export PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" # Install build dependencies pip install --user --upgrade setuptools wheel twine # Build the package python3 setup.py sdist bdist_wheel ''' } } } } stage('Security Scan') { parallel { stage('Trivy Filesystem Scan') { steps { sh ''' docker run --rm -v ${WORKSPACE}:/project \\ aquasec/trivy:latest fs \\ --severity HIGH,CRITICAL \\ --exit-code 1 \\ --format json \\ --output trivy-fs-report.json \\ /project ''' // Archive security scan results archiveArtifacts artifacts: 'trivy-fs-report.json', allowEmptyArchive: true } } stage('Package Vulnerability Scan') { when { // Only scan if build artifacts exist expression { fileExists('dist/*.whl') } } steps { script { docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp -e PIP_CACHE_DIR=/tmp/.pip') { sh ''' # Add local bin to PATH export PATH="/tmp/.local/bin:$PATH" # Corporate standard: CodeArtifact primary, PyPI fallback export PIP_INDEX_URL="${CODEART_URL}" export PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" pip install --user safety safety check --json --output safety-report.json || true ''' } } archiveArtifacts artifacts: 'safety-report.json', allowEmptyArchive: true } } } } stage('Test') { steps { script { docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp -e PIP_CACHE_DIR=/tmp/.pip') { sh ''' # Add local bin to PATH export PATH="/tmp/.local/bin:$PATH" # Corporate standard: CodeArtifact primary, PyPI fallback export PIP_INDEX_URL="${CODEART_URL}" export PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" # Install test dependencies if they exist if [ -f requirements-test.txt ]; then pip install --user -r requirements-test.txt fi # Install the built package for testing pip install --user dist/*.whl # Run tests if they exist if [ -f pytest.ini ] || [ -d tests ]; then pip install --user pytest python -m pytest --junitxml=test-results.xml || true fi ''' } } } post { always { // Publish test results if they exist script { if (fileExists('test-results.xml')) { junit 'test-results.xml' } } } } } stage('Publish') { steps { script { docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp') { sh ''' # Add local bin to PATH export PATH="/tmp/.local/bin:$PATH" # Corporate standard: CodeArtifact primary, PyPI fallback export PIP_INDEX_URL="${CODEART_URL}" export PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" # Install twine pip install --user twine # Configure twine for CodeArtifact in /tmp cat > /tmp/.pypirc <