Skip to content

Commit 977e381

Browse files
committed
feat(labs): implement Lab 15-02 external dependencies (PostgreSQL + Redis + Mailhog)
1 parent 31232ab commit 977e381

3 files changed

Lines changed: 289 additions & 44 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,42 @@ run: bash tests/labs/test-lab-15-01.sh
111111
- name: Cleanup
112112
if: always()
113113
run: docker compose -f docker/docker-compose.standalone.yml down -v
114+
lab-02-smoke:
115+
name: Lab 02 -- Taiga External Dependencies (PostgreSQL + Redis + Mailhog)
116+
runs-on: ubuntu-latest
117+
needs: validate
118+
continue-on-error: true
119+
steps:
120+
- uses: actions/checkout@v4
121+
122+
- name: Install tools
123+
run: sudo apt-get install -y curl postgresql-client -qq
124+
125+
- name: Validate LAN compose
126+
run: docker compose -f docker/docker-compose.lan.yml config -q && echo "LAN compose valid"
127+
128+
- name: Start LAN stack
129+
run: docker compose -f docker/docker-compose.lan.yml up -d
130+
131+
- name: Wait for PostgreSQL
132+
run: timeout 120 bash -c 'until docker exec taiga-l02-db pg_isready -U taiga -d taiga; do sleep 5; done'
133+
134+
- name: Wait for Redis
135+
run: timeout 60 bash -c 'until docker exec taiga-l02-redis redis-cli ping | grep -q PONG; do sleep 5; done'
136+
137+
- name: Wait for Mailhog
138+
run: timeout 60 bash -c 'until curl -sf http://localhost:8710/api/v2/messages; do sleep 5; done'
139+
140+
- name: Wait for Taiga backend
141+
run: timeout 180 bash -c 'until curl -o /dev/null -sw "%{http_code}" http://localhost:8011/api/v1/ | grep -q "^[234]"; do sleep 10; done'
142+
143+
- name: Run Lab 15-02 test script
144+
run: bash tests/labs/test-lab-15-02.sh --no-cleanup
145+
146+
- name: Collect logs on failure
147+
if: failure()
148+
run: docker compose -f docker/docker-compose.lan.yml logs
149+
150+
- name: Cleanup
151+
if: always()
152+
run: docker compose -f docker/docker-compose.lan.yml down -v

docker/docker-compose.lan.yml

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,127 @@
1-
# Lab 02 — External Dependencies: taiga with external PostgreSQL and Redis
1+
# Lab 02 — External Dependencies: Taiga with external PostgreSQL, Redis, and Mailhog
2+
# Models the LAN tier: DB/cache on data-net, app on app-net.
3+
# Taiga UI: http://localhost:8410
4+
# Taiga API: http://localhost:8011
5+
# Mailhog: http://localhost:8710
26
---
7+
name: it-stack-taiga-lab02
8+
39
services:
4-
taiga:
5-
image: taigaio/taiga-front:latest
6-
container_name: it-stack-taiga
10+
11+
# ── External Database (simulates lab-db1) ─────────────────────────────────
12+
taiga-l02-db:
13+
image: postgres:15-alpine
14+
container_name: taiga-l02-db
715
restart: unless-stopped
8-
ports:
9-
- "80:$firstPort"
1016
environment:
11-
- IT_STACK_ENV=lab-02-lan
12-
- DB_HOST=
13-
- DB_PORT=5432
14-
- REDIS_HOST=
17+
POSTGRES_DB: taiga
18+
POSTGRES_USER: taiga
19+
POSTGRES_PASSWORD: TaigaLab02!
20+
volumes:
21+
- taiga-l02-db-data:/var/lib/postgresql/data
22+
healthcheck:
23+
test: ["CMD-SHELL", "pg_isready -U taiga -d taiga"]
24+
interval: 10s
25+
timeout: 5s
26+
retries: 8
27+
start_period: 30s
1528
networks:
16-
- it-stack-net
29+
- taiga-l02-data-net
1730

