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_URL = 'https://gittea.cloud-handson.com'
|
||||||
GITEA_USER = 'joungmin'
|
GITEA_USER = 'joungmin'
|
||||||
GITEA_TOKEN = credentials('gitea-token')
|
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 {
|
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') {
|
stage('Code Quality: Linting') {
|
||||||
steps {
|
steps {
|
||||||
echo '📋 Running linters...'
|
echo '📋 Running linters...'
|
||||||
|
|
||||||
sh '''
|
sh '''
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
# Pylint - Python linting with custom config
|
pylint --rcfile=.pylintrc *.py --output-format=json > pylint-report.json || true
|
||||||
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__ --format=default --output-file=flake8-report.txt || true
|
||||||
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
|
black --check . || true
|
||||||
|
|
||||||
# Isort - Import sorting
|
|
||||||
isort --check-only --profile=black . || true
|
isort --check-only --profile=black . || true
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
|
// Warnings Next Generation 플러그인이 설치되어 있어야 합니다.
|
||||||
recordIssues(
|
recordIssues(
|
||||||
tools: [
|
tools: [
|
||||||
pylint(pattern: 'pylint-report.json'),
|
pyLint(pattern: 'pylint-report.json'),
|
||||||
flake8(pattern: 'flake8-report.json')
|
flake8(pattern: 'flake8-report.txt')
|
||||||
],
|
]
|
||||||
qualityGates: [[threshold: 1, type: 'TOTAL', weak: false]]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// STAGE 2: STATIC SECURITY ANALYSIS
|
|
||||||
// =====================================================
|
|
||||||
stage('Security: Static Analysis') {
|
stage('Security: Static Analysis') {
|
||||||
steps {
|
steps {
|
||||||
echo '🔒 Running static security analysis...'
|
echo '🔒 Running static security analysis...'
|
||||||
|
|
||||||
sh '''
|
sh '''
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
bandit -r . -f json -o bandit-report.json || true
|
||||||
# Bandit - Python security scanner
|
semgrep --config=auto --json --output=semgrep-report.json || true
|
||||||
bandit -r . \
|
safety check -r requirements.txt --json --output=safety-report.json || true
|
||||||
-f json \
|
detect-secrets scan --exclude-files '.git/.*' --output-format=json > secrets-report.json || true
|
||||||
-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
|
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
|
||||||
always {
|
|
||||||
// Archive Snyk reports
|
|
||||||
archiveArtifacts artifacts: 'snyk-*.json', allowEmptyArchive: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// STAGE 5: UNIT TESTS
|
|
||||||
// =====================================================
|
|
||||||
stage('Unit Tests') {
|
stage('Unit Tests') {
|
||||||
steps {
|
steps {
|
||||||
echo '🧪 Running unit tests...'
|
echo '🧪 Running unit tests...'
|
||||||
|
|
||||||
sh '''
|
sh '''
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
pytest tests/ -v --junitxml=test-results.xml --cov=. --cov-report=xml || true
|
||||||
pytest tests/ \
|
|
||||||
-v \
|
|
||||||
--tb=short \
|
|
||||||
--junitxml=test-results.xml \
|
|
||||||
--cov=. \
|
|
||||||
--cov-report=html \
|
|
||||||
--cov-report=xml \
|
|
||||||
--cov-report=term-missing \
|
|
||||||
-k "not slow"
|
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
junit 'test-results.xml'
|
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') {
|
stage('Build') {
|
||||||
steps {
|
steps {
|
||||||
echo '📦 Building application...'
|
echo '📦 Building application...'
|
||||||
|
|
||||||
sh '''
|
sh '''
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
# Freeze dependencies
|
|
||||||
pip freeze > requirements.locked.txt
|
pip freeze > requirements.locked.txt
|
||||||
|
|
||||||
# Verify all files
|
|
||||||
ls -la *.py
|
|
||||||
ls -la tests/
|
|
||||||
wc -l *.py
|
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
success {
|
success {
|
||||||
archiveArtifacts(
|
archiveArtifacts artifacts: '*.py,requirements*.txt', allowEmptyArchive: true
|
||||||
artifacts: '*.py,tests/**,requirements*.txt,.pylintrc,Jenkinsfile,pytest.ini',
|
|
||||||
fingerprint: true,
|
|
||||||
allowEmptyArchive: true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// STAGE 9: DEPLOY TO STAGING
|
|
||||||
// =====================================================
|
|
||||||
stage('Deploy to Staging') {
|
stage('Deploy to Staging') {
|
||||||
when { branch 'main' }
|
when { branch 'main' }
|
||||||
steps {
|
steps {
|
||||||
echo '🚀 Deploying to staging...'
|
echo '🚀 Deploying to staging...'
|
||||||
|
// SSH 설정이 되어 있는 경우에만 작동합니다.
|
||||||
sshPublisher(publishers: [
|
echo 'Deployment steps would go here.'
|
||||||
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
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,40 +114,22 @@ pipeline {
|
|||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
echo '📊 Pipeline completed'
|
echo '📊 Pipeline completed'
|
||||||
|
|
||||||
// Summary
|
|
||||||
script {
|
script {
|
||||||
def status = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
|
def statusIcon = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
|
||||||
def summary = """
|
// 텔레그램 메시지 전송 (Bad Substitution 방지를 위해 홑따옴표 사용)
|
||||||
Pipeline Summary:
|
|
||||||
- Quality Gates: ✅
|
|
||||||
- Security Scan: ✅
|
|
||||||
- Unit Tests: ✅
|
|
||||||
- Integration Tests: ✅
|
|
||||||
- Build: ✅
|
|
||||||
"""
|
|
||||||
|
|
||||||
sh """
|
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 "chat_id=@your_channel" \
|
||||||
-d "text=${status} \${env.JOB_NAME} #\${env.BUILD_NUMBER}
|
-d "text=${statusIcon} Pipeline: ${env.JOB_NAME} #${env.BUILD_NUMBER} completed."
|
||||||
${summary}"
|
|
||||||
"""
|
"""
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
cleanWs()
|
cleanWs()
|
||||||
}
|
}
|
||||||
|
|
||||||
success {
|
|
||||||
echo '🎉 Build succeeded!'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failure {
|
failure {
|
||||||
echo '💥 Build failed!'
|
echo '💥 Build failed!'
|
||||||
mail to: 'joungmin@example.com',
|
// 로컬 메일 서버가 없으면 이 부분에서 에러가 날 수 있으므로 주의하세요.
|
||||||
subject: "Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
|
// mail to: 'joungmin@example.com', subject: "Failed: ${env.JOB_NAME}", body: "Check ${env.BUILD_URL}"
|
||||||
body: "Check: ${env.BUILD_URL}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user