Repository navigation
Expand file tree
/
Copy patheval.sh
More file actions
277 lines (236 loc) · 11.3 KB
/
Copy patheval.sh
File metadata and controls
277 lines (236 loc) · 11.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
270
271
272
273
274
275
276
277
#!/usr/bin/env bash
PASS_COUNT=0
FAIL_COUNT=0
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
pass() {
echo -e " ${GREEN}✓ PASS${RESET} $1"
PASS_COUNT=$((PASS_COUNT + 1))
}
fail() {
local reason="${2:-}"
if [ -n "$reason" ]; then
echo -e " ${RED}✗ FAIL${RESET} $1 ${DIM}→ ${reason}${RESET}"
else
echo -e " ${RED}✗ FAIL${RESET} $1"
fi
FAIL_COUNT=$((FAIL_COUNT + 1))
}
warn() {
echo -e " ${YELLOW}⚠ WARN${RESET} $1"
}
section() {
echo ""
echo -e "${BLUE}${BOLD}$1${RESET}"
echo -e "${DIM}────────────────────────────────────────────────────${RESET}"
}
mongo_container() {
docker ps --format '{{.Names}} {{.Image}}' 2>/dev/null \
| grep -i 'mongo' | head -1 | awk '{print $1}'
}
app_container() {
docker ps --format '{{.Names}} {{.Ports}}' 2>/dev/null \
| grep '0\.0\.0\.0:80->' | head -1 | awk '{print $1}'
}
# ── Header ────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}║ DevOps Assignment Grader v2.0 ║${RESET}"
echo -e "${BOLD}║ 30 marks · 15 checks · 2 marks each ║${RESET}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${RESET}"
echo -e "${DIM} Host : $(hostname)${RESET}"
echo -e "${DIM} Date : $(date)${RESET}"
# ── Section A — Environment Setup ─────────────────────────────────────────────
section "Section A — Environment Setup (Checks 01–03)"
# 01 — Docker installed
if command -v docker &>/dev/null; then
pass "Check 01 Docker is installed $(docker --version 2>/dev/null | head -1)"
else
fail "Check 01 Docker is installed" "'docker' command not found"
fi
# 02 — Docker daemon running
if systemctl is-active --quiet docker 2>/dev/null; then
pass "Check 02 Docker daemon is active (systemd)"
elif docker info &>/dev/null; then
pass "Check 02 Docker daemon is active (socket reachable)"
else
fail "Check 02 Docker daemon is active" "docker info failed"
fi
# 03 — System packages up to date
apt-get update -qq 2>/dev/null
UPGRADABLE=$(apt-get -s upgrade 2>/dev/null | grep -c '^Inst' || true)
if [ "$UPGRADABLE" -eq 0 ]; then
pass "Check 03 System packages are up to date"
else
fail "Check 03 System packages are up to date" "${UPGRADABLE} package(s) still upgradable"
fi
# ── Section B — Container Setup ───────────────────────────────────────────────
section "Section B — Container Setup (Checks 04–06)"
# 04 — Exactly 2 containers running
CONTAINER_COUNT=$(docker ps -q 2>/dev/null | wc -l | tr -d ' ')
if [ "$CONTAINER_COUNT" -eq 2 ]; then
pass "Check 04 Exactly 2 containers are running"
else
fail "Check 04 Exactly 2 containers are running" "found ${CONTAINER_COUNT} running container(s)"
fi
# 05 — MongoDB container present
MONGO_CTR=$(mongo_container)
if [ -n "$MONGO_CTR" ]; then
MONGO_IMG=$(docker inspect "$MONGO_CTR" --format '{{.Config.Image}}' 2>/dev/null)
pass "Check 05 MongoDB container is running [${MONGO_CTR} · ${MONGO_IMG}]"
else
fail "Check 05 MongoDB container is running" "no running container with mongo image found"
fi
# 06 — App accessible on host port 80
APP_CTR=$(app_container)
if [ -n "$APP_CTR" ]; then
pass "Check 06 App container is bound to host port 80 [${APP_CTR}]"
else
fail "Check 06 App container is bound to host port 80" "no container mapping 0.0.0.0:80 found"
fi
# ── Section C — Application Behaviour ────────────────────────────────────────
section "Section C — Application Behaviour (Checks 07–10)"
# 07 — HTTP 200 from localhost and public IP
HTTP_LOCAL=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 http://localhost 2>/dev/null || echo '000')
PUBLIC_IP=$(curl -s --connect-timeout 3 http://169.254.169.254/latest/meta-data/public-ipv4 2>/dev/null || echo '')
if [ -n "$PUBLIC_IP" ]; then
HTTP_PUBLIC=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 "http://${PUBLIC_IP}" 2>/dev/null || echo '000')
else
HTTP_PUBLIC="$HTTP_LOCAL"
fi
if [ "$HTTP_LOCAL" = "200" ] && [ "$HTTP_PUBLIC" = "200" ]; then
pass "Check 07 GET http://localhost → 200 | GET http://${PUBLIC_IP:-<local>} → 200"
else
fail "Check 07 App accessible via HTTP 200" "localhost=${HTTP_LOCAL} public=${HTTP_PUBLIC}"
fi
# 08 — Built React frontend served (not dev server)
HTML_BODY=$(curl -s --connect-timeout 5 http://localhost 2>/dev/null || echo '')
if echo "$HTML_BODY" | grep -qi '/assets/index-'; then
pass "Check 08 Built React frontend is served at /"
else
fail "Check 08 Built React frontend is served at /" "Vite built bundle not detected — dev server or wrong path"
fi
# 09 — GET /api/users → 200 + JSON
API_STATUS=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 http://localhost/api/users 2>/dev/null || echo '000')
API_CT=$(curl -s -o /dev/null -w '%{content_type}' --connect-timeout 5 http://localhost/api/users 2>/dev/null || echo '')
if [ "$API_STATUS" = "200" ] && echo "$API_CT" | grep -qi 'application/json'; then
pass "Check 09 GET /api/users → HTTP 200 · Content-Type: application/json"
else
fail "Check 09 GET /api/users → HTTP 200 · Content-Type: application/json" \
"status=${API_STATUS} content-type=${API_CT:-<empty>}"
fi
# 10 — MongoDB /data/db on persistent volume
MONGO_CTR=$(mongo_container)
if [ -n "$MONGO_CTR" ]; then
MOUNT_TYPE=$(docker inspect "$MONGO_CTR" \
--format '{{range .Mounts}}{{if eq .Destination "/data/db"}}{{.Type}}{{end}}{{end}}' 2>/dev/null || echo '')
if [[ "$MOUNT_TYPE" == "volume" || "$MOUNT_TYPE" == "bind" ]]; then
pass "Check 10 MongoDB /data/db is on a persistent volume [type=${MOUNT_TYPE}]"
else
fail "Check 10 MongoDB /data/db is on a persistent volume" "no volume or bind mount on /data/db"
fi
else
fail "Check 10 MongoDB /data/db is on a persistent volume" "MongoDB container not found"
fi
# ── Section D — Hidden Checks ─────────────────────────────────────────────────
section "Section D — Hidden Checks (Checks 11–15)"
# 11 — POST /api/users creates a user
UNIQUE_TAG="grader_$(date +%s)"
POST_STATUS=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \
-X POST http://localhost/api/users \
-H 'Content-Type: application/json' \
-d "{\"name\":\"${UNIQUE_TAG}\",\"email\":\"${UNIQUE_TAG}@grader.test\"}" 2>/dev/null || echo '000')
if [[ "$POST_STATUS" =~ ^(200|201)$ ]]; then
pass "Check 11 Hidden check 1"
else
fail "Check 11 Hidden check 1"
fi
# 12 — Created user readable via GET
sleep 1
GET_BODY=$(curl -s --connect-timeout 5 http://localhost/api/users 2>/dev/null || echo '')
if echo "$GET_BODY" | grep -q "$UNIQUE_TAG"; then
pass "Check 12 Hidden check 2"
else
fail "Check 12 Hidden check 2"
fi
# 13 — MongoDB port 27017 NOT exposed on host
MONGO_CTR=$(mongo_container)
if [ -z "$MONGO_CTR" ]; then
fail "Check 13 Hidden check 3" "MongoDB container not running"
else
MONGO_EXPOSED=$(docker ps --format '{{.Ports}}' 2>/dev/null | grep -c '0\.0\.0\.0:27017' || true)
if [ "$MONGO_EXPOSED" -eq 0 ]; then
pass "Check 13 Hidden check 3"
else
fail "Check 13 Hidden check 3"
fi
fi
# 14 — SPA routing via Express wildcard
SPA_STATUS=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 http://localhost 2>/dev/null || echo '000')
SPA_BODY=$(curl -s --connect-timeout 5 http://localhost 2>/dev/null || echo '')
if [ "$SPA_STATUS" = "200" ] && echo "$SPA_BODY" | grep -qi 'id="root"'; then
pass "Check 14 Hidden check 4"
else
fail "Check 14 Hidden check 4"
fi
# 15 — App and MongoDB on shared Docker network
APP_CTR=$(app_container)
MONGO_CTR=$(mongo_container)
if [ -n "$APP_CTR" ] && [ -n "$MONGO_CTR" ]; then
APP_NETS=$(docker inspect "$APP_CTR" \
--format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null \
| tr ' ' '\n' | grep -v '^$' | sort)
MONGO_NETS=$(docker inspect "$MONGO_CTR" \
--format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null \
| tr ' ' '\n' | grep -v '^$' | sort)
SHARED=$(comm -12 <(echo "$APP_NETS") <(echo "$MONGO_NETS") | grep -v '^bridge$' | head -1)
if [ -n "$SHARED" ]; then
pass "Check 15 Hidden check 5"
else
fail "Check 15 Hidden check 5"
fi
else
fail "Check 15 Hidden check 5"
fi
# ── Integrity Check (ungraded) ────────────────────────────────────────────────
section "Integrity Check (ungraded)"
BASE_COMMIT="771940b4d15ae04a9999f0c4c01ca752df89e506"
LAB_START_UTC="2026-04-26 03:30:00"
REPO_PATH=""
for dir in /home/ubuntu/*/; do
if git -C "$dir" rev-parse HEAD &>/dev/null 2>&1; then
if git -C "$dir" cat-file -e "${BASE_COMMIT}^{commit}" 2>/dev/null; then
REPO_PATH="$dir"
break
fi
fi
done
if [ -z "$REPO_PATH" ]; then
warn "Repository not found in home directory — manual review required"
else
EARLY_COMMITS=$(git -C "$REPO_PATH" log \
${BASE_COMMIT}..HEAD \
--before="$LAB_START_UTC" \
--oneline 2>/dev/null | wc -l | tr -d ' ')
if [ "$EARLY_COMMITS" -gt 0 ]; then
warn "${EARLY_COMMITS} commit(s) found after base commit but before lab start — manual review required [${REPO_PATH}]"
else
echo -e " ${GREEN}✓ OK${RESET} Git integrity check passed [${REPO_PATH}]"
fi
fi
# ── Summary ───────────────────────────────────────────────────────────────────
SCORE=$((PASS_COUNT * 2))
echo ""
echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}║ RESULTS ║${RESET}"
echo -e "${BOLD}╠═══════════════════════════════════════════════════╣${RESET}"
printf "${BOLD}║${RESET} Checks passed : ${GREEN}%2s${RESET} / 15 ${BOLD}║${RESET}\n" "$PASS_COUNT"
printf "${BOLD}║${RESET} Checks failed : ${RED}%2s${RESET} / 15 ${BOLD}║${RESET}\n" "$FAIL_COUNT"
printf "${BOLD}║${RESET} Score : ${GREEN}%2s${RESET} / 30 marks ${BOLD}║${RESET}\n" "$SCORE"
echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${RESET}"
echo ""