-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJenkinsfile
More file actions
269 lines (226 loc) Β· 10.3 KB
/
Jenkinsfile
File metadata and controls
269 lines (226 loc) Β· 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
pipeline {
agent any
tools {
nodejs "NODEJS_24"
}
options {
timestamps()
ansiColor('xterm')
}
stages {
stage('Build') {
steps {
echo "π¨ Building the project"
// Example: if packaging into Docker
// sh 'docker build -t my-inventory-app:latest .'
sh 'npm install' // simple build step for Node.js
}
}
stage('Test') {
steps {
sh 'npm run ci:test'
}
post {
always {
junit 'reports/junit/*.xml'
archiveArtifacts artifacts: 'coverage/**', fingerprint: true, onlyIfSuccessful: false
}
}
}
stage('Code Quality Analysis') {
steps {
withSonarQubeEnv('SonarCloud') {
sh '''
set -e
mkdir -p .scanner
SCANNER_VERSION=6.2.1.4610
if [ ! -x ".scanner/sonar-scanner-$SCANNER_VERSION-linux-x64/bin/sonar-scanner" ]; then
curl -sSL -o .scanner/scanner.zip \
https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SCANNER_VERSION-linux-x64.zip
unzip -q .scanner/scanner.zip -d .scanner
rm .scanner/scanner.zip
fi
export PATH="$PWD/.scanner/sonar-scanner-$SCANNER_VERSION-linux-x64/bin:$PATH"
sonar-scanner \
-Dsonar.projectKey=$(grep '^sonar.projectKey=' sonar-project.properties | cut -d= -f2) \
-Dsonar.organization=$(grep '^sonar.organization=' sonar-project.properties | cut -d= -f2) \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.token=$SONAR_AUTH_TOKEN
'''
}
}
}
stage('Security') {
environment {
// Gates (fail build when >0 of these severities)
FAIL_ON_CRITICAL = '1'
FAIL_ON_HIGH = '1'
FAIL_ON_MODERATE = '0' // keep 0 to not fail on Mod/Low
FAIL_ON_LOW = '0'
// Optional: try auto-fix first, then re-scan (0 = off, 1 = on)
AUTO_FIX = '0'
}
steps {
echo "π‘οΈ Running dependency security audit..."
sh '''
set -e
mkdir -p reports/security
# --- 1) optional auto-fix (package-lock only to avoid surprise code changes)
if [ "$AUTO_FIX" = "1" ]; then
echo "π§ Attempting npm audit fix (lockfile-only)..."
npm audit fix --package-lock-only || true
# ensure deps match lockfile after fix attempt
npm ci
fi
# --- 2) run audit and generate summary
npm audit --json > reports/security/npm-audit.json || true
node scripts/generate-security-summary.js reports/security/npm-audit.json > reports/security/SECURITY_SUMMARY.md
echo "---- SECURITY SUMMARY ----"
cat reports/security/SECURITY_SUMMARY.md || true
echo "--------------------------"
# --- 3) parse counts via Node (works across npm versions)
JSON=reports/security/npm-audit.json
CRIT=$(node -e "let j=require('./$JSON');let c=0;if(j.vulnerabilities){for(const k in j.vulnerabilities){if(j.vulnerabilities[k].severity==='critical'){c+=(j.vulnerabilities[k].via||[]).length||1}}}else if(j.advisories){for(const k in j.advisories){if(j.advisories[k].severity==='critical'){c++}}}console.log(c)")
HIGH=$(node -e "let j=require('./$JSON');let c=0;if(j.vulnerabilities){for(const k in j.vulnerabilities){if(j.vulnerabilities[k].severity==='high'){c+=(j.vulnerabilities[k].via||[]).length||1}}}else if(j.advisories){for(const k in j.advisories){if(j.advisories[k].severity==='high'){c++}}}console.log(c)")
MOD=$(node -e "let j=require('./$JSON');let c=0;if(j.vulnerabilities){for(const k in j.vulnerabilities){if(j.vulnerabilities[k].severity==='moderate'){c+=(j.vulnerabilities[k].via||[]).length||1}}}else if(j.advisories){for(const k in j.advisories){if(j.advisories[k].severity==='moderate'){c++}}}console.log(c)")
LOW=$(node -e "let j=require('./$JSON');let c=0;if(j.vulnerabilities){for(const k in j.vulnerabilities){if(j.vulnerabilities[k].severity==='low'){c+=(j.vulnerabilities[k].via||[]).length||1}}}else if(j.advisories){for(const k in j.advisories){if(j.advisories[k].severity==='low'){c++}}}console.log(c)")
echo "Counts => Critical:$CRIT High:$HIGH Moderate:$MOD Low:$LOW"
# --- 4) gate logic
FAIL=0
[ "$FAIL_ON_CRITICAL" = "1" ] && [ "$CRIT" -gt 0 ] && { echo "β Critical vulns found"; FAIL=1; }
[ "$FAIL_ON_HIGH" = "1" ] && [ "$HIGH" -gt 0 ] && { echo "β High vulns found"; FAIL=1; }
[ "$FAIL_ON_MODERATE" = "1" ] && [ "$MOD" -gt 0 ] && { echo "β Moderate vulns found"; FAIL=1; }
[ "$FAIL_ON_LOW" = "1" ] && [ "$LOW" -gt 0 ] && { echo "β Low vulns found"; FAIL=1; }
# exit code 5 = fail; 0 = ok. For only Mod/Low, we allow post { unstable } to handle.
if [ "$FAIL" -ne 0 ]; then
echo "Security gate failed. See SECURITY_SUMMARY.md and npm-audit.json under reports/security/"
exit 5
fi
'''
}
post {
always {
archiveArtifacts artifacts: 'reports/security/*', fingerprint: true, onlyIfSuccessful: false
}
success {
script {
// No JSON, no Slurper β just read the status marker written by the shell step
def marker = sh(returnStdout: true, script: "cat reports/security/build_status 2>/dev/null || echo OK").trim()
if (marker == 'UNSTABLE') {
echo "β οΈ Moderate/Low vulns found -> marking build UNSTABLE (see SECURITY_SUMMARY.md)."
currentBuild.result = 'UNSTABLE'
} else {
echo "β
Security gate clean."
}
}
}
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh '''
set -e
# Talk to your existing DinD
export DOCKER_HOST=tcp://dind:2375
# We will bring the stack up under a NEW project name "ci"
export COMPOSE_PROJECT_NAME=ci
echo "Docker endpoint: ${DOCKER_HOST}"
docker version
docker-compose version
echo "Cleaning previous project/network created without COMPOSE_PROJECT_NAME..."
# Old project name is derived from your job/workspace: "devsecops_main"
docker-compose -p devsecops_main -f docker-compose.yml down -v --remove-orphans || true
docker network rm devsecops_main_default || true
# (optional) verify no leftovers
docker network ls | grep -E 'devsecops_main_default|ci_default' || true
echo "Bringing up only web under project 'ci'..."
docker-compose -p ci -f docker-compose.yml up -d --build web
echo "Waiting for health..."
for i in $(seq 1 30); do
if docker-compose -p ci -f docker-compose.yml exec -T web sh -lc "wget -qO- http://localhost:3000/health >/dev/null 2>&1"; then
echo "Service healthy."; break
fi
echo "not ready yet... ($i/30)"; sleep 2
done
docker-compose -p ci -f docker-compose.yml ps
'''
}
post {
failure {
sh '''
mkdir -p reports/deploy
docker-compose -p ci -f docker-compose.yml logs --no-color > reports/deploy/compose-logs.txt || true
'''
archiveArtifacts artifacts: 'reports/deploy/**', fingerprint: true, onlyIfSuccessful: false
}
}
}
stage('Release') {
when { branch 'main' }
steps {
echo "π¦ [Release Stage] Starting release process (dummy values)"
sh '''
set -e
# --- Dummy AWS/CodeDeploy Simulation ---
echo "Packaging build into zip..."
mkdir -p reports/release
echo "Pretend we zipped the repo here" > reports/release/DevSecOpsBuild.zip
echo "Uploading to S3 bucket (dummy-bucket)"
echo "aws s3 cp DevSecOpsBuild.zip s3://dummy-bucket/builds/DevSecOpsBuild.zip"
echo "Creating CodeDeploy deployment"
echo "aws deploy create-deployment --application-name DummyApp --deployment-group-name DummyDG"
echo "Simulating wait for deployment status..."
for i in $(seq 1 5); do
echo " -> pretending status = InProgress ($i/5)"
sleep 1
done
echo "β
Dummy deployment succeeded!"
'''
}
post {
success {
archiveArtifacts artifacts: 'reports/release/**', fingerprint: true, onlyIfSuccessful: true
echo "Artifacts archived from dummy release stage."
}
failure {
echo "β Dummy release stage failed."
}
}
}
stage('Monitoring & Alerting') {
steps {
withCredentials([string(credentialsId: 'newrelic-api-key', variable: 'NR_API_KEY')]) {
sh '''
set -e
APP_NAME="DevSecOpsApp" # just a label
NR_ACCOUNT_ID="12345678" # replace with your real Account ID
echo "π Sending test metric to New Relic..."
curl -X POST "https://metric-api.newrelic.com/metric/v1" \
-H "Api-Key:$NR_API_KEY" \
-H "Content-Type: application/json" \
-d "[
{
\\"metrics\\": [
{
\\"name\\": \\"jenkins.deploy.success\\",
\\"type\\": \\"count\\",
\\"value\\": 1,
\\"timestamp\\": $(date +%s),
\\"attributes\\": { \\"service\\": \\"$APP_NAME\\" }
}
]
}
]"
echo "β
Metric sent to New Relic."
'''
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'reports/junit/*.xml', fingerprint: true, onlyIfSuccessful: false
}
}
}