18-
# Lightweight local DB for lab (replace with lab-db1 in real env)
19-
postgres:
20-
image: postgres:16
21-
container_name: it-stack-taiga-db
31+
# ── External Redis (simulates shared cache tier) ───────────────────────────
32+
taiga-l02-redis:
33+
image: redis:7-alpine
34+
container_name: taiga-l02-redis
35+
restart: unless-stopped
36+
healthcheck:
37+
test: ["CMD", "redis-cli", "ping"]
38+
interval: 10s
39+
timeout: 5s
40+
retries: 5
41+
networks:
42+
- taiga-l02-data-net
43+
44+
# ── External SMTP relay (Mailhog) ─────────────────────────────────────────
45+
taiga-l02-mail:
46+
image: mailhog/mailhog:latest
47+
container_name: taiga-l02-mail
48+
restart: unless-stopped
49+
ports:
50+
- "8710:8025"
51+
networks:
52+
- taiga-l02-app-net
53+
54+
# ── Taiga Backend (Django API) ────────────────────────────────────────────
55+
taiga-l02-back:
56+
image: taigaio/taiga-back:latest
57+
container_name: taiga-l02-back
58+
restart: unless-stopped
59+
depends_on:
60+
taiga-l02-db:
61+
condition: service_healthy
62+
taiga-l02-redis:
63+
condition: service_healthy
64+
taiga-l02-mail:
65+
condition: service_started
2266
environment:
23-
POSTGRES_DB: taiga_db
24-
POSTGRES_USER: taiga_user
25-
POSTGRES_PASSWORD: taiga_pass
67+
POSTGRES_HOST: taiga-l02-db
68+
POSTGRES_DB: taiga
69+
POSTGRES_USER: taiga
70+
POSTGRES_PASSWORD: TaigaLab02!
71+
TAIGA_SECRET_KEY: "lab02-lan-secret-key-for-testing-only-change-in-prod"
72+
TAIGA_SITES_DOMAIN: "localhost:8011"
73+
TAIGA_SITES_SCHEME: "http"
74+
TAIGA_REDIS_URL: "redis://taiga-l02-redis:6379/0"
75+
DEFAULT_FROM_EMAIL: "taiga@lab02.local"
76+
EMAIL_BACKEND: "django.core.mail.backends.smtp.EmailBackend"
77+
EMAIL_USE_TLS: "False"
78+
EMAIL_USE_SSL: "False"
79+
EMAIL_HOST: taiga-l02-mail
80+
EMAIL_PORT: "1025"
81+
SESSION_COOKIE_SECURE: "False"
82+
CSRF_COOKIE_SECURE: "False"
83+
ENABLE_TELEMETRY: "False"
84+
ports:
85+
- "8011:8000"
2686
volumes:
27-
- taiga_pg_data:/var/lib/postgresql/data
87+
- taiga-l02-static:/taiga-back/static
88+
- taiga-l02-media:/taiga-back/media
89+
healthcheck:
90+
test: ["CMD-SHELL", "curl -sf http://localhost:8000/api/v1/ | grep -q 'projects\\|auth\\|404' || curl -o /dev/null -sw '%{http_code}' http://localhost:8000/api/v1/ | grep -q '^[234]'"]
91+
interval: 30s
92+
timeout: 15s
93+
retries: 8
94+
start_period: 90s
95+
networks:
96+
- taiga-l02-data-net
97+
- taiga-l02-app-net
98+
99+
# ── Taiga Frontend (Nginx) ────────────────────────────────────────────────
100+
taiga-l02-front:
101+
image: taigaio/taiga-front:latest
102+
container_name: taiga-l02-front
103+
restart: unless-stopped
104+
environment:
105+
TAIGA_URL: "http://localhost:8011"
106+
TAIGA_WEBSOCKETS_URL: "ws://localhost:8011"
107+
ports:
108+
- "8410:80"
109+
healthcheck:
110+
test: ["CMD-SHELL", "curl -sf http://localhost/ | grep -qi 'taiga\\|doctype' || exit 1"]
111+
interval: 30s
112+
timeout: 10s
113+
retries: 5
114+
start_period: 60s
28115
networks:
29-
- it-stack-net
116+
- taiga-l02-app-net
30117

31118
networks:
32-
it-stack-net:
119+
taiga-l02-data-net:
120+
driver: bridge
121+
taiga-l02-app-net:
33122
driver: bridge
34123

35124
volumes:
36-
taiga_pg_data:
125+
taiga-l02-db-data:
126+
taiga-l02-static:
127+
taiga-l02-media:

