11#! /usr/bin/env bash
22# test-lab-18-02.sh — Lab 18-02: External Dependencies
3- # Module 18: Traefik reverse proxy and load balancer
4- # traefik with external PostgreSQL, Redis, and network integration
3+ # Module 18: Traefik — TLS + Middleware Chains
54set -euo pipefail
65
76LAB_ID=" 18-02"
8- LAB_NAME=" External Dependencies"
9- MODULE=" traefik"
7+ LAB_NAME=" TLS + Middleware Chains"
108COMPOSE_FILE=" docker/docker-compose.lan.yml"
11- PASS=0
12- FAIL=0
9+ TRAEFIK_HTTP=" ${TRAEFIK_HTTP:- http:// localhost: 80} "
10+ TRAEFIK_HTTPS=" ${TRAEFIK_HTTPS:- https:// localhost: 443} "
11+ TRAEFIK_DASH=" ${TRAEFIK_DASH:- http:// localhost: 8080} "
12+ PASS=0; FAIL=0
1313
14- # ── Colors ────────────────────────────────────────────────────────────────────
1514RED=' \033[0;31m' ; GREEN=' \033[0;32m' ; YELLOW=' \033[1;33m'
16- CYAN=' \033[0;36m' ; NC=' \033[0m'
15+ CYAN=' \033[0;36m' ; BOLD= ' \033[1m ' ; NC=' \033[0m'
1716
18- pass () { echo -e " ${GREEN} [PASS]${NC} $1 " ; (( PASS++ )) ; }
19- fail () { echo -e " ${RED} [FAIL]${NC} $1 " ; (( FAIL++ )) ; }
20- info () { echo -e " ${CYAN} [INFO]${NC} $1 " ; }
21- warn () { echo -e " ${YELLOW} [WARN]${NC} $1 " ; }
17+ pass () { echo -e " ${GREEN} [PASS]${NC} $1 " ; (( ++ PASS)) ; }
18+ fail () { echo -e " ${RED} [FAIL]${NC} $1 " ; (( ++ FAIL)) ; }
19+ info () { echo -e " ${CYAN} [INFO]${NC} $1 " ; }
20+ warn () { echo -e " ${YELLOW} [WARN]${NC} $1 " ; }
21+ header () { echo -e " \n${BOLD}${CYAN} ━━━ $1 ━━━${NC} " ; }
2222
23- echo -e " ${CYAN} ======================================${NC} "
24- echo -e " ${CYAN} Lab ${LAB_ID} : ${LAB_NAME}${NC} "
25- echo -e " ${CYAN} Module: ${MODULE}${NC} "
26- echo -e " ${CYAN} ======================================${NC} "
27- echo " "
23+ echo -e " \n${BOLD} IT-Stack Lab ${LAB_ID} — ${LAB_NAME}${NC} "
24+ echo -e " Module 18: Traefik | $( date ' +%Y-%m-%d %H:%M:%S' ) \n"
25+
26+ header " Phase 1: Setup"
27+ docker compose -f " ${COMPOSE_FILE} " pull --quiet 2> /dev/null || true
28+ docker compose -f " ${COMPOSE_FILE} " up -d --remove-orphans
29+ info " Waiting for Traefik /ping..."
30+ timeout 60 bash -c " until curl -sf ${TRAEFIK_HTTP} /ping > /dev/null 2>&1; do sleep 3; done"
31+ pass " Traefik ready"
32+
33+ header " Phase 2: Core Health"
34+ PING=$( curl -sf --max-time 5 " ${TRAEFIK_HTTP} /ping" 2> /dev/null || echo " " )
35+ if [ " ${PING} " = " OK" ]; then
36+ pass " /ping returns 'OK'"
37+ else
38+ fail " /ping returned '${PING} ' — expected 'OK'"
39+ fi
40+
41+ VER=$( curl -sf --max-time 5 " ${TRAEFIK_DASH} /api/version" 2> /dev/null | grep -o ' "Version":"[^"]*"' | head -1 || echo " " )
42+ if echo " ${VER} " | grep -q " Version" ; then
43+ pass " Dashboard API /version: ${VER} "
44+ else
45+ fail " Dashboard API /version not available: ${VER} "
46+ fi
47+
48+ header " Phase 3: HTTP → HTTPS Redirect"
49+ REDIR=$( curl -so /dev/null -w " %{http_code}" --max-time 5 \
50+ -H " Host: app-a.lab.localhost" " ${TRAEFIK_HTTP} /" 2> /dev/null || echo " 000" )
51+ if [ " ${REDIR} " = " 301" ] || [ " ${REDIR} " = " 302" ] || [ " ${REDIR} " = " 308" ]; then
52+ pass " HTTP → HTTPS redirect: HTTP ${REDIR} "
53+ else
54+ warn " HTTP redirect returned ${REDIR} (may be config-dependent)"
55+ fi
2856
29- # ── PHASE 1: Setup ────────────────────────────────────────────────────────────
30- info " Phase 1: Setup"
31- docker compose -f " ${COMPOSE_FILE} " up -d
32- info " Waiting 30s for ${MODULE} to initialize..."
33- sleep 30
57+ header " Phase 4: HTTPS Backends (-k ignores staging cert)"
58+ for host in " app-a.lab.localhost" " app-b.lab.localhost" ; do
59+ CODE=$( curl -sk --max-time 5 -o /dev/null -w " %{http_code}" \
60+ -H " Host: ${host} " " ${TRAEFIK_HTTPS} /" 2> /dev/null || echo " 000" )
61+ if [ " ${CODE} " = " 200" ]; then
62+ pass " HTTPS ${host} → HTTP ${CODE} "
63+ else
64+ warn " HTTPS ${host} returned ${CODE} (cert challenge may need public IP/DNS)"
65+ fi
66+ done
3467
35- # ── PHASE 2: Health Checks ────────────────────────────────────────────────────
36- info " Phase 2: Health Checks"
68+ header " Phase 5: Path-Prefix Routing"
69+ CODE=$( curl -sk --max-time 5 -o /dev/null -w " %{http_code}" \
70+ " ${TRAEFIK_HTTPS} /api/v1/echo" 2> /dev/null || echo " 000" )
71+ if [ " ${CODE} " = " 200" ] || [ " ${CODE} " = " 404" ]; then
72+ pass " Path /api/v1/echo routed (HTTP ${CODE} )"
73+ else
74+ warn " Path routing returned ${CODE} "
75+ fi
3776
38- if docker compose -f " ${COMPOSE_FILE} " ps | grep -q " running\|Up" ; then
39- pass " Container is running"
77+ header " Phase 6: API — Routers & Services Registered"
78+ ROUTERS=$( curl -sf --max-time 5 " ${TRAEFIK_DASH} /api/http/routers" 2> /dev/null || echo " []" )
79+ ROUTER_COUNT=$( echo " ${ROUTERS} " | grep -o ' "name"' | wc -l | tr -d ' ' )
80+ if [ " ${ROUTER_COUNT} " -ge 3 ] 2> /dev/null; then
81+ pass " API reports ${ROUTER_COUNT} HTTP router(s) registered"
4082else
41- fail " Container is not running "
83+ warn " Only ${ROUTER_COUNT} routers registered — backends may still be starting "
4284fi
4385
44- # ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
45- info " Phase 3: Functional Tests (Lab 02 — External Dependencies)"
86+ SERVICES=$( curl -sf --max-time 5 " ${TRAEFIK_DASH} /api/http/services" 2> /dev/null || echo " []" )
87+ SVC_COUNT=$( echo " ${SERVICES} " | grep -o ' "name"' | wc -l | tr -d ' ' )
88+ if [ " ${SVC_COUNT} " -ge 3 ] 2> /dev/null; then
89+ pass " API reports ${SVC_COUNT} HTTP service(s) registered"
90+ else
91+ warn " Only ${SVC_COUNT} services registered"
92+ fi
4693
47- # TODO: Add module-specific functional tests here
48- # Example:
49- # if curl -sf http://localhost:80/health > /dev/null 2>&1 ; then
50- # pass "Health endpoint responds "
51- # else
52- # fail "Health endpoint not reachable "
53- # fi
94+ header " Phase 7: Security Headers Middleware "
95+ HDR= $( curl -skI --max-time 5 -H " Host: app-a.lab.localhost " " ${TRAEFIK_HTTPS} / " 2> /dev/null || echo " " )
96+ if echo " ${HDR} " | grep -qi " x-frame-options\|strict-transport-security\|x-content-type-options " ; then
97+ pass " Security headers present in app-a response "
98+ else
99+ warn " Security headers not detected (middleware may require HTTPS + valid cert) "
100+ fi
54101
55- warn " Functional tests for Lab 18-02 pending implementation"
102+ header " Phase 8: Load Balancing"
103+ HOSTS=()
104+ for _ in 1 2 3 4; do
105+ H=$( curl -sk --max-time 5 -H " Host: lb.lab.localhost" " ${TRAEFIK_HTTPS} /" 2> /dev/null \
106+ | grep -i " Hostname:" | awk ' {print $2}' || true)
107+ HOSTS+=(" ${H} " )
108+ done
109+ UNIQUE=$( printf ' %s\n' " ${HOSTS[@]} " | sort -u | grep -c . || echo " 0" )
110+ if [ " ${UNIQUE} " -ge 2 ] 2> /dev/null; then
111+ pass " Load balanced across ${UNIQUE} unique backends"
112+ else
113+ warn " Got ${UNIQUE} unique backend(s) (LB replicas may use same hostname)"
114+ fi
56115
57- # ── PHASE 4: Cleanup ──────────────────────────────────────────────────────────
58- info " Phase 4: Cleanup"
116+ header " Phase 9: Cleanup"
59117docker compose -f " ${COMPOSE_FILE} " down -v --remove-orphans
60- info " Cleanup complete "
118+ pass " Stack stopped and volumes removed "
61119
62- # ── Results ───────────────────────────────────────────────────────────────────
63120echo " "
64- echo -e " ${CYAN} ====================================== ${NC} "
65- echo -e " Lab ${LAB_ID} Complete "
66- echo -e " ${GREEN} PASS: ${PASS}${ NC} | ${RED} FAIL: ${FAIL}${NC }"
67- echo -e " ${CYAN} ====================================== ${NC} "
68-
121+ echo -e " ${BOLD} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ${NC} "
122+ echo -e " ${BOLD} Lab ${LAB_ID} Results ${NC} "
123+ echo -e " ${GREEN} Passed: ${ NC} ${PASS }"
124+ echo -e " ${RED} Failed: ${NC} ${FAIL }"
125+ echo -e " ${BOLD} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ${NC} "
69126if [ " ${FAIL} " -gt 0 ]; then
70- exit 1
127+ echo -e " ${RED} FAIL ${NC} — ${FAIL} test(s) failed " ; exit 1
71128fi
129+ echo -e " ${GREEN} PASS${NC} — All ${PASS} tests passed"
0 commit comments