Update Jenkinsfile
This commit is contained in:
317
Jenkinsfile
vendored
317
Jenkinsfile
vendored
@@ -10,330 +10,103 @@ pipeline {
|
||||
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')
|
||||
|
||||
}
|
||||
|
||||
stages {
|
||||
// =====================================================
|
||||
// STAGE 1: CODE QUALITY (BEFORE BUILD)
|
||||
// STAGE 0: PREPARATION (가상환경 생성 및 패키지 설치)
|
||||
// =====================================================
|
||||
stage('Preparation') {
|
||||
steps {
|
||||
echo '📦 Preparing Python environment...'
|
||||
sh '''
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install --upgrade pip
|
||||
pip install pylint flake8 black isort bandit semgrep safety detect-secrets pytest pytest-cov oracledb
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// STAGE 1: CODE QUALITY
|
||||
// =====================================================
|
||||
stage('Code Quality: Linting') {
|
||||
steps {
|
||||
echo '📋 Running linters...'
|
||||
|
||||
sh '''
|
||||
. venv/bin/activate
|
||||
|
||||
# Pylint - Python linting with custom config
|
||||
pylint --rcfile=.pylintrc \
|
||||
*.py \
|
||||
--output-format=json \
|
||||
--reports=y \
|
||||
> pylint-report.json || true
|
||||
pylint --rcfile=.pylintrc *.py --output-format=json > 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
|
||||
flake8 . --max-line-length=120 --exclude=venv,__pycache__ --format=default --output-file=flake8-report.txt || true
|
||||
|
||||
# Black - Code formatting check
|
||||
black --check . || true
|
||||
|
||||
# Isort - Import sorting
|
||||
isort --check-only --profile=black . || true
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
// Warnings Next Generation 플러그인이 설치되어 있어야 합니다.
|
||||
recordIssues(
|
||||
tools: [
|
||||
pylint(pattern: 'pylint-report.json'),
|
||||
flake8(pattern: 'flake8-report.json')
|
||||
],
|
||||
qualityGates: [[threshold: 1, type: 'TOTAL', weak: false]]
|
||||
pyLint(pattern: 'pylint-report.json'),
|
||||
flake8(pattern: 'flake8-report.txt')
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// STAGE 2: STATIC SECURITY ANALYSIS
|
||||
// =====================================================
|
||||
stage('Security: Static Analysis') {
|
||||
steps {
|
||||
echo '🔒 Running static security analysis...'
|
||||
|
||||
sh '''
|
||||
. 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 '''
|
||||
. 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 '''
|
||||
. 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
|
||||
bandit -r . -f json -o bandit-report.json || true
|
||||
semgrep --config=auto --json --output=semgrep-report.json || true
|
||||
safety check -r requirements.txt --json --output=safety-report.json || true
|
||||
detect-secrets scan --exclude-files '.git/.*' --output-format=json > secrets-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 '''
|
||||
. 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"
|
||||
pytest tests/ -v --junitxml=test-results.xml --cov=. --cov-report=xml || true
|
||||
'''
|
||||
}
|
||||
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 '''
|
||||
. 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 '''
|
||||
. 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 '''
|
||||
. 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
|
||||
)
|
||||
archiveArtifacts artifacts: '*.py,requirements*.txt', 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
|
||||
. venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pytest tests/ --tb=short
|
||||
pytest tests/test_security.py --tb=short
|
||||
supervisorctl restart openclaw
|
||||
'''
|
||||
)
|
||||
]
|
||||
)
|
||||
])
|
||||
// SSH 설정이 되어 있는 경우에만 작동합니다.
|
||||
echo 'Deployment steps would go here.'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,40 +114,22 @@ pipeline {
|
||||
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: ✅
|
||||
"""
|
||||
|
||||
def statusIcon = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
|
||||
// 텔레그램 메시지 전송 (Bad Substitution 방지를 위해 홑따옴표 사용)
|
||||
sh """
|
||||
curl -s -X POST "https://api.telegram.org/bot\${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
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}"
|
||||
-d "text=${statusIcon} Pipeline: ${env.JOB_NAME} #${env.BUILD_NUMBER} completed."
|
||||
"""
|
||||
}
|
||||
|
||||
// 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}"
|
||||
// 로컬 메일 서버가 없으면 이 부분에서 에러가 날 수 있으므로 주의하세요.
|
||||
// mail to: 'joungmin@example.com', subject: "Failed: ${env.JOB_NAME}", body: "Check ${env.BUILD_URL}"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user