tests/labs/test-lab-15-02.sh

Lines changed: 137 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,153 @@ echo -e "${CYAN} Module: ${MODULE}${NC}"
2626
echo -e "${CYAN}======================================${NC}"
2727
echo ""
2828

29+
# ── Cleanup control ───────────────────────────────────────────────────────────
30+
CLEANUP=true
31+
[[ "${1:-}" == "--no-cleanup" ]] && CLEANUP=false
32+
33+
cleanup() {
34+
if [[ "${CLEANUP}" == "true" ]]; then
35+
info "Phase 4: Cleanup"
36+
docker compose -f "${COMPOSE_FILE}" down -v --remove-orphans 2>/dev/null || true
37+
info "Cleanup complete"
38+
else
39+
info "Skipping cleanup (--no-cleanup)"
40+
fi
41+
}
42+
trap cleanup EXIT
43+
2944
# ── PHASE 1: Setup ────────────────────────────────────────────────────────────
3045
info "Phase 1: Setup"
3146
docker compose -f "${COMPOSE_FILE}" up -d
32-
info "Waiting 30s for ${MODULE} to initialize..."
33-
sleep 30
3447

3548
# ── PHASE 2: Health Checks ────────────────────────────────────────────────────
3649
info "Phase 2: Health Checks"
3750

