383 lines
14 KiB
Groovy
383 lines
14 KiB
Groovy
pipeline {
|
|
agent any
|
|
|
|
environment {
|
|
// Credentials
|
|
ORACLE_DSN = credentials('oracle-dsn')
|
|
ORACLE_USER = credentials('oracle-user')
|
|
ORACLE_PASSWORD = credentials('oracle-password')
|
|
TELEGRAM_BOT_TOKEN = credentials('telegram-bot-token')
|
|
GITEA_URL = 'https://gittea.cloud-handson.com'
|
|
GITEA_USER = 'joungmin'
|
|
GITEA_TOKEN = credentials('gitea-token')
|
|
|
|
// SonarQube (uncomment and configure)
|
|
// SONAR_URL = 'http://localhost:9000'
|
|
// SONAR_TOKEN = credentials('sonarqube-token')
|
|
|
|
// Snyk (uncomment and configure)
|
|
// SNYK_TOKEN = credentials('snyk-token')
|
|
|
|
// Paths
|
|
WORKSPACE = "${WORKSPACE}"
|
|
}
|
|
|
|
stages {
|
|
// =====================================================
|
|
// STAGE 1: CODE QUALITY (BEFORE BUILD)
|
|
// =====================================================
|
|
stage('Code Quality: Linting') {
|
|
steps {
|
|
echo '📋 Running linters...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
# Pylint - Python linting with custom config
|
|
pylint --rcfile=.pylintrc \
|
|
*.py \
|
|
--output-format=json \
|
|
--reports=y \
|
|
> pylint-report.json || true
|
|
|
|
# Flake8 - Style guide enforcement
|
|
flake8 . \
|
|
--max-line-length=120 \
|
|
--exclude=venv,__pycache__,node_modules,build,dist \
|
|
--format=json \
|
|
--output-file=flake8-report.json || true
|
|
|
|
# Black - Code formatting check
|
|
black --check . || true
|
|
|
|
# Isort - Import sorting
|
|
isort --check-only --profile=black . || true
|
|
'''
|
|
}
|
|
post {
|
|
always {
|
|
recordIssues(
|
|
tools: [
|
|
pylint(pattern: 'pylint-report.json'),
|
|
flake8(pattern: 'flake8-report.json')
|
|
],
|
|
qualityGates: [[threshold: 1, type: 'TOTAL', weak: false]]
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 2: STATIC SECURITY ANALYSIS
|
|
// =====================================================
|
|
stage('Security: Static Analysis') {
|
|
steps {
|
|
echo '🔒 Running static security analysis...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
# Bandit - Python security scanner
|
|
bandit -r . \
|
|
-f json \
|
|
-o bandit-report.json || true
|
|
|
|
# Semgrep - Pattern matching security scan
|
|
semgrep --config=auto \
|
|
--json \
|
|
--output=semgrep-report.json \
|
|
--skip-vendor || true
|
|
|
|
# Safety - Known vulnerabilities check
|
|
safety check -r requirements.txt \
|
|
--json \
|
|
--output=safety-report.json || true
|
|
|
|
# Detect Secrets - Hardcoded secrets scan
|
|
detect-secrets scan \
|
|
--exclude-files '.git/.*' \
|
|
--output-format=json \
|
|
> secrets-report.json || true
|
|
'''
|
|
}
|
|
post {
|
|
always {
|
|
recordIssues(
|
|
tools: [bandit(pattern: 'bandit-report.json')],
|
|
qualityGates: [[threshold: 1, type: 'HIGH', weak: false]]
|
|
)
|
|
echo '✅ Static security analysis completed'
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 3: SONARQUBE QUALITY GATE
|
|
// =====================================================
|
|
stage('Security: SonarQube') {
|
|
when {
|
|
expression { env.SONAR_URL != null }
|
|
}
|
|
steps {
|
|
echo '🔍 Running SonarQube analysis...'
|
|
|
|
withSonarQubeEnv('SonarQube') {
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
sonar-scanner \
|
|
-Dsonar.projectKey=openclaw \
|
|
-Dsonar.sources=. \
|
|
-Dsonar.python.version=3.11 \
|
|
-Dsonar.exclusions=venv/**,__pycache/**,tests/** \
|
|
-Dsonar.coverage.exclusions=tests/**,venv/**
|
|
'''
|
|
}
|
|
|
|
// Wait for quality gate
|
|
timeout(time: 5, unit: 'MINUTES') {
|
|
waitForQualityGate abortPipeline: true
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 4: SNYK VULNERABILITY SCAN
|
|
// =====================================================
|
|
stage('Security: Snyk') {
|
|
when {
|
|
expression { env.SNYK_TOKEN != null }
|
|
}
|
|
steps {
|
|
echo '🛡️ Running Snyk vulnerability scan...'
|
|
|
|
withCredentials([string(credentialsId: 'snyk-token', variable: 'SNYK_TOKEN')]) {
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
# Snyk test for Python dependencies
|
|
snyk test \
|
|
--all-projects \
|
|
--severity-threshold=high \
|
|
--json-file-output=snyk-report.json || true
|
|
|
|
# Snyk code (SAST)
|
|
snyk code test \
|
|
--json-file-output=snyk-code-report.json || true
|
|
'''
|
|
}
|
|
}
|
|
post {
|
|
always {
|
|
// Archive Snyk reports
|
|
archiveArtifacts artifacts: 'snyk-*.json', allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 5: UNIT TESTS
|
|
// =====================================================
|
|
stage('Unit Tests') {
|
|
steps {
|
|
echo '🧪 Running unit tests...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
pytest tests/ \
|
|
-v \
|
|
--tb=short \
|
|
--junitxml=test-results.xml \
|
|
--cov=. \
|
|
--cov-report=html \
|
|
--cov-report=xml \
|
|
--cov-report=term-missing \
|
|
-k "not slow"
|
|
'''
|
|
}
|
|
post {
|
|
always {
|
|
junit 'test-results.xml'
|
|
cobertura(
|
|
coberturaPackage: 'coverage.xml',
|
|
failNoStubs: false,
|
|
onlyStable: false
|
|
)
|
|
publishHTML([
|
|
reportDir: 'htmlcov',
|
|
reportFiles: 'index.html',
|
|
reportName: 'Coverage Report'
|
|
])
|
|
}
|
|
failure {
|
|
error '❌ Unit tests failed!'
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 6: SECURITY UNIT TESTS
|
|
// =====================================================
|
|
stage('Security Tests') {
|
|
steps {
|
|
echo '🔐 Running security unit tests...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
pytest tests/test_security.py \
|
|
-v \
|
|
--tb=short \
|
|
--junitxml=security-test-results.xml
|
|
'''
|
|
}
|
|
post {
|
|
always {
|
|
junit 'security-test-results.xml'
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 7: INTEGRATION TESTS
|
|
// =====================================================
|
|
stage('Integration Tests') {
|
|
steps {
|
|
echo '🔗 Running integration tests...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
# Oracle connection test
|
|
python3 -c "
|
|
import oracledb
|
|
try:
|
|
conn = oracledb.connect(
|
|
user=\"${ORACLE_USER}\",
|
|
password=\"${ORACLE_PASSWORD}\",
|
|
dsn=\"${ORACLE_DSN}\"
|
|
)
|
|
cursor = conn.cursor()
|
|
cursor.execute('SELECT 1 FROM DUAL')
|
|
print('✅ Oracle connection successful')
|
|
conn.close()
|
|
except Exception as e:
|
|
print(f'⚠️ Oracle test: {e}')
|
|
" || echo "⚠️ Oracle connection skipped"
|
|
|
|
# Telegram API test
|
|
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" \
|
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print('✅ Telegram:', d.get('result',{}).get('username','N/A'))" \
|
|
|| echo "⚠️ Telegram test skipped"
|
|
|
|
# Gitea API test
|
|
curl -s -u "${GITEA_USER}:${GITEA_TOKEN}" "${GITEA_URL}/api/v1/user" \
|
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print('✅ Gitea:', d.get('username','N/A'))" \
|
|
|| echo "⚠️ Gitea test skipped"
|
|
'''
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 8: BUILD
|
|
// =====================================================
|
|
stage('Build') {
|
|
steps {
|
|
echo '📦 Building application...'
|
|
|
|
sh '''
|
|
source venv/bin/activate
|
|
|
|
# Freeze dependencies
|
|
pip freeze > requirements.locked.txt
|
|
|
|
# Verify all files
|
|
ls -la *.py
|
|
ls -la tests/
|
|
wc -l *.py
|
|
'''
|
|
}
|
|
post {
|
|
success {
|
|
archiveArtifacts(
|
|
artifacts: '*.py,tests/**,requirements*.txt,.pylintrc,Jenkinsfile,pytest.ini',
|
|
fingerprint: true,
|
|
allowEmptyArchive: true
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// STAGE 9: DEPLOY TO STAGING
|
|
// =====================================================
|
|
stage('Deploy to Staging') {
|
|
when { branch 'main' }
|
|
steps {
|
|
echo '🚀 Deploying to staging...'
|
|
|
|
sshPublisher(publishers: [
|
|
sshPublisherDesc(
|
|
configName: 'ubuntu-server',
|
|
transfers: [
|
|
sshTransfer(
|
|
sourceFiles: '*.py,tests/,requirements*.txt,.pylintrc,Jenkinsfile,pytest.ini',
|
|
remoteDirectory: '/home/joungmin/openclaw',
|
|
execCommand: '''
|
|
cd /home/joungmin/openclaw
|
|
source venv/bin/activate
|
|
pip install -r requirements.txt
|
|
pytest tests/ --tb=short
|
|
pytest tests/test_security.py --tb=short
|
|
supervisorctl restart openclaw
|
|
'
|
|
)
|
|
]
|
|
)
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
always {
|
|
echo '📊 Pipeline completed'
|
|
|
|
// Summary
|
|
script {
|
|
def status = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
|
|
def summary = """
|
|
Pipeline Summary:
|
|
- Quality Gates: ✅
|
|
- Security Scan: ✅
|
|
- Unit Tests: ✅
|
|
- Integration Tests: ✅
|
|
- Build: ✅
|
|
"""
|
|
|
|
sh """
|
|
curl -s -X POST "https://api.telegram.org/bot\${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
-d "chat_id=@your_channel" \
|
|
-d "text=${status} \${env.JOB_NAME} #\${env.BUILD_NUMBER}
|
|
${summary}"
|
|
"""
|
|
}
|
|
|
|
// Cleanup
|
|
cleanWs()
|
|
}
|
|
|
|
success {
|
|
echo '🎉 Build succeeded!'
|
|
}
|
|
|
|
failure {
|
|
echo '💥 Build failed!'
|
|
mail to: 'joungmin@example.com',
|
|
subject: "Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
|
|
body: "Check: ${env.BUILD_URL}"
|
|
}
|
|
}
|
|
}
|