From 000876cccd0178239a12c621bebbd773823732b2 Mon Sep 17 00:00:00 2001 From: lenape Date: Sun, 29 Jun 2025 18:04:11 +0000 Subject: [PATCH] automated terminal push --- Jenkinsfile | 148 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 25 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 71ca95c..2b75a91 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,12 +2,24 @@ pipeline { agent any environment { - BUILD_IMAGE = 'python:3.9-slim' - AWS_REGION = 'us-east-1' - AWS_ACCOUNT_ID = '123456789012' - CODEART_DOMAIN = 'jacquesingram' - CODEART_REPO = 'hello-codeartifact' - CODEART_TOKEN = credentials('codeartifact-token') + // 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 { @@ -17,17 +29,22 @@ pipeline { } } - stage('Setup') { + stage('Authenticate & Configure') { steps { - script { - docker.image(env.BUILD_IMAGE).inside { - sh ''' - pip install --upgrade pip - pip install build twine pytest - if [ -f requirements.txt ]; then - pip install -r requirements.txt - fi - ''' + 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/" } } } @@ -36,21 +53,88 @@ pipeline { stage('Build') { steps { script { - docker.image(env.BUILD_IMAGE).inside { + // Use Docker for consistent, isolated build environment + docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp -e PIP_CACHE_DIR=/tmp/.pip') { sh ''' - python -m build + # 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 --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 ''' + # Corporate standard: CodeArtifact primary, PyPI fallback + export PIP_INDEX_URL="${CODEART_URL}" + export PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" + + pip install 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 { + docker.image(env.BUILD_IMAGE).inside('-e HOME=/tmp -e PIP_CACHE_DIR=/tmp/.pip') { sh ''' - python -m pytest tests/ --junitxml=test-results.xml || true + # 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 -r requirements-test.txt + fi + + # Install the built package for testing + pip install dist/*.whl + + # Run tests if they exist + if [ -f pytest.ini ] || [ -d tests ]; then + python -m pytest --junitxml=test-results.xml || true + fi ''' } } @@ -90,18 +174,32 @@ EOF } } } - } + post { always { // Archive build artifacts - archiveArtifacts artifacts: 'dist/**', fingerprint: true, allowEmptyArchive: true + archiveArtifacts artifacts: 'dist/*', allowEmptyArchive: true + + // Clean up workspace to prevent credential leakage + cleanWs() } + success { - echo 'Pipeline completed successfully!' + echo '✅ Build succeeded and package published to CodeArtifact.' + + // Notify success (Slack, email, etc.) + // slackSend channel: '#builds', color: 'good', message: "✅ ${env.JOB_NAME} - ${env.BUILD_NUMBER} succeeded" } + failure { - echo 'Pipeline failed!' + echo '❌ Build failed — check the console output for errors.' + + // Notify failure + // slackSend channel: '#builds', color: 'danger', message: "❌ ${env.JOB_NAME} - ${env.BUILD_NUMBER} failed" + } + + unstable { + echo '⚠️ Build unstable — tests may have failed.' } } -} \ No newline at end of file