38-
if docker compose -f "${COMPOSE_FILE}" ps | grep -q "running\|Up"; then
39-
pass "Container is running"
51+
info "Waiting for external PostgreSQL (taiga-l02-db, up to 90s)..."
52+
for i in $(seq 1 18); do
53+
if docker exec taiga-l02-db pg_isready -U taiga -d taiga >/dev/null 2>&1; then
54+
pass "External PostgreSQL healthy"
55+
break
56+
fi
57+
[[ $i -eq 18 ]] && fail "External PostgreSQL timed out after 90s"
58+
sleep 5
59+
done
60+
61+
info "Waiting for external Redis (taiga-l02-redis, up to 60s)..."
62+
for i in $(seq 1 12); do
63+
if docker exec taiga-l02-redis redis-cli ping 2>/dev/null | grep -q 'PONG'; then
64+
pass "External Redis healthy"
65+
break
66+
fi
67+
[[ $i -eq 12 ]] && fail "External Redis timed out after 60s"
68+
sleep 5
69+
done
70+
71+
info "Waiting for Mailhog (taiga-l02-mail, up to 60s)..."
72+
for i in $(seq 1 12); do
73+
if curl -sf http://localhost:8710/api/v2/messages >/dev/null 2>&1; then
74+
pass "Mailhog API reachable"
75+
break
76+
fi
77+
[[ $i -eq 12 ]] && fail "Mailhog timed out after 60s"
78+
sleep 5
79+
done
80+
81+
info "Waiting for Taiga backend API (taiga-l02-back, up to 180s)..."
82+
for i in $(seq 1 36); do
83+
http_code=$(curl -o /dev/null -sw '%{http_code}' http://localhost:8011/api/v1/ 2>/dev/null || echo "000")
84+
if [[ "${http_code}" =~ ^[234] ]]; then
85+
pass "Taiga backend API responding (HTTP ${http_code})"
86+
break
87+
fi
88+
[[ $i -eq 36 ]] && fail "Taiga backend timed out after 180s"
89+
sleep 5
90+
done
91+
92+
info "Waiting for Taiga frontend (taiga-l02-front, up to 120s)..."
93+
for i in $(seq 1 24); do
94+
if curl -sf http://localhost:8410/ 2>/dev/null | grep -qi 'taiga\|doctype\|html'; then
95+
pass "Taiga frontend serving HTML"
96+
break
97+
fi
98+
[[ $i -eq 24 ]] && fail "Taiga frontend timed out after 120s"
99+
sleep 5
100+
done
101+
102+
# ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
103+
info "Phase 3: Functional Tests (Lab 15-02 — External Dependencies)"
104+
105+
# Container states
106+
for svc in taiga-l02-db taiga-l02-redis taiga-l02-mail taiga-l02-back taiga-l02-front; do
107+
state=$(docker inspect --format='{{.State.Status}}' "${svc}" 2>/dev/null || echo "missing")
108+
if [[ "${state}" == "running" ]]; then
109+
pass "Container ${svc} is running"
110+
else
111+
fail "Container ${svc} state: ${state}"
112+
fi
113+
done
114+
115+
# DB connectivity from back container
116+
if docker exec taiga-l02-back bash -c 'python -c "import psycopg2; psycopg2.connect(host=\"taiga-l02-db\",dbname=\"taiga\",user=\"taiga\",password=\"TaigaLab02!\"); print(\"ok\")"' 2>/dev/null | grep -q 'ok'; then
117+
pass "Backend can connect to external PostgreSQL"
40118
else
41-
fail "Container is not running"
119+
warn "psycopg2 direct test skipped (may not be installed separately); checking via pg_isready"
120+
if docker exec taiga-l02-db psql -U taiga -d taiga -c '\dt' 2>/dev/null | grep -qi 'row\|table\|schema'; then
121+
pass "PostgreSQL taiga database has schema tables"
122+
else
123+
warn "PostgreSQL schema check inconclusive (migrations may not have run yet)"
124+
fi
42125
fi
43126

44-
# ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
45-
info "Phase 3: Functional Tests (Lab 02 — External Dependencies)"
46-
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
54-
55-
warn "Functional tests for Lab 15-02 pending implementation"
56-
57-
# ── PHASE 4: Cleanup ──────────────────────────────────────────────────────────
58-
info "Phase 4: Cleanup"
59-
docker compose -f "${COMPOSE_FILE}" down -v --remove-orphans
60-
info "Cleanup complete"
127+
# Redis connectivity from back container
128+
if docker exec taiga-l02-back bash -c 'python -c "import redis; r=redis.Redis(host=\"taiga-l02-redis\"); r.ping(); print(\"ok\")"' 2>/dev/null | grep -q 'ok'; then
129+
pass "Backend can connect to external Redis"
130+
else
131+
# Fallback: redis-cli from Redis container itself
132+
if docker exec taiga-l02-redis redis-cli ping 2>/dev/null | grep -q 'PONG'; then
133+
pass "External Redis is healthy (redis-cli ping)"
134+
else
135+
fail "External Redis not responding"
136+
fi
137+
fi
138+
139+
# Mailhog API format check
140+
mailhog_resp=$(curl -sf http://localhost:8710/api/v2/messages 2>/dev/null || echo "{}")
141+
if echo "${mailhog_resp}" | grep -q 'total\|items\|count'; then
142+
pass "Mailhog API returns valid JSON message list"
143+
else
144+
fail "Mailhog API response unexpected: ${mailhog_resp}"
145+
fi
146+
147+
# HTTP status checks
148+
for url_port in "8011/api/v1/" "8410/"; do
149+
port=$(echo "${url_port}" | cut -d/ -f1)
150+
path=$(echo "${url_port}" | cut -d/ -f2-)
151+
http_code=$(curl -o /dev/null -sw '%{http_code}' "http://localhost:${url_port}" 2>/dev/null || echo "000")
152+
if [[ "${http_code}" =~ ^[234] ]]; then
153+
pass "HTTP GET http://localhost:${url_port} -> ${http_code}"
154+
else
155+
fail "HTTP GET http://localhost:${url_port} -> ${http_code}"
156+
fi
157+
done
158+
159+
# Key env vars present in back container
160+
for var in POSTGRES_HOST TAIGA_SECRET_KEY TAIGA_REDIS_URL EMAIL_HOST; do
161+
if docker exec taiga-l02-back printenv "${var}" 2>/dev/null | grep -q '.'; then
162+
pass "Env var ${var} set in taiga-l02-back"
163+
else
164+
fail "Env var ${var} missing in taiga-l02-back"
165+
fi
166+
done
167+
168+
# Volume existence
169+
for vol in taiga-l02-db-data taiga-l02-static taiga-l02-media; do
170+
if docker volume ls --format '{{.Name}}' | grep -q "${vol}"; then
171+
pass "Volume ${vol} exists"
172+
else
173+
fail "Volume ${vol} missing"
174+
fi
175+
done
61176

62177
# ── Results ───────────────────────────────────────────────────────────────────
63178
echo ""

0 commit comments

Comments
 (0)