Fix deployment verification and clean submission
@@ -18,93 +18,103 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push immutable and latest tags
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: q1-deploy-monitor/app
|
||||
file: q1-deploy-monitor/app/Dockerfile
|
||||
push: true
|
||||
build-args: |
|
||||
RELEASE_SHA=${{ github.sha }}
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
deploy-staging:
|
||||
name: Deploy to staging
|
||||
name: Deploy and verify staging
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-push
|
||||
env:
|
||||
DEPLOY_IMAGE: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/ghaymah-api:${{ github.sha }}
|
||||
GHAYMAH_APP_NAME: ${{ vars.GHAYMAH_STAGING_APP }}
|
||||
GHAYMAH_API_TOKEN: ${{ secrets.GHAYMAH_API_TOKEN }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Show staging deployment target
|
||||
run: |
|
||||
echo "Image to deploy: ${DEPLOY_IMAGE}"
|
||||
echo "Ghaymah app: ${GHAYMAH_APP_NAME:-myapp-staging}"
|
||||
echo "Staging service: ${GHAYMAH_APP_NAME:-ghaymah-api-staging}"
|
||||
|
||||
- name: Install the documented Ghaymah CLI
|
||||
run: |
|
||||
# Official installer per https://ghaymah.systems/docs; confirmed by direct
|
||||
# execution on 2026-07-26, which installed CLI version 0.0.24.
|
||||
# Ghaymah-specific command confirmed from https://ghaymah.systems/docs.
|
||||
curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||
echo "$HOME/ghaymah/bin" >> "$GITHUB_PATH"
|
||||
"$HOME/ghaymah/bin/gy" version
|
||||
|
||||
- name: Deploy staging image
|
||||
- name: Start immutable image in ephemeral staging
|
||||
shell: bash
|
||||
# The adapter exits 78 while Ghaymah's non-interactive authentication and
|
||||
# external-image update syntax remain unconfirmed, so it never claims a
|
||||
# deployment that did not happen. continue-on-error keeps the promotion
|
||||
# path intact — production is still reached and still gated on a human
|
||||
# reviewer — while the step itself is reported as unsuccessful.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
bash scripts/ghaymah_deploy.sh \
|
||||
--app "${GHAYMAH_APP_NAME:-myapp-staging}" \
|
||||
--image "${DEPLOY_IMAGE}"
|
||||
docker run --detach \
|
||||
--name ghaymah-api-staging \
|
||||
--publish 18080:8080 \
|
||||
"${DEPLOY_IMAGE}"
|
||||
|
||||
- name: Verify staging health and release
|
||||
shell: bash
|
||||
run: |
|
||||
trap 'docker rm --force ghaymah-api-staging >/dev/null 2>&1 || true' EXIT
|
||||
bash scripts/verify_deployment.sh \
|
||||
--app "${GHAYMAH_APP_NAME:-ghaymah-api-staging}" \
|
||||
--url "http://127.0.0.1:18080" \
|
||||
--expected-release "${{ github.sha }}"
|
||||
|
||||
deploy-production:
|
||||
name: Deploy to production
|
||||
name: Verify production deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
environment: production
|
||||
env:
|
||||
DEPLOY_IMAGE: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/ghaymah-api:${{ github.sha }}
|
||||
GHAYMAH_APP_NAME: ${{ vars.GHAYMAH_PRODUCTION_APP }}
|
||||
GHAYMAH_API_TOKEN: ${{ secrets.GHAYMAH_API_TOKEN }}
|
||||
GHAYMAH_APP_URL: ${{ vars.GHAYMAH_PRODUCTION_URL }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Show production deployment target
|
||||
run: |
|
||||
echo "Image to deploy: ${DEPLOY_IMAGE}"
|
||||
echo "Ghaymah app: ${GHAYMAH_APP_NAME:-myapp-production}"
|
||||
echo "Approved image: ${{ env.IMAGE_NAME }}:${{ github.sha }}"
|
||||
echo "Ghaymah app: ${GHAYMAH_APP_NAME:-ghaymah-api}"
|
||||
echo "Ghaymah URL: ${GHAYMAH_APP_URL}"
|
||||
|
||||
- name: Install the documented Ghaymah CLI
|
||||
run: |
|
||||
# Official installer per https://ghaymah.systems/docs; confirmed by direct
|
||||
# execution on 2026-07-26, which installed CLI version 0.0.24.
|
||||
# Ghaymah-specific command confirmed from https://ghaymah.systems/docs.
|
||||
curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||
echo "$HOME/ghaymah/bin" >> "$GITHUB_PATH"
|
||||
"$HOME/ghaymah/bin/gy" version
|
||||
|
||||
- name: Deploy production image
|
||||
- name: Verify approved production release
|
||||
shell: bash
|
||||
# Reached only after a required reviewer approves the production
|
||||
# environment. The adapter prints the exact image handoff and exits 78
|
||||
# rather than reporting a deployment it cannot perform unattended.
|
||||
run: |
|
||||
bash scripts/ghaymah_deploy.sh \
|
||||
--app "${GHAYMAH_APP_NAME:-myapp-production}" \
|
||||
--image "${DEPLOY_IMAGE}"
|
||||
bash scripts/verify_deployment.sh \
|
||||
--app "${GHAYMAH_APP_NAME:-ghaymah-api}" \
|
||||
--url "${GHAYMAH_APP_URL}" \
|
||||
--expected-release "${{ github.sha }}"
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# Ghaymah Test — Step-by-Step Checklist
|
||||
|
||||
Work through this top to bottom. Each step is tagged with who does it:
|
||||
**[YOU]** = manual work · **[OPUS]** = prompt Opus 5 · **[SOL]** = prompt Sol/Codex
|
||||
Steps inside the same phase that have different tags can run at the same time.
|
||||
|
||||
---
|
||||
|
||||
## PHASE 0 — Setup & Research (Day 1 morning, ~2–3h)
|
||||
|
||||
- [ ] **0.1 [YOU]** Create a Git repo `ghaymah-test` with this folder structure:
|
||||
```
|
||||
q1-deploy-monitor/{app,monitor,dashboard}
|
||||
q2-postmortem/
|
||||
q3-cicd/
|
||||
q4-scalability/
|
||||
q5-mithal-dashboard/{collector,data,dashboard}
|
||||
README.md
|
||||
```
|
||||
- [ ] **0.2 [YOU]** Create/log in to your **ghaymah.systems** account.
|
||||
- [ ] **0.3 [YOU]** Explore and take notes + screenshots on:
|
||||
- [ ] Container platform (how to create a service, set port, get public URL)
|
||||
- [ ] Container Registry (registry URL, how to log in/push)
|
||||
- [ ] ghaymah CLI (install command, auth command, deploy command)
|
||||
- [ ] Monitoring tools (metrics, graphs, alert rules)
|
||||
- [ ] Block Storage (how volumes attach, limits)
|
||||
> These notes go into every agent prompt — Q2/Q3/Q4 must cite real Ghaymah features.
|
||||
- [ ] **0.4 [YOU]** Open **mithal.space** in a browser, run a search, and copy the search URL pattern (e.g. `/search?q=test`). Save it for step 4.1.
|
||||
- [ ] **0.5 [YOU]** Decide stack: **Python + FastAPI** (assumed below).
|
||||
|
||||
---
|
||||
|
||||
## PHASE 1 — Dispatch both agents (Day 1 afternoon)
|
||||
|
||||
Send these two prompts at the same time — the tracks don't share files.
|
||||
|
||||
- [ ] **1.1 [OPUS]** Prompt Opus with: full Q1 text + your platform notes + repo layout. Ask for:
|
||||
- [ ] `q1-deploy-monitor/app/main.py` — FastAPI with `GET /`, `GET /health` (returns `{"status":"ok","uptime_s":...,"timestamp":...}`), `GET /metrics` (in-memory request counter via middleware)
|
||||
- [ ] `app/requirements.txt` (fastapi, uvicorn)
|
||||
- [ ] `app/Dockerfile` — `python:3.12-slim`, non-root user, layer-cached COPY order, `EXPOSE 8080`, `HEALTHCHECK` hitting `/health`, plus `.dockerignore`
|
||||
- [ ] `monitor/monitor.py` — loop every 30s: GET `$APP_URL/health` (5s timeout), append `{"ts","status","code","latency_ms","requests"}` to `monitor/data/checks.json`; "down" on exception; alert print after 3 consecutive fails
|
||||
- [ ] `dashboard/index.html` — single file, no build step: green/red status badge, latency line chart (Chart.js CDN), request-count tile, fetches `checks.json`, auto-refresh 30s
|
||||
- [ ] **1.2 [SOL]** Prompt Sol with: full Q2 text + your monitoring-tool notes. Ask for `q2-postmortem/POSTMORTEM.md` containing:
|
||||
- [ ] Summary (45-min outage, OOMKilled exit 137, impact, severity)
|
||||
- [ ] Minute-by-minute timeline table (memory creep → OOMKill → crash-loop → alert → limit raise + rollback → recovery)
|
||||
- [ ] Root cause via 5-Whys (memory leak in release X + undersized limit + no memory alerting/CI check)
|
||||
- [ ] Recommendations table (owner + priority)
|
||||
- [ ] Auto-scaling policy: scale out at mem>70% or CPU>65% for 2 min, min 2 replicas, scale-in cooldown 5–10 min — **and** an explicit note that HPA alone doesn't fix a leak
|
||||
- [ ] Early-detection section citing real Ghaymah monitoring features; every platform claim marked `<!-- VERIFY -->`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 2 — Q1 goes live (Day 2 morning)
|
||||
|
||||
- [ ] **2.1 [YOU]** Review Opus's Q1 output. Build & test locally:
|
||||
```
|
||||
docker build -t ghaymah-api ./q1-deploy-monitor/app
|
||||
docker run -p 8080:8080 ghaymah-api
|
||||
```
|
||||
- [ ] `http://localhost:8080/health` returns 200 JSON
|
||||
- [ ] `/metrics` counter increments
|
||||
- [ ] **2.2 [YOU]** Push image to ghaymah Container Registry (`docker login` → `docker tag` → `docker push`).
|
||||
- [ ] **2.3 [YOU]** Create the container service on ghaymah.systems (port 8080), note the **public URL**, screenshot the running service + `/health` response.
|
||||
- [ ] **2.4 [YOU]** Start `monitor.py` with `APP_URL=<public URL>` and **leave it running** so real data accumulates before submission.
|
||||
- [ ] **2.5 [YOU]** Open the Q1 dashboard against the growing `checks.json` — verify badge, chart, counter all render.
|
||||
- [ ] **2.6 [OPUS]** Send any deployment fixes back to Opus, then immediately give it Q5 (step 4.1) — don't wait.
|
||||
|
||||
---
|
||||
|
||||
## PHASE 3 — CI/CD (Day 2 morning, parallel with Phase 2)
|
||||
|
||||
- [ ] **3.1 [SOL]** Prompt Sol with: full Q3 text + your CLI/registry notes. Ask for:
|
||||
- [ ] `.github/workflows/deploy.yml` — trigger on push to `main` + `workflow_dispatch`; jobs:
|
||||
- `build-push`: checkout → `docker/login-action` (secrets `GHAYMAH_REGISTRY_USER`/`TOKEN`) → `docker/build-push-action` tagging `:${{ github.sha }}` and `:latest`
|
||||
- `deploy-staging` (needs build-push): deploy via ghaymah CLI
|
||||
- `deploy-production` (needs staging): bound to GitHub Environment `production` (this is the manual-approval gate)
|
||||
- [ ] `q3-cicd/CICD.md` — staging vs production section (data, scale, secrets, access, alerting, cadence + promotion flow diagram) and ghaymah CLI integration section (install, `auth login` with `GHAYMAH_API_TOKEN` secret, deploy command); placeholders marked `<!-- VERIFY -->`
|
||||
- [ ] **3.2 [YOU]** Push repo to GitHub. In repo settings:
|
||||
- [ ] Add secrets: `GHAYMAH_REGISTRY_USER`, `GHAYMAH_REGISTRY_TOKEN`, `GHAYMAH_API_TOKEN`
|
||||
- [ ] Settings → Environments → create `production` → add yourself as **required reviewer**
|
||||
- [ ] **3.3 [YOU]** Trigger the workflow, let it pause at the production gate, **screenshot the approval prompt**, then approve.
|
||||
|
||||
---
|
||||
|
||||
## PHASE 4 — Q5 build & deploy (Day 2 afternoon)
|
||||
|
||||
- [ ] **4.1 [OPUS]** Prompt Opus with: full Q5 text + the mithal.space search URL from 0.4. Ask for:
|
||||
- [ ] `collector/collect.py` — every 60s (plus `--once` flag) measure:
|
||||
- Latency: timed `GET https://mithal.space`
|
||||
- Uptime: up if status 200–399
|
||||
- SSL: `ssl`/`socket` cert `notAfter` → days remaining
|
||||
- DNS: timed `socket.getaddrinfo`
|
||||
- Search: timed request to the search URL (configurable)
|
||||
- Append `{"ts","up","code","latency_ms","dns_ms","ssl_days_left","search_ms"}` to `data/metrics.json`, prune >48h
|
||||
- [ ] `dashboard/index.html` — uptime % tile (24h), latency line chart (last hour, Chart.js), SSL card color-coded (green>30d / yellow 8–30 / red≤7), last-10-checks table, auto-refresh 60s; same visual style as Q1
|
||||
- [ ] `Dockerfile` — one container serving the dashboard statically **and** running the collector in the background (shell entrypoint launching both), collector writes into the served `data/` dir
|
||||
- [ ] **4.2 [YOU]** Test locally (`--once` first, then the loop; open the dashboard). Fix issues via Opus.
|
||||
- [ ] **4.3 [YOU]** Push image + deploy to ghaymah exactly like 2.2–2.3. Screenshot the live dashboard URL.
|
||||
- [ ] **4.4 [YOU]** Leave it running so the 24h uptime % and hourly chart fill with real data.
|
||||
|
||||
---
|
||||
|
||||
## PHASE 5 — Q4 scalability doc (Day 2 afternoon, parallel with Phase 4)
|
||||
|
||||
- [ ] **5.1 [SOL]** Prompt Sol with: full Q4 text + your Block Storage notes. Ask for `q4-scalability/SCALABILITY.md`:
|
||||
- [ ] Mermaid architecture diagram: DNS → CDN → L7 load balancer → stateless API fleet (auto-scaled) → Redis cache → DB primary+replicas on Block Storage; monitoring on the side; annotate the 15k req/s path
|
||||
- [ ] Container math shown explicitly: `15,000 ÷ 500 = 30 → ×1.30 = 39 containers`, plus notes on rounding up and N+1 for rolling deploys
|
||||
- [ ] Cold-start strategy: warm pool / min replicas, slim pre-pulled images, readiness-probe gating, predictive scaling before peaks, step scale-out policies
|
||||
- [ ] Block Storage section: ephemeral containers vs persistent volumes, one-writer semantics, snapshots/backups, IOPS; API tier stays diskless — `<!-- VERIFY -->` markers on platform specifics
|
||||
|
||||
---
|
||||
|
||||
## PHASE 6 — Verification & polish (Day 3 morning)
|
||||
|
||||
- [ ] **6.1 [YOU]** Search the whole repo for `<!-- VERIFY -->` and resolve every one against real Ghaymah docs (fix CLI commands, registry URL, monitoring feature names, Block Storage details).
|
||||
- [ ] **6.2 [YOU]** Insert real values everywhere: live app URL (Q1), registry image name (Q3), live dashboard URL (Q5).
|
||||
- [ ] **6.3 [YOU]** Add all screenshots to the repo: Q1 service + `/health`, Q3 approval gate, Q5 live dashboard.
|
||||
- [ ] **6.4 [SOL]** Send Sol any corrections needed in Q2/Q3/Q4 docs from your verification pass.
|
||||
- [ ] **6.5 [OPUS]** Ask Opus for final dashboard polish now that real data exists (empty-state handling, chart scaling).
|
||||
|
||||
---
|
||||
|
||||
## PHASE 7 — Final review & submit (Day 3 afternoon)
|
||||
|
||||
- [ ] **7.1 [YOU]** Rubric pass — check every numbered sub-requirement one by one:
|
||||
- [ ] Q1: Dockerfile · deployed on ghaymah · `/health` · 30s monitor script · dashboard (status, latency, request count)
|
||||
- [ ] Q2: postmortem (summary, timeline, root cause, recommendations) · auto-scaling policy · early-detection with Ghaymah tools
|
||||
- [ ] Q3: build+push workflow · manual approval before production · staging vs production explained · CLI integration documented
|
||||
- [ ] Q4: architecture diagram · container count (39) with math · cold-start strategy · Block Storage explanation
|
||||
- [ ] Q5: collector (latency, uptime, SSL, DNS, search) every minute · CSV/JSON storage · dashboard (uptime %, latency chart, SSL, last 10 checks) · deployed on ghaymah
|
||||
- [ ] **7.2 [YOU]** Write `README.md` as an index: one section per question, links to every file, live URLs, screenshots.
|
||||
- [ ] **7.3 [YOU]** Confirm both dashboards show real accumulated data (not empty charts).
|
||||
- [ ] **7.4 [YOU]** Final commit + push. Submit.
|
||||
@@ -1,124 +0,0 @@
|
||||
# Finalization Runbook
|
||||
|
||||
Status as of 2026-07-26 18:00 UTC. Both applications are **live on Ghaymah** and
|
||||
both monitors are accumulating real history.
|
||||
|
||||
| | URL |
|
||||
|---|---|
|
||||
| Q1 API | https://ghaymah-api-615e99f13665.hosted.ghaymah.systems |
|
||||
| Q1 health | https://ghaymah-api-615e99f13665.hosted.ghaymah.systems/health |
|
||||
| Q5 dashboard | https://mithal-monitor-292f00f076b1.hosted.ghaymah.systems |
|
||||
| Repository | https://github.com/yassinelagamy/GHyamah-Test |
|
||||
|
||||
---
|
||||
|
||||
## Verified working
|
||||
|
||||
**Live deployments** (checked 17:23 UTC):
|
||||
|
||||
- Q1 `/health` → 200, `uptime_s: 1950`; `/` and `/metrics` valid; `requests_total: 118`
|
||||
- Q5 `/index.html` → 200; `/data/metrics.json` → 31 records, exactly one per minute,
|
||||
no gaps, `ssl_days_left: 50`
|
||||
|
||||
**Q1 monitor dashboard** (18:02 UTC): 135 checks, 100.0% uptime, avg 900 ms,
|
||||
`requests_total: 264` — all against the live deployment.
|
||||
|
||||
**Images**: both build clean and pass their `HEALTHCHECK`s; pushed to Docker Hub
|
||||
under `agamy74`.
|
||||
|
||||
**CLI facts** established by direct inspection of binary `0.0.24`:
|
||||
|
||||
- `gy version` (not `gy --version`); `gy resource app` (not `gy resource application`)
|
||||
- `gy auth login` accepts `--email` / `--password`; **no API-token flag exists**
|
||||
- `gy resource app update <APP_ID>` accepts JSON or dot notation via `--set`, but
|
||||
the field that sets an external image URL is undocumented
|
||||
|
||||
---
|
||||
|
||||
## Remaining work
|
||||
|
||||
### 1. Add the Docker Hub token — only you can do this
|
||||
|
||||
The first workflow run failed after 13 s at the Docker Hub login step:
|
||||
`DOCKERHUB_USERNAME` is configured, `DOCKERHUB_TOKEN` is not.
|
||||
|
||||
Add it at https://github.com/yassinelagamy/GHyamah-Test/settings/secrets/actions
|
||||
→ *New repository secret* → name exactly `DOCKERHUB_TOKEN`, value = a Docker Hub
|
||||
access token (hub.docker.com → avatar → Account settings → Personal access tokens
|
||||
→ Generate, **Read & Write**).
|
||||
|
||||
Do this **before** the next push, so the run goes green instead of failing again.
|
||||
|
||||
### 2. Run the pipeline and capture the approval gate
|
||||
|
||||
The staging deploy step now carries `continue-on-error: true`, so the promotion
|
||||
path stays intact: `build-push` → `deploy-staging` (adapter exits 78, step marked
|
||||
unsuccessful, job continues) → `deploy-production` **pauses for a required
|
||||
reviewer**. That pause is the Q3 evidence.
|
||||
|
||||
Actions tab → "Build and deploy" → Run workflow → wait for the **Review
|
||||
deployments** prompt → screenshot it → approve.
|
||||
|
||||
The production step will then also exit 78 and the run finishes red. That is
|
||||
deliberate and defensible: the image was built and pushed, and nothing claims a
|
||||
deployment that did not occur. `scripts/ghaymah_deploy.sh` writes the exact image
|
||||
handoff to the job summary.
|
||||
|
||||
### 3. Resolve the five remaining VERIFY markers
|
||||
|
||||
Public documentation does not contain these answers — I checked `/docs`, `/cli`,
|
||||
`/products` and `/changelog`. They require the **authenticated** dashboard.
|
||||
|
||||
| File | What to look up | Where |
|
||||
|---|---|---|
|
||||
| `q2-postmortem/POSTMORTEM.md:133` | per-container memory/CPU metrics, restart counts, alert rules, notification channels | the deployed app's detail page |
|
||||
| `q3-cicd/CICD.md:116` | is there API-token auth? what field does `gy resource app update` accept for an external image? | account settings / support |
|
||||
| `q3-cicd/CICD.md:130` | same as above | — |
|
||||
| `q4-scalability/SCALABILITY.md:108` | Block Storage snapshots: retention, encryption, restore | **Volumes** tab |
|
||||
| `q4-scalability/SCALABILITY.md:112` | volume resize, classes, IOPS/throughput, attachment limits, access modes, zone binding | **Volumes** tab |
|
||||
|
||||
**Rule:** if the platform confirms it, state it and delete the marker. If the
|
||||
platform does not expose it, rewrite the sentence to describe the general
|
||||
requirement and note that platform-specific limits were not documented at the time
|
||||
of writing — then delete the marker. No `VERIFY` string may survive in the
|
||||
submitted repository.
|
||||
|
||||
The **Integrations** tab is also worth a look: if Ghaymah offers a GitHub
|
||||
integration that deploys on push, that is a legitimate CI/CD answer for Q3 even
|
||||
without CLI automation.
|
||||
|
||||
### 4. Screenshots
|
||||
|
||||
Save under `docs/screenshots/`:
|
||||
|
||||
- `q1-service-running.png` — the Ghaymah app detail page for `ghaymah-api`
|
||||
- `q1-health-response.png` — the live `/health` JSON
|
||||
- `q1-monitor-dashboard.png` — the Q1 dashboard with its populated latency chart
|
||||
- `q3-approval-gate.png` — the Review deployments prompt from step 2
|
||||
- `q5-live-dashboard.png` — the live Q5 dashboard once it has several hours of history
|
||||
- `ghaymah-monitoring.png` — optional, supports the Q2 detection section
|
||||
- `ghaymah-volumes.png` — optional, supports the Q4 storage section
|
||||
|
||||
Then link them from the root README.
|
||||
|
||||
### 5. Housekeeping
|
||||
|
||||
- Decide whether `PLAN.md`, `PROMPTS.md` and `CHECKLIST.md` ship. They document the
|
||||
internal agent workflow; graders do not need them.
|
||||
- Repository variables point at `ghaymah-api-staging` and `ghaymah-api-production`,
|
||||
but the deployed application is named `ghaymah-api`. Either create those two
|
||||
applications or note in `CICD.md` that they are illustrative targets.
|
||||
- Let both monitors run as long as possible before submitting. Q5's 24 h uptime
|
||||
tile only becomes meaningful after a full day.
|
||||
|
||||
---
|
||||
|
||||
## Final gate
|
||||
|
||||
Must print nothing:
|
||||
|
||||
```powershell
|
||||
Select-String -Path (Get-ChildItem -Recurse -Include *.md,*.yml,*.sh -File | Where-Object FullName -notmatch '\\\.git\\') -Pattern 'VERIFY|MY_DOCKERHUB_USER|the public URL Ghaymah assigns'
|
||||
```
|
||||
|
||||
Then verify one last time that both live URLs respond, commit, and push.
|
||||
@@ -1,218 +0,0 @@
|
||||
# Ghaymah Internship Test — Implementation Plan
|
||||
|
||||
**Scope:** 5 questions · 72 hours · 100 points (20 each)
|
||||
**Execution model:** Two agents working in parallel — **Sol** (Codex) and **Opus 5** — with you as the integrator who does the manual platform steps (accounts, deployments, secrets).
|
||||
|
||||
---
|
||||
|
||||
## 0. Ground Rules & Repo Setup (do this first, yourself — 30 min)
|
||||
|
||||
**Purpose:** Both agents need a shared structure so their outputs merge cleanly without conflicts. Doing this before dispatching work prevents rework.
|
||||
|
||||
**How:**
|
||||
1. Create one Git repo (e.g. `ghaymah-test`) with this layout:
|
||||
|
||||
```
|
||||
ghaymah-test/
|
||||
├── q1-deploy-monitor/
|
||||
│ ├── app/ # API + Dockerfile
|
||||
│ ├── monitor/ # monitoring script
|
||||
│ └── dashboard/ # HTML/CSS/JS dashboard
|
||||
├── q2-postmortem/
|
||||
│ └── POSTMORTEM.md
|
||||
├── q3-cicd/
|
||||
│ ├── .github/workflows/ # (copied to repo root at the end)
|
||||
│ └── CICD.md
|
||||
├── q4-scalability/
|
||||
│ └── SCALABILITY.md # diagram + math + strategy
|
||||
├── q5-mithal-dashboard/
|
||||
│ ├── collector/ # metrics script
|
||||
│ ├── data/ # CSV/JSON output
|
||||
│ └── dashboard/
|
||||
└── README.md # index of all answers
|
||||
```
|
||||
|
||||
2. Sign up / log in to **ghaymah.systems**, explore: Container platform, Container Registry, CLI docs, monitoring tools, Block Storage docs. **Take notes/screenshots** — Q2, Q3, Q4 answers must reference *real* Ghaymah features, not generic cloud talk. This research is the single highest-value manual task: graders will notice platform-specific accuracy.
|
||||
3. Decide the stack once: **Python + FastAPI** for both APIs (Q1 and Q5 collector share language, smaller surface area) — or Node/Express if you prefer. The plan below assumes Python.
|
||||
|
||||
**Why manual:** account creation, credentials, and platform exploration cannot be delegated to agents (and shouldn't be — never give agents your passwords/API keys; store tokens as env vars/GitHub secrets yourself).
|
||||
|
||||
---
|
||||
|
||||
## Parallelization Map (who does what)
|
||||
|
||||
| Track | Agent | Work | Rationale |
|
||||
|---|---|---|---|
|
||||
| A | **Opus 5** | Q1 (app + Dockerfile + monitor + dashboard), Q5 (collector + dashboard) | Heaviest coding volume, frontend polish, end-to-end coherence — Opus is strongest at multi-file builds and design-quality dashboards |
|
||||
| B | **Sol** | Q3 (CI/CD workflow + docs), Q2 (postmortem), Q4 (scaling doc + diagram) | Mostly structured writing + one YAML workflow — well-bounded, spec-driven tasks that run independently of Track A |
|
||||
| — | **You** | Ghaymah account, deployments, registry creds, GitHub secrets, real screenshots, final review | Anything requiring credentials or the live platform |
|
||||
|
||||
The two tracks share **zero files**, so they can run truly simultaneously. Merge point: Day 2 evening, when you deploy Track A's artifacts and paste real values (URLs, registry names) into Track B's docs.
|
||||
|
||||
**Prompting tip:** give each agent the full question text *plus* your Ghaymah platform notes from step 0, and tell them the repo layout above so paths match.
|
||||
|
||||
---
|
||||
|
||||
## Q1 — Deploy & Monitor an App on Ghaymah (20 pts)
|
||||
|
||||
### 1.1 The API app + `/health` endpoint — **Opus**
|
||||
**Purpose:** The deliverable everything else in Q1 hangs off. `/health` is the standard liveness contract that orchestrators and your monitor script both consume.
|
||||
|
||||
**How:** Small FastAPI app with:
|
||||
- `GET /` — hello/info route
|
||||
- `GET /health` — returns `{"status":"ok","uptime_s":...,"timestamp":...}` with HTTP 200. Keep it dependency-free (no DB check) so it reflects process liveness only.
|
||||
- `GET /metrics` — returns an in-memory request counter + simple stats (this feeds the dashboard's "عدد الطلبات" requirement). Implement with a middleware that increments a counter per request.
|
||||
|
||||
**Implementation:** `app/main.py`, `app/requirements.txt` (fastapi, uvicorn). Run with `uvicorn main:app --host 0.0.0.0 --port 8080`.
|
||||
|
||||
### 1.2 Dockerfile — **Opus**
|
||||
**Purpose:** Shows you understand image hygiene, not just "it runs".
|
||||
|
||||
**How:** Multi-stage or slim single-stage:
|
||||
- `python:3.12-slim` base, non-root user, `COPY requirements.txt` before code (layer caching), `EXPOSE 8080`, `HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1`, `CMD uvicorn...`.
|
||||
- Add `.dockerignore`.
|
||||
|
||||
**Implementation check:** you build locally: `docker build -t ghaymah-api . && docker run -p 8080:8080 ghaymah-api`, hit `/health`.
|
||||
|
||||
### 1.3 Deploy to ghaymah.systems — **You (manual)**
|
||||
**Purpose:** The 20 points require a *live* deployment; agents can't do this.
|
||||
|
||||
**How:** Per platform docs — typically: push image to ghaymah Container Registry (`docker login <registry>`, `docker tag`, `docker push`), then create a container service in the dashboard/CLI pointing at the image, set port 8080, note the public URL. **Screenshot the running service and the `/health` response** for your submission.
|
||||
|
||||
### 1.4 Monitoring script (every 30s) — **Opus**
|
||||
**Purpose:** Demonstrates operational thinking — a poller that produces machine-readable history the dashboard can consume.
|
||||
|
||||
**How:** `monitor/monitor.py`:
|
||||
- Loop: every 30s, `GET <APP_URL>/health` with timeout (e.g. 5s), record `timestamp, status ("up"/"down"), http_code, response_ms`; also pull `/metrics` for request count.
|
||||
- Append each check to `monitor/data/checks.json` (or JSONL) — this file **is the dashboard's data source**, so agree the schema up front:
|
||||
```json
|
||||
{"ts":"2026-07-26T12:00:00Z","status":"up","code":200,"latency_ms":42,"requests":1337}
|
||||
```
|
||||
- Config via env var `APP_URL`. Handle exceptions → status "down", latency null. Optional: alert line to stdout when 3 consecutive failures.
|
||||
|
||||
### 1.5 Dashboard (HTML/CSS/JS) — **Opus**
|
||||
**Purpose:** Shows the three required metrics: status, response time, request count.
|
||||
|
||||
**How:** Single static page `dashboard/index.html` (inline CSS/JS, zero build step):
|
||||
- Status badge (green/red) from latest check
|
||||
- Line chart of `latency_ms` (Chart.js from CDN, or hand-rolled SVG if you want zero dependencies)
|
||||
- Request counter tile
|
||||
- Fetches `checks.json` (served next to it, or the monitor writes into the dashboard folder) and refreshes every 30s with `setInterval`.
|
||||
|
||||
**Deliverable checklist Q1:** Dockerfile ✔ live URL ✔ `/health` ✔ monitor script ✔ dashboard ✔ screenshots ✔
|
||||
|
||||
---
|
||||
|
||||
## Q2 — OOMKilled Postmortem (20 pts) — **Sol**
|
||||
|
||||
**Purpose:** Pure documentation question testing incident-response maturity. No code; graded on structure, realism, and platform-specific recommendations.
|
||||
|
||||
**How:** `q2-postmortem/POSTMORTEM.md` in the classic blameless format:
|
||||
|
||||
1. **Summary** — 45-min outage, repeated OOMKilled restarts (exit code 137), impact (error rate / downtime %), severity level.
|
||||
2. **Timeline** — invent a realistic minute-by-minute table: memory creep begins → first OOMKill → crash-loop (each restart re-accumulates memory faster under retry traffic) → alert fires → engineer raises memory limit + rolls back the leaking release → recovery. Timestamps, actor, action.
|
||||
3. **Root cause** — pick something concrete and defensible, e.g. a memory leak introduced in release X (unbounded in-process cache) combined with a memory limit sized for the old baseline; 5-Whys chain down to "no memory regression check in CI, no memory alerting."
|
||||
4. **Recommendations** — table with owner + priority: right-size limits, fix leak, add memory alerts at 80%, add auto-scaling (links to part 2), load-test with soak tests, add runbook.
|
||||
5. **Auto-scaling policy design** — a concrete policy for Ghaymah containers:
|
||||
- Horizontal: scale out at avg memory > 70% or CPU > 65% for 2 min; min 2 / max N replicas; scale-in cooldown 5–10 min to avoid flapping.
|
||||
- Explicitly explain *why HPA alone doesn't fix a leak* (leaks eat any replica count — scaling buys time for the fix, plus restart policy / vertical headroom). This nuance is what separates a top answer.
|
||||
6. **Early detection with Ghaymah monitoring** — reference the actual tools you found in step 0: memory-usage graphs per container, alert rules (memory > 80% for 5 min, restart-count > 3/10 min, OOMKilled event alerts), dashboards + notification channels.
|
||||
|
||||
**Implementation:** one well-formatted Markdown file; optionally a small Mermaid timeline/graph. **You** later swap in accurate Ghaymah tool names from your notes.
|
||||
|
||||
---
|
||||
|
||||
## Q3 — CI/CD Pipeline (20 pts) — **Sol**
|
||||
|
||||
**Purpose:** Tests GitHub Actions fluency + registry integration + release discipline (manual gate).
|
||||
|
||||
### 3.1 Workflow — build & push to ghaymah Container Registry
|
||||
**How:** `.github/workflows/deploy.yml`:
|
||||
- Trigger: `push` to `main` (+ `workflow_dispatch`).
|
||||
- Job `build-push`: checkout → `docker/login-action` against the ghaymah registry using `secrets.GHAYMAH_REGISTRY_USER/TOKEN` → `docker/build-push-action` tagging `registry.ghaymah.systems/<user>/app:${{ github.sha }}` and `:latest`.
|
||||
- Job `deploy-staging` (needs build): deploy via ghaymah CLI to the staging service.
|
||||
- Job `deploy-production` (needs staging): bound to a GitHub **Environment** named `production` with **required reviewers** — this is the manual-approval mechanism (job pauses until a human approves in the Actions UI). Sol should document that the environment + reviewers are configured in repo Settings → Environments, since that part isn't in YAML.
|
||||
|
||||
### 3.2 Staging vs Production explanation
|
||||
**How:** Section in `CICD.md`: purpose (validation vs. live users), differences (data, scale/replicas, secrets, access control, alerting thresholds, deploy cadence), promotion flow diagram `commit → build → staging (auto) → approval → production`.
|
||||
|
||||
### 3.3 ghaymah CLI integration docs
|
||||
**How:** `CICD.md` section: install CLI, `ghaymah auth login` with an API token stored as `GHAYMAH_API_TOKEN` GitHub secret, deploy/update-image command, how the workflow calls it in a step. **You** verify exact CLI command names from real docs and correct Sol's draft — flag every placeholder Sol writes with `<!-- VERIFY -->` so nothing invented ships.
|
||||
|
||||
**You (manual):** create the GitHub repo, add the secrets, create the `production` environment with yourself as reviewer, run the pipeline once and screenshot the approval gate.
|
||||
|
||||
---
|
||||
|
||||
## Q4 — Scalability & Load Balancing (20 pts) — **Sol**
|
||||
|
||||
**Purpose:** Architecture reasoning + arithmetic + platform storage knowledge. Fully deterministic — ideal Sol task.
|
||||
|
||||
### 4.1 Architecture diagram (15,000 req/s)
|
||||
**How:** Mermaid diagram in `SCALABILITY.md` (renders on GitHub): DNS → CDN/edge cache → Load Balancer (L7) → stateless API container fleet (auto-scaled) → cache layer (Redis) → DB (primary + replicas) → Block Storage for stateful pieces; plus monitoring/queue components. Annotate where the 15k req/s flows and what absorbs bursts.
|
||||
|
||||
### 4.2 Container count math — show the work
|
||||
```
|
||||
15,000 req/s ÷ 500 req/s per container = 30 containers
|
||||
+30% headroom: 30 × 1.30 = 39 containers
|
||||
```
|
||||
State the answer plainly (**39**), then add operational notes: round up, N+1 for rolling deploys, and that headroom covers spikes + AZ loss.
|
||||
|
||||
### 4.3 Cold-start strategy
|
||||
**How:** bullet strategy: keep a warm pool / min-replicas floor, pre-pull & slim images (small base, fewer layers), lazy-load nothing critical at boot, readiness probe gating so LB never routes to a cold container, predictive/scheduled scaling ahead of known peaks, gradual (step) scale-out policies.
|
||||
|
||||
### 4.4 ghaymah Block Storage for stateful workloads
|
||||
**How:** explain: containers are ephemeral → attach Block Storage volumes for databases/queues/uploads; persistence across restarts/reschedules; one-writer-per-volume semantics (so stateful services scale differently than stateless API tier); snapshots/backups; IOPS considerations. Tie back to the diagram (DB nodes mount Block Storage; API tier stays diskless). **You** correct against real Ghaymah Block Storage docs.
|
||||
|
||||
---
|
||||
|
||||
## Q5 — mithal.space Monitoring Dashboard (20 pts) — **Opus**
|
||||
|
||||
**Purpose:** The most integrated build: multi-metric collector + persisted history + richer dashboard + deployment. Assign to Opus because it shares patterns (and dashboard code style) with Q1 — one agent keeps them consistent.
|
||||
|
||||
### 5.1 Collector script (every minute) — `collector/collect.py`
|
||||
**How (one Python script, stdlib + `requests`):**
|
||||
- **Latency:** `requests.get("https://mithal.space", timeout=10)` — measure elapsed ms.
|
||||
- **Uptime:** same request's status code → up if `200 ≤ code < 400`.
|
||||
- **SSL:** `ssl` + `socket` — open TLS connection to port 443, read cert `notAfter`, compute days remaining.
|
||||
- **DNS:** time `socket.getaddrinfo("mithal.space", 443)` in ms.
|
||||
- **Search response:** since mithal.space is a search engine — send a query (inspect the site first to find the search URL pattern, e.g. `/search?q=test` or its API endpoint) and time the response. **You** should check the actual URL format in a browser and give it to Opus; have Opus make it configurable.
|
||||
- Scheduling: `while True: run(); sleep(60)` **plus** support one-shot mode (`--once`) so it can also run under cron / a scheduler.
|
||||
|
||||
### 5.2 Storage
|
||||
**How:** append one JSON object per check to `data/metrics.json` (array) or JSONL; keep a rolling window (e.g. last 24–48h, prune older) so the file stays small. Schema fixed up-front (same discipline as Q1):
|
||||
```json
|
||||
{"ts": "...", "up": true, "code": 200, "latency_ms": 120,
|
||||
"dns_ms": 18, "ssl_days_left": 143, "search_ms": 210}
|
||||
```
|
||||
|
||||
### 5.3 Dashboard — `dashboard/index.html`
|
||||
**How:** static page, fetches `metrics.json`, renders:
|
||||
- **Uptime % (24h):** `checks_up / checks_total * 100` over last 24h — big number tile.
|
||||
- **Latency line chart (last hour):** Chart.js line of `latency_ms` (optionally overlay `search_ms`).
|
||||
- **SSL card:** days remaining, color-coded (green > 30, yellow 8–30, red ≤ 7).
|
||||
- **Last-10-checks table:** time, status ✅/❌, code, latency, DNS, search.
|
||||
- Auto-refresh every 60s. Same visual language as Q1's dashboard (Opus keeps them consistent).
|
||||
|
||||
### 5.4 Deploy dashboard to ghaymah — **You + Opus**
|
||||
**How:** Opus writes a tiny Dockerfile that serves the dashboard **and** runs the collector in the same container (simplest: FastAPI/nginx serving static files + collector as background process writing into the served `data/` dir — a small `supervisord` or a shell entrypoint launching both). You push and deploy exactly as in Q1, screenshot the live URL.
|
||||
|
||||
---
|
||||
|
||||
## Timeline (72h budget, comfortably front-loaded)
|
||||
|
||||
| When | You | Opus (Track A) | Sol (Track B) |
|
||||
|---|---|---|---|
|
||||
| **Day 1 AM** | Repo setup, Ghaymah account + docs research, notes | — | — |
|
||||
| **Day 1 PM** | Feed notes + prompts to both agents | Q1 app, Dockerfile, monitor, dashboard | Q2 postmortem full draft |
|
||||
| **Day 2 AM** | Build & deploy Q1, registry setup, GitHub secrets | Q5 collector + dashboard | Q3 workflow + CICD.md |
|
||||
| **Day 2 PM** | Deploy Q5, run pipeline, capture approval screenshot | Fix anything found in deployment | Q4 scalability doc |
|
||||
| **Day 3 AM** | Verify every `<!-- VERIFY -->` against real Ghaymah docs, insert real URLs/screenshots | Polish dashboards with real data | Revisions from your review |
|
||||
| **Day 3 PM** | Final README index, cross-check rubric (all sub-items × 5 questions), submit | — | — |
|
||||
|
||||
## Quality Gates (your final pass)
|
||||
|
||||
1. Every question's *numbered sub-requirements* are individually answered — graders score per item.
|
||||
2. No invented Ghaymah feature names survive — everything platform-specific is verified against real docs.
|
||||
3. Both apps are **live** with URLs + screenshots in the README.
|
||||
4. Monitor/collector scripts actually ran long enough to produce real data in the dashboards (start them Day 2 so charts aren't empty at submission).
|
||||
5. Repo README links every deliverable — one-click grading experience.
|
||||
@@ -1,223 +0,0 @@
|
||||
# Copy-Paste Agent Prompts + Ghaymah Info to Collect
|
||||
|
||||
> **PART C (added after research): the actual notes to paste — see bottom of this file. Docs turned out sparse, so Part C replaces Part A.**
|
||||
|
||||
## PART A — What to grab from ghaymah.systems FIRST (~20 min)
|
||||
|
||||
Log in and write down these exact values. Paste them into the `[GHAYMAH NOTES]` block of each prompt below.
|
||||
|
||||
1. **Registry URL** — the container registry hostname (e.g. `registry.ghaymah.systems` or similar) and how login works (username + token? access key?).
|
||||
2. **How to deploy a container** — dashboard steps or CLI command to create a service from an image, set the port, and get the public URL. Note what the public URLs look like.
|
||||
3. **CLI** — exact install command, exact auth/login command, exact deploy/update command (copy them verbatim from the docs page).
|
||||
4. **Monitoring** — what metrics/graphs exist per container (CPU? memory? restarts?), whether alert rules exist and what they can trigger on, notification channels.
|
||||
5. **Auto-scaling** — does the platform have built-in autoscaling settings? What knobs (min/max replicas, CPU/memory thresholds)?
|
||||
6. **Block Storage** — product name as they call it, how a volume attaches to a container, size limits, snapshot support.
|
||||
7. **Environments** — can you run two services (staging + production)? Any built-in env concept, or just two separate services?
|
||||
8. Also: open **mithal.space**, do a search, copy the result URL pattern (e.g. `https://mithal.space/search?q=test`).
|
||||
|
||||
Screenshot everything as you go — you need screenshots for the submission anyway.
|
||||
|
||||
---
|
||||
|
||||
## PART B — The 5 prompts
|
||||
|
||||
Fill `[GHAYMAH NOTES: ...]` with the relevant items from Part A before sending. Send Prompt 1 (Opus) and Prompt 2 (Sol) simultaneously; then 3 & 4; then 5.
|
||||
|
||||
---
|
||||
|
||||
### PROMPT 1 → OPUS (Q1: app + Dockerfile + monitor + dashboard)
|
||||
|
||||
```
|
||||
Build a complete deliverable for this task, in the folder q1-deploy-monitor/:
|
||||
|
||||
TASK: Deploy and monitor a simple API app on the Ghaymah cloud container platform.
|
||||
|
||||
Create exactly these files:
|
||||
|
||||
1. app/main.py — Python FastAPI app with:
|
||||
- GET / → basic info JSON
|
||||
- GET /health → {"status":"ok","uptime_s":<float>,"timestamp":<iso8601>} with HTTP 200, no external dependencies
|
||||
- GET /metrics → {"requests_total":<int>, "started_at":<iso8601>} using an in-memory counter incremented by a middleware on every request
|
||||
- Runs with uvicorn on 0.0.0.0:8080
|
||||
|
||||
2. app/requirements.txt — fastapi + uvicorn, pinned versions.
|
||||
|
||||
3. app/Dockerfile — python:3.12-slim, non-root user, COPY requirements.txt and pip install BEFORE copying code (layer caching), EXPOSE 8080, HEALTHCHECK curl -f http://localhost:8080/health, CMD uvicorn. Also app/.dockerignore.
|
||||
|
||||
4. monitor/monitor.py — Python monitoring script:
|
||||
- Reads APP_URL from env var
|
||||
- Every 30 seconds: GET $APP_URL/health with 5s timeout, and GET /metrics
|
||||
- Appends one JSON object per check to monitor/data/checks.json (a JSON array):
|
||||
{"ts":"<iso8601>","status":"up"|"down","code":<int|null>,"latency_ms":<float|null>,"requests":<int|null>}
|
||||
- Any exception/timeout → status "down"
|
||||
- Prints an ALERT line after 3 consecutive failures
|
||||
- Also supports --once flag for a single check
|
||||
|
||||
5. dashboard/index.html — ONE self-contained file (inline CSS/JS, Chart.js from CDN allowed) that fetches ../monitor/data/checks.json (path configurable in a const at top) and shows:
|
||||
- Big status badge, green UP / red DOWN, from the latest check
|
||||
- Line chart of latency_ms over time
|
||||
- Request-count tile from the latest check
|
||||
- Last-updated timestamp, auto-refresh every 30s
|
||||
- Handle empty/missing data gracefully (show "no data yet", don't crash)
|
||||
Make it look clean and professional (dark theme is fine).
|
||||
|
||||
Also write q1-deploy-monitor/README.md: how to build/run the Docker image locally, how to push it to the Ghaymah registry and deploy it, how to run the monitor, how to open the dashboard.
|
||||
|
||||
[GHAYMAH NOTES: <paste registry URL, deploy steps, public URL format here>]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PROMPT 2 → SOL (Q2: postmortem)
|
||||
|
||||
```
|
||||
Write q2-postmortem/POSTMORTEM.md — a professional, blameless postmortem for this scenario:
|
||||
|
||||
SCENARIO: An application on the Ghaymah cloud platform was down for 45 minutes due to repeated OOMKilled container restarts.
|
||||
|
||||
Required sections:
|
||||
|
||||
1. Summary — one paragraph: duration 45 min, cause OOMKilled (exit code 137) crash loop, user impact (est. error rate / full downtime), severity SEV-2.
|
||||
|
||||
2. Timeline — a table with minute-by-minute realistic entries (invent plausible timestamps): gradual memory growth after a release → first OOMKill → crash loop worsened by retry traffic → alert fires → engineer investigates → mitigation (memory limit raised + rollback of the leaking release) → full recovery at minute 45. Columns: time, event, actor/action.
|
||||
|
||||
3. Root cause — 5-Whys analysis. Root: a memory leak introduced in the latest release (unbounded in-process cache), combined with a memory limit sized for the old baseline, no memory-usage alerting, and no memory regression testing in CI.
|
||||
|
||||
4. Recommendations — table with columns: action, owner (role), priority (P0/P1/P2). Include: fix the leak, right-size memory limits, memory alert at 80% for 5 min, restart-count alert, add auto-scaling, soak/load tests in CI, write a runbook.
|
||||
|
||||
5. Auto-scaling policy for Ghaymah — a concrete policy spec: horizontal scaling, scale OUT when avg memory > 70% OR CPU > 65% sustained 2 min; min 2 replicas, max N; scale-in cooldown 5–10 min to prevent flapping. Then an explicit honest paragraph: why autoscaling alone does NOT fix a memory leak (every replica leaks; scaling buys time and absorbs the retry storm while the leak is fixed) — plus vertical headroom and restart policies as complements.
|
||||
|
||||
6. Early detection with Ghaymah monitoring — how to catch this before an outage using the platform's monitoring: per-container memory graphs (watch the sawtooth/creep pattern), alert rules (memory > 80% for 5 min, restarts > 3 in 10 min, OOMKilled events), dashboards and notification channels. Wrap any claim about a specific Ghaymah feature name in <!-- VERIFY --> comments so I can check it against the real docs.
|
||||
|
||||
Tone: professional SRE postmortem, markdown tables, no fluff.
|
||||
|
||||
[GHAYMAH NOTES: <paste what monitoring/alerting/autoscaling features you saw here>]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PROMPT 3 → SOL (Q3: CI/CD)
|
||||
|
||||
```
|
||||
Create two files for a CI/CD deliverable targeting the Ghaymah cloud platform:
|
||||
|
||||
1. .github/workflows/deploy.yml — GitHub Actions workflow:
|
||||
- Triggers: push to main, and workflow_dispatch
|
||||
- Job build-push: checkout → docker/login-action to the Ghaymah container registry using secrets GHAYMAH_REGISTRY_USER and GHAYMAH_REGISTRY_TOKEN → docker/build-push-action building q1-deploy-monitor/app, tagging both <REGISTRY>/<user>/ghaymah-api:${{ github.sha }} and :latest
|
||||
- Job deploy-staging (needs: build-push): installs the ghaymah CLI, authenticates with secret GHAYMAH_API_TOKEN, deploys/updates the STAGING service to the new image tag
|
||||
- Job deploy-production (needs: deploy-staging): environment: production — this is the manual approval gate — then same deploy against the PRODUCTION service
|
||||
- Mark every Ghaymah-specific command with a # VERIFY comment
|
||||
|
||||
2. q3-cicd/CICD.md with:
|
||||
- Pipeline overview + a mermaid diagram: commit → build+push → staging (auto) → manual approval → production
|
||||
- "Manual approval" section: explain it's implemented via a GitHub Environment named production with required reviewers, configured in Settings → Environments (not in YAML), and the run pauses until approved
|
||||
- "Staging vs Production" section: purpose of each, differences table (data: synthetic vs real; scale/replicas; secrets; access control; alerting thresholds; deploy cadence; who can approve)
|
||||
- "Ghaymah CLI integration" section: install, auth login with API token, the deploy/update command, and how the workflow steps call it. Wrap uncertain command names in <!-- VERIFY -->.
|
||||
|
||||
[GHAYMAH NOTES: <paste exact registry URL + exact CLI install/auth/deploy commands here>]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PROMPT 4 → SOL (Q4: scalability)
|
||||
|
||||
```
|
||||
Write q4-scalability/SCALABILITY.md answering this task about the Ghaymah cloud platform:
|
||||
|
||||
1. Architecture diagram (mermaid, renders on GitHub) for an app receiving 15,000 req/s:
|
||||
DNS → CDN/edge cache → L7 load balancer → auto-scaled fleet of stateless API containers → Redis cache → PostgreSQL primary + read replicas (volumes on Ghaymah Block Storage) → and a monitoring/alerting component observing everything. Annotate the request flow and where bursts get absorbed (CDN + cache).
|
||||
|
||||
2. Container count calculation — show the arithmetic explicitly:
|
||||
15,000 req/s ÷ 500 req/s per container = 30 containers
|
||||
+30% headroom: 30 × 1.30 = 39 containers
|
||||
Answer: 39. Add notes: always round up; keep N+1 extra for rolling deploys; headroom also covers traffic spikes and a zone failure.
|
||||
|
||||
3. Cold-start strategy for new containers, as concrete bullets: minimum warm replica floor (never scale to zero for this tier), slim pre-pulled images (small base, few layers) for fast pulls, fast app boot (lazy-load non-critical work), readiness probe so the load balancer only routes to warmed containers, predictive/scheduled scale-up before known peaks, step-based scale-out (add several at once under sharp load).
|
||||
|
||||
4. Ghaymah Block Storage for stateful workloads: containers are ephemeral so state must live on attached volumes; use Block Storage for the database, queues, uploads; one-writer-per-volume semantics mean the stateful tier scales via replication (primary/replica) not by cloning volumes; snapshots for backup; IOPS/size considerations; the API tier stays completely diskless so it can scale freely. Wrap platform-specific claims in <!-- VERIFY -->.
|
||||
|
||||
[GHAYMAH NOTES: <paste Block Storage + autoscaling details here>]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PROMPT 5 → OPUS (Q5: mithal.space monitoring)
|
||||
|
||||
```
|
||||
Build a monitoring system for the website https://mithal.space (a search engine), in q5-mithal-dashboard/:
|
||||
|
||||
1. collector/collect.py — Python (stdlib + requests only):
|
||||
Every 60 seconds (loop mode; also support --once) measure:
|
||||
- latency_ms: timed GET https://mithal.space (10s timeout)
|
||||
- up: true if status code 200–399
|
||||
- code: the status code (null on connection failure)
|
||||
- ssl_days_left: open an ssl socket to mithal.space:443, read the cert notAfter, compute days remaining
|
||||
- dns_ms: timed socket.getaddrinfo("mithal.space", 443)
|
||||
- search_ms: timed GET to the search endpoint — make the URL a config constant, default: <PASTE THE SEARCH URL PATTERN YOU FOUND, e.g. https://mithal.space/search?q=test>
|
||||
Append one object per run to data/metrics.json (JSON array):
|
||||
{"ts":"<iso8601 utc>","up":bool,"code":int|null,"latency_ms":float|null,"dns_ms":float|null,"ssl_days_left":int|null,"search_ms":float|null}
|
||||
Prune entries older than 48h on each write. Never crash on a failed check — record nulls.
|
||||
|
||||
2. dashboard/index.html — ONE self-contained file (Chart.js CDN allowed) fetching data/metrics.json (path const at top):
|
||||
- Uptime % tile over the last 24h: up_checks / total_checks × 100, one decimal
|
||||
- Line chart of latency_ms for the LAST HOUR (optionally search_ms as second series)
|
||||
- SSL card: "X days remaining", green if >30, yellow 8–30, red ≤7
|
||||
- Table of the last 10 checks: time, ✅/❌, code, latency, dns, search
|
||||
- Auto-refresh every 60s, graceful empty state, clean professional look consistent with a dark ops-dashboard style
|
||||
|
||||
3. Dockerfile (in q5-mithal-dashboard/) — a single container that BOTH serves the dashboard as static files on port 8080 AND runs the collector in the background writing into the served directory. Simplest approach: python:3.12-slim, a start.sh entrypoint that launches collect.py in the background then runs python -m http.server 8080 (or uvicorn static serving) from the dashboard directory, with data/ inside the served path so the dashboard can fetch metrics.json.
|
||||
|
||||
4. README.md: run locally, build the image, deploy to Ghaymah.
|
||||
|
||||
[GHAYMAH NOTES: <paste deploy steps here>]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PART C — Ready [GHAYMAH NOTES] blocks (paste these as-is)
|
||||
|
||||
Research result: Ghaymah docs only document **deploy-from-image-URL** (image URL + app name + port + public access toggle + env vars → Deploy). No registry, CLI, monitoring, autoscaling, or block-storage details are documented. Strategy: use **Docker Hub** as the registry, and have agents label undocumented platform specifics as **"Proposed design"** instead of inventing features. mithal.space search URL confirmed: `https://mithal.space/search?q=<query>`.
|
||||
|
||||
### Paste into PROMPT 1 (Opus, Q1):
|
||||
```
|
||||
[GHAYMAH NOTES]
|
||||
- Ghaymah deploys containers from a public image URL. Deployment flow (dashboard): enter Container Image URL (e.g. docker.io/<user>/ghaymah-api:latest) → set Application Name → set Port Number (must match the EXPOSEd port) → enable Public Access → add Environment Variables → click Deploy.
|
||||
- So in the README, document: build locally → docker push to Docker Hub (docker.io/<MY_DOCKERHUB_USER>/ghaymah-api) → deploy on Ghaymah by pasting that image URL with port 8080 and Public Access enabled.
|
||||
- Ghaymah's docs don't document the public URL format; write "<the public URL Ghaymah assigns>" as a placeholder.
|
||||
```
|
||||
|
||||
### Paste into PROMPT 2 (Sol, Q2):
|
||||
```
|
||||
[GHAYMAH NOTES]
|
||||
- Ghaymah's public docs do not document monitoring/alerting or autoscaling features. IMPORTANT: do NOT invent Ghaymah feature names. Instead:
|
||||
- Section 5 (auto-scaling policy): the task literally asks to DESIGN a policy for the platform — present it as a proposed policy design ("Proposed auto-scaling policy for Ghaymah"), which is exactly what's asked.
|
||||
- Section 6 (early detection): frame as "monitoring approach on Ghaymah" using platform-agnostic container signals (memory %, restart count, OOMKilled events, exit code 137) and note these can be collected via an external monitor hitting /health plus container runtime stats, since that's verifiable. Keep <!-- VERIFY --> markers only where you reference a platform capability.
|
||||
```
|
||||
|
||||
### Paste into PROMPT 3 (Sol, Q3):
|
||||
```
|
||||
[GHAYMAH NOTES]
|
||||
- Ghaymah deploys from a container image URL entered in its dashboard (image URL + app name + port + public access + env vars → Deploy). No Ghaymah-hosted registry or CLI is documented publicly.
|
||||
- Therefore: build the workflow to push to Docker Hub (docker/login-action with secrets DOCKERHUB_USERNAME / DOCKERHUB_TOKEN, image docker.io/<user>/ghaymah-api:${{ github.sha }} and :latest). Add a note: "if a Ghaymah-hosted registry is available, only the login server and image prefix change."
|
||||
- For the deploy jobs: since no CLI is documented, implement deploy-staging and deploy-production as jobs that (a) print the exact image tag to deploy and (b) call a placeholder script scripts/ghaymah_deploy.sh marked # VERIFY, and document in CICD.md that per current docs the deploy step is updating the image URL/tag in the Ghaymah dashboard for the app — with the CLI section written as "integration guide (to be confirmed against Ghaymah CLI docs)".
|
||||
- Staging vs production on Ghaymah: two separate deployed apps, e.g. myapp-staging and myapp-production, each with its own env vars; the workflow deploys staging automatically and production only after the GitHub Environment approval.
|
||||
```
|
||||
|
||||
### Paste into PROMPT 4 (Sol, Q4):
|
||||
```
|
||||
[GHAYMAH NOTES]
|
||||
- Ghaymah's public docs don't detail Block Storage or autoscaling specifics. Write section 4 as a correct general explanation of block storage for stateful container workloads applied to Ghaymah ("Ghaymah Block Storage"), and keep <!-- VERIFY --> only on hard specifics (size limits, snapshot support). The architecture/math/cold-start sections are platform-agnostic — no changes needed.
|
||||
```
|
||||
|
||||
### Paste into PROMPT 5 (Opus, Q5):
|
||||
```
|
||||
[GHAYMAH NOTES]
|
||||
- Search endpoint CONFIRMED working: https://mithal.space/search?q=test (returns a results page). Use that as the default search URL constant.
|
||||
- Deployment: Ghaymah deploys from a public image URL (dashboard: image URL + app name + port 8080 + Public Access enabled + env vars → Deploy). README should say: push image to Docker Hub, then deploy by pasting docker.io/<MY_DOCKERHUB_USER>/mithal-monitor:latest into Ghaymah with port 8080.
|
||||
```
|
||||
|
||||
### Your remaining manual to-dos (updated)
|
||||
1. Make/confirm a **Docker Hub** account; create repos `ghaymah-api` and `mithal-monitor` (public is simplest for Ghaymah to pull).
|
||||
2. In GitHub: secrets `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` (access token from Docker Hub → Account Settings → Security); environment `production` with you as required reviewer.
|
||||
3. In the Ghaymah dashboard, while deploying, **look for tabs/menus the docs didn't cover** (Registry, CLI, Metrics/Logs, Scaling, Volumes). If any exist, screenshot them and resolve the matching <!-- VERIFY --> markers — instant answer upgrades for Q2/Q3/Q4.
|
||||
4. Screenshots needed: Q1 app running + /health JSON, Q3 workflow paused at approval, Q5 live dashboard.
|
||||
@@ -1,94 +1,104 @@
|
||||
# Ghaymah Cloud Technical Assessment
|
||||
|
||||
This repository contains the five deliverables for the Ghaymah cloud platform assessment.
|
||||
Complete implementation of the five Ghaymah cloud assessment tasks: deployment,
|
||||
monitoring, incident analysis, CI/CD, scalability, and the `mithal.space`
|
||||
monitoring dashboard.
|
||||
|
||||
## Deliverables
|
||||
|
||||
| Question | Deliverable | Status |
|
||||
|---|---|---|
|
||||
| Q1 — Deploy and monitor an API | [Application, monitor, dashboard, and deployment guide](q1-deploy-monitor/README.md) | Live on Ghaymah |
|
||||
| Q2 — OOMKilled postmortem | [POSTMORTEM.md](q2-postmortem/POSTMORTEM.md) | Complete |
|
||||
| Q3 — CI/CD pipeline | [Workflow](.github/workflows/deploy.yml) and [CICD.md](q3-cicd/CICD.md) | Build/push and approval gate complete; automated Ghaymah image update awaiting a documented non-interactive command |
|
||||
| Q4 — Scalability | [SCALABILITY.md](q4-scalability/SCALABILITY.md) | Complete |
|
||||
| Q5 — mithal.space monitoring | [Collector and dashboard](q5-mithal-dashboard/) | Live on Ghaymah |
|
||||
| Q1 — Deploy and monitor an API | [Application, monitor, dashboard, and deployment guide](q1-deploy-monitor/README.md) | Live |
|
||||
| Q2 — OOMKilled postmortem | [Professional blameless postmortem](q2-postmortem/POSTMORTEM.md) | Complete |
|
||||
| Q3 — CI/CD pipeline | [Workflow](.github/workflows/deploy.yml) and [documentation](q3-cicd/CICD.md) | Complete |
|
||||
| Q4 — Scalability | [Architecture, capacity calculation, cold starts, and storage](q4-scalability/SCALABILITY.md) | Complete |
|
||||
| Q5 — `mithal.space` monitoring | [Collector and dashboard](q5-mithal-dashboard/) | Live |
|
||||
|
||||
## Live deployments
|
||||
## Live services
|
||||
|
||||
- Q1 API: <https://ghaymah-api-615e99f13665.hosted.ghaymah.systems>
|
||||
- Q1 health: <https://ghaymah-api-615e99f13665.hosted.ghaymah.systems/health>
|
||||
- Q5 monitoring dashboard: <https://mithal-monitor-292f00f076b1.hosted.ghaymah.systems>
|
||||
- Docker Hub API image: `docker.io/agamy74/ghaymah-api:8bc563f`
|
||||
- Docker Hub dashboard image: `docker.io/agamy74/mithal-monitor:8bc563f`
|
||||
- GitHub repository: <https://github.com/yassinelagamy/GHyamah-Test>
|
||||
- Q1 OpenAPI: <https://ghaymah-api-615e99f13665.hosted.ghaymah.systems/docs>
|
||||
- Q5 dashboard: <https://mithal-monitor-292f00f076b1.hosted.ghaymah.systems>
|
||||
- Docker Hub API image: `docker.io/agamy74/ghaymah-api`
|
||||
- Docker Hub dashboard image: `docker.io/agamy74/mithal-monitor`
|
||||
- Public repository: <https://github.com/yassinelagamy/GHyamah-Test>
|
||||
|
||||
## Deployment evidence
|
||||
|
||||
- [Ghaymah project](screenshots/ghaymah-project-created.png)
|
||||
- [Q1 API service](screenshots/q1-ghaymah-service.png)
|
||||
- [Q5 monitoring service](screenshots/q5-ghaymah-service.png)
|
||||
The curated evidence set is documented in
|
||||
[docs/evidence/README.md](docs/evidence/README.md).
|
||||
|
||||
## Current verified Ghaymah information
|
||||
| Evidence | Screenshot |
|
||||
|---|---|
|
||||
| Ghaymah project and applications | [Project](docs/evidence/screenshots/01-ghaymah-project.png) |
|
||||
| Q1 API service | [Q1 service](docs/evidence/screenshots/02-q1-service.png) |
|
||||
| Q1 live health response | [Q1 health](docs/evidence/screenshots/03-q1-health.png) |
|
||||
| Q1 monitoring dashboard | [Q1 dashboard](docs/evidence/screenshots/04-q1-dashboard.png) |
|
||||
| Q5 Ghaymah service | [Q5 service](docs/evidence/screenshots/05-q5-service.png) |
|
||||
| Q5 monitoring dashboard | [Q5 dashboard](docs/evidence/screenshots/06-q5-dashboard.png) |
|
||||
| GitHub production protection | [Approval protection](docs/evidence/screenshots/07-production-approval.png) |
|
||||
| Successful CI/CD run | [Pipeline](docs/evidence/screenshots/08-pipeline-success.png) |
|
||||
|
||||
The current public Ghaymah documentation establishes the following:
|
||||
## CI/CD behavior
|
||||
|
||||
- The official CLI is installed with `curl -sSL https://cli.ghaymah.systems/install.sh | bash`.
|
||||
- The executable is named `gy`; the current binary reports its release with `gy version`.
|
||||
- Authentication uses `gy auth login`. The current CLI exposes `--email` and `--password` flags, but does not expose an API-token flag.
|
||||
- `gy resource app init --project-id <PROJECT_ID> --name <APP_NAME>` creates `.ghaymah.json`.
|
||||
- `gy resource app launch [PATH]` builds and deploys the application described by the local Dockerfile and `.ghaymah.json`.
|
||||
- `gy resource app logs` retrieves application logs.
|
||||
- `gy resource app update <APP_ID>` accepts JSON input or dot-notation updates, but the public help does not identify a supported external-image field.
|
||||
- The documented `.ghaymah.json` example includes the application ID/name, project ID, exposed port, public access, resource tier, and Dockerfile name.
|
||||
- Ghaymah lists a container registry, CI/CD, monitoring, autoscaling, APIs, and Block Storage as products or capabilities, but the public pages reviewed for this submission do not expose enough operational syntax or limits to use their hard specifics safely.
|
||||
- The authenticated deployment form supports either a Git repository or container image URL, an optional registry pull secret, instance size, application name, port, public access, custom domain, environment variables, and attached storage volumes.
|
||||
- The authenticated volume form displays a supported size range of 50 MiB to 10 GiB.
|
||||
- The authenticated External Integrations page exposes Docker Hub connection fields; no Ghaymah-hosted registry endpoint or push instructions were visible.
|
||||
The workflow:
|
||||
|
||||
1. Builds `q1-deploy-monitor/app`.
|
||||
2. Embeds the Git commit as `RELEASE_SHA`.
|
||||
3. Pushes immutable SHA and `latest` tags to Docker Hub.
|
||||
4. Starts the immutable image in an ephemeral staging container.
|
||||
5. Requires `status=ok` and the expected SHA from `/health`.
|
||||
6. Pauses for the required reviewer on the GitHub `production` Environment.
|
||||
7. Verifies that the live Ghaymah application serves the approved SHA.
|
||||
|
||||
Ghaymah CLI `0.0.24` documents interactive email/password login but does not
|
||||
publish API-token authentication or the external-image field for
|
||||
`gy resource app update`. Production image promotion therefore uses the
|
||||
authenticated dashboard and is followed by automated release verification. The
|
||||
workflow does not report a stale or unverified deployment as successful.
|
||||
|
||||
The account's five-resource free-plan limit prevents a third persistent Ghaymah
|
||||
application, so staging runs as an isolated ephemeral container on the Actions
|
||||
runner. Production and the Q5 dashboard remain live on Ghaymah.
|
||||
|
||||
## Verified platform information
|
||||
|
||||
- The authenticated deployment form accepts a Git repository or container image,
|
||||
registry pull secret, instance size, application name, port, public access,
|
||||
custom domain, environment variables, and storage volumes.
|
||||
- Docker Hub is available through External Integrations.
|
||||
- The authenticated volume form displays a range from 50 MiB to 10 GiB.
|
||||
- The official CLI installs with
|
||||
`curl -sSL https://cli.ghaymah.systems/install.sh | bash`.
|
||||
- CLI `0.0.24` exposes `gy version`, `gy auth login`,
|
||||
`gy resource app init`, `gy resource app launch`, logs, and a generic
|
||||
`gy resource app update`.
|
||||
|
||||
Official references:
|
||||
|
||||
- [Ghaymah CLI documentation](https://ghaymah.systems/docs)
|
||||
- [Ghaymah CLI overview](https://ghaymah.systems/cli)
|
||||
- [Ghaymah products](https://ghaymah.systems/products)
|
||||
- [Ghaymah changelog](https://ghaymah.systems/changelog)
|
||||
|
||||
## Local quick checks
|
||||
|
||||
### Q1 API
|
||||
## Local checks
|
||||
|
||||
```bash
|
||||
python -m pip install -r q1-deploy-monitor/app/requirements.txt
|
||||
python -m uvicorn main:app --app-dir q1-deploy-monitor/app --host 0.0.0.0 --port 8080
|
||||
docker build --build-arg RELEASE_SHA=local \
|
||||
-t ghaymah-api:local q1-deploy-monitor/app
|
||||
docker run --rm -p 8080:8080 ghaymah-api:local
|
||||
curl -f http://localhost:8080/health
|
||||
```
|
||||
|
||||
Then open:
|
||||
|
||||
- `http://localhost:8080/health`
|
||||
- `http://localhost:8080/metrics`
|
||||
- `http://localhost:8080/docs`
|
||||
|
||||
### Q1 monitor and dashboard
|
||||
|
||||
```bash
|
||||
APP_URL=http://localhost:8080 python q1-deploy-monitor/monitor/monitor.py --once
|
||||
python -m http.server 8000 --directory q1-deploy-monitor
|
||||
APP_URL=https://ghaymah-api-615e99f13665.hosted.ghaymah.systems \
|
||||
python q1-deploy-monitor/monitor/monitor.py --once
|
||||
```
|
||||
|
||||
Open `http://localhost:8000/dashboard/index.html`.
|
||||
|
||||
### Q5 collector
|
||||
|
||||
```bash
|
||||
python -m pip install -r q5-mithal-dashboard/collector/requirements.txt
|
||||
python q5-mithal-dashboard/collector/collect.py --once
|
||||
```
|
||||
|
||||
## Work that requires account access
|
||||
|
||||
The following require a credential or capability that is not available in the public documentation:
|
||||
|
||||
1. Add `DOCKERHUB_TOKEN` and any confirmed Ghaymah CI authentication secret to GitHub.
|
||||
2. Confirm the supported API-token authentication mechanism and image field for `gy resource app update`.
|
||||
3. Approve the production GitHub Environment after the image-build job succeeds and capture the approval evidence.
|
||||
4. Confirm monitoring/autoscaling controls, snapshot behavior, and detailed Block Storage performance limits before removing the remaining `VERIFY` markers.
|
||||
|
||||
No credentials should be committed to this repository. Use GitHub Secrets and Ghaymah's secret-management controls for sensitive values.
|
||||
No credentials are stored in the repository. Docker Hub credentials remain in
|
||||
GitHub Secrets and Ghaymah registry authentication remains in the platform
|
||||
integration.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Submission Evidence
|
||||
|
||||
These screenshots are intentionally limited to the evidence needed to review
|
||||
the assessment. Internal prompts, planning notes, transient failures, and
|
||||
duplicate captures are not included.
|
||||
|
||||
| Number | File | Demonstrates |
|
||||
|---:|---|---|
|
||||
| 01 | `01-ghaymah-project.png` | Authenticated Ghaymah project and deployed applications. |
|
||||
| 02 | `02-q1-service.png` | Q1 API application in Ghaymah. |
|
||||
| 03 | `03-q1-health.png` | Live HTTP 200 health response with release identifier. |
|
||||
| 04 | `04-q1-dashboard.png` | Q1 status, latency, request count, uptime, and history. |
|
||||
| 05 | `05-q5-service.png` | Q5 monitoring application in Ghaymah. |
|
||||
| 06 | `06-q5-dashboard.png` | Q5 uptime, latency chart, SSL, DNS/search metrics, and recent checks. |
|
||||
| 07 | `07-production-approval.png` | GitHub `production` Environment required-reviewer protection. |
|
||||
| 08 | `08-pipeline-success.png` | Successful build, staging verification, approval, and production verification. |
|
||||
|
||||
All service URLs and Docker image names are also listed in the repository root
|
||||
[README](../../README.md).
|
||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 58 KiB |
@@ -39,7 +39,9 @@ every request. It is per-process and deliberately resets on restart — a counte
|
||||
that drops to zero in the dashboard is a visible signal that the container was
|
||||
restarted or redeployed.
|
||||
|
||||
Environment variables (all optional): `APP_NAME`, `APP_VERSION`, `PORT` (default `8080`).
|
||||
Environment variables (all optional): `APP_NAME`, `APP_VERSION`, `RELEASE_SHA`,
|
||||
and `PORT` (default `8080`). CI embeds the immutable Git commit in
|
||||
`RELEASE_SHA`, and `/health` exposes it as `release` for deployment verification.
|
||||
|
||||
### Run locally without Docker
|
||||
|
||||
@@ -124,7 +126,7 @@ anonymously from the URL you paste in.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Container Image URL | `docker.io/agamy74/ghaymah-api:8bc563f` |
|
||||
| Container Image URL | `docker.io/agamy74/ghaymah-api:<git-commit-sha>` |
|
||||
| Application Name | `ghaymah-api` |
|
||||
| Port Number | `8080` (must match the `EXPOSE`d port) |
|
||||
| Public Access | **enabled** |
|
||||
@@ -139,7 +141,8 @@ service a public URL.
|
||||
curl -f https://ghaymah-api-615e99f13665.hosted.ghaymah.systems/health
|
||||
```
|
||||
|
||||
Expected: HTTP 200 with `{"status":"ok","uptime_s":...,"timestamp":"..."}`.
|
||||
Expected: HTTP 200 with
|
||||
`{"status":"ok","release":"<git-commit-sha>","uptime_s":...,"timestamp":"..."}`.
|
||||
|
||||
Also open `https://ghaymah-api-615e99f13665.hosted.ghaymah.systems/docs` in a browser for the OpenAPI page,
|
||||
and screenshot both the running service in the Ghaymah dashboard and the `/health`
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
ARG RELEASE_SHA=local
|
||||
|
||||
# curl is required by the HEALTHCHECK below; everything else stays out of the image.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl \
|
||||
@@ -8,6 +10,7 @@ RUN apt-get update \
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
RELEASE_SHA=${RELEASE_SHA} \
|
||||
PORT=8080
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -15,6 +15,7 @@ from fastapi.responses import JSONResponse
|
||||
|
||||
APP_NAME = os.getenv("APP_NAME", "ghaymah-api")
|
||||
APP_VERSION = os.getenv("APP_VERSION", "1.0.0")
|
||||
RELEASE_SHA = os.getenv("RELEASE_SHA", "local")
|
||||
PORT = int(os.getenv("PORT", "8080"))
|
||||
|
||||
# Process start time drives both uptime and the /metrics started_at field.
|
||||
@@ -53,6 +54,7 @@ async def root():
|
||||
return {
|
||||
"service": APP_NAME,
|
||||
"version": APP_VERSION,
|
||||
"release": RELEASE_SHA,
|
||||
"message": "Ghaymah deployment demo API",
|
||||
"endpoints": ["/", "/health", "/metrics", "/docs"],
|
||||
"started_at": STARTED_AT_ISO,
|
||||
@@ -68,6 +70,7 @@ async def health():
|
||||
status_code=200,
|
||||
content={
|
||||
"status": "ok",
|
||||
"release": RELEASE_SHA,
|
||||
"uptime_s": _uptime_s(),
|
||||
"timestamp": _now_iso(),
|
||||
},
|
||||
|
||||
@@ -957,5 +957,481 @@
|
||||
"code": 200,
|
||||
"latency_ms": 707.51,
|
||||
"requests": 268
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:03:51.848034+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 892.26,
|
||||
"requests": 270
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:04:25.528579+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 694.95,
|
||||
"requests": 272
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:04:59.231057+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1861.06,
|
||||
"requests": 274
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:05:30.454738+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 586.48,
|
||||
"requests": 276
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:06:02.324156+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 890.61,
|
||||
"requests": 278
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:06:34.144517+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 905.66,
|
||||
"requests": 280
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:07:05.780266+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 738.98,
|
||||
"requests": 282
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:07:37.395548+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 843.17,
|
||||
"requests": 284
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:08:09.145094+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 843.5,
|
||||
"requests": 286
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:08:42.112423+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1355.9,
|
||||
"requests": 288
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:09:14.075130+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 917.64,
|
||||
"requests": 290
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:09:45.825068+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 982.26,
|
||||
"requests": 292
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:10:17.588615+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 891.01,
|
||||
"requests": 294
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:10:49.297873+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 855.83,
|
||||
"requests": 296
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:11:20.912121+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 775.26,
|
||||
"requests": 298
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:11:52.499884+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 775.21,
|
||||
"requests": 300
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:12:24.514786+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1206.22,
|
||||
"requests": 302
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:12:56.158854+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 897.33,
|
||||
"requests": 304
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:13:27.673469+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 727.96,
|
||||
"requests": 306
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:14:00.527846+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1527.71,
|
||||
"requests": 308
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:14:32.323584+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 880.48,
|
||||
"requests": 310
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:15:03.962668+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 879.14,
|
||||
"requests": 312
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:15:35.833296+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 851.54,
|
||||
"requests": 314
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:16:07.355297+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 760.54,
|
||||
"requests": 316
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:16:39.012633+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 799.64,
|
||||
"requests": 318
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:17:11.074470+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 873.43,
|
||||
"requests": 320
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:17:42.996047+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1053.03,
|
||||
"requests": 322
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:18:14.894655+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1185.62,
|
||||
"requests": 324
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:18:46.652731+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 859.36,
|
||||
"requests": 326
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:19:17.573385+00:00",
|
||||
"status": "down",
|
||||
"code": 502,
|
||||
"latency_ms": 895.37,
|
||||
"requests": null
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:19:49.320997+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 866.28,
|
||||
"requests": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:20:21.060460+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 839.12,
|
||||
"requests": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:20:52.829427+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 889.52,
|
||||
"requests": 6
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:21:24.408306+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 782.05,
|
||||
"requests": 8
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:21:55.914426+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 723.7,
|
||||
"requests": 10
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:22:27.534277+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 799.56,
|
||||
"requests": 12
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:22:59.367798+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 979.2,
|
||||
"requests": 14
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:23:30.996828+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 779.25,
|
||||
"requests": 16
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:24:02.747579+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 880.05,
|
||||
"requests": 18
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:24:34.214686+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 717.03,
|
||||
"requests": 20
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:25:05.757052+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 757.22,
|
||||
"requests": 22
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:25:37.276950+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 733.39,
|
||||
"requests": 24
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:26:09.223657+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1189.45,
|
||||
"requests": 26
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:26:43.045315+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 872.87,
|
||||
"requests": 28
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:27:17.288142+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 3339.91,
|
||||
"requests": 30
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:27:50.892062+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 2525.6,
|
||||
"requests": 32
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:28:22.569143+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 891.36,
|
||||
"requests": 34
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:28:55.514777+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1051.03,
|
||||
"requests": 36
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:29:27.193474+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 857.1,
|
||||
"requests": 38
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:29:59.036359+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 865.17,
|
||||
"requests": 40
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:30:30.544858+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 714.51,
|
||||
"requests": 42
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:31:02.161454+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 782.59,
|
||||
"requests": 44
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:31:35.413556+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 789.57,
|
||||
"requests": 46
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:32:09.118559+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 901.22,
|
||||
"requests": 48
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:32:40.893263+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 900.57,
|
||||
"requests": 50
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:33:12.679890+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 863.33,
|
||||
"requests": 52
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:33:44.501781+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1023.98,
|
||||
"requests": 54
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:34:16.365978+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 750.53,
|
||||
"requests": 56
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:34:48.286153+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 960.3,
|
||||
"requests": 58
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:35:19.863722+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 793.72,
|
||||
"requests": 60
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:35:51.502123+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 768.31,
|
||||
"requests": 63
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:36:23.231024+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 849.43,
|
||||
"requests": 65
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:36:55.246965+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 948.16,
|
||||
"requests": 67
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:37:26.935618+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 905.51,
|
||||
"requests": 69
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:38:00.920175+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 2599.19,
|
||||
"requests": 71
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:38:32.921709+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 895.96,
|
||||
"requests": 73
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:39:05.019900+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 1029.07,
|
||||
"requests": 75
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-26T18:39:39.166081+00:00",
|
||||
"status": "up",
|
||||
"code": 200,
|
||||
"latency_ms": 881.37,
|
||||
"requests": 77
|
||||
}
|
||||
]
|
||||
|
||||
@@ -130,5 +130,4 @@ Because Ghaymah's public documentation does not establish specific monitoring, a
|
||||
|
||||
Operational dashboards should show the current release version alongside memory utilization, limit, restart count, termination reason, health status, latency, and error rate. Alerts should route to the on-call notification channel with the workload, container, release, current memory percentage, restart count, and a link to the OOM runbook.
|
||||
|
||||
<!-- VERIFY: Confirm whether Ghaymah provides native container runtime metrics/events, configurable dashboards, alert rules, and notification-channel integrations. If not, export runtime signals to an external monitoring system and use an external /health probe. -->
|
||||
|
||||
The authenticated application page reviewed for this assessment exposes deployment status and application logs, but no documented configurable memory/restart alert controls were established. The implementation should therefore export container runtime metrics and events to an external monitoring system and retain an external `/health` probe unless Ghaymah support enables equivalent account-level controls.
|
||||
|
||||
@@ -2,137 +2,147 @@
|
||||
|
||||
## Pipeline overview
|
||||
|
||||
The workflow builds the API from `q1-deploy-monitor/app`, publishes two Docker Hub tags, deploys the immutable commit tag to a separate staging application, pauses for manual approval, and then deploys the exact same image to production. Promoting the same immutable `${{ github.sha }}` tag prevents staging and production from running artifacts built from different source.
|
||||
The workflow builds the API from `q1-deploy-monitor/app`, embeds the Git commit
|
||||
SHA in the image, and publishes immutable SHA and `latest` tags to Docker Hub.
|
||||
It then starts that immutable image in an isolated GitHub Actions runner,
|
||||
validates `/health`, pauses at the protected `production` Environment, and
|
||||
verifies that the approved Ghaymah application is serving the same SHA.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Commit to main"] --> B["Build image"]
|
||||
B --> C["Push SHA and latest tags<br/>to Docker Hub"]
|
||||
C --> D["Deploy staging<br/>(automatic)"]
|
||||
D --> E{"Manual approval<br/>GitHub Environment"}
|
||||
E -->|Approved| F["Deploy production"]
|
||||
E -->|Rejected| G["Stop deployment"]
|
||||
C --> D["Ephemeral staging container<br/>automatic"]
|
||||
D --> E["Health and release check"]
|
||||
E --> F{"Production Environment<br/>required reviewer"}
|
||||
F -->|Approved| G["Ghaymah production"]
|
||||
G --> H["Verify /health<br/>and exact release SHA"]
|
||||
F -->|Rejected| I["Stop promotion"]
|
||||
```
|
||||
|
||||
The workflow runs on:
|
||||
The free Ghaymah account used for this assessment permits five total resources.
|
||||
The two required live applications (`ghaymah-api` and `mithal-monitor`) already
|
||||
consume the available allocation together with their supporting resources. A
|
||||
third Ghaymah application was tested and rejected by the platform with the
|
||||
documented account message `Resource limit exceeded ... maximum 5 resources`.
|
||||
Staging is therefore an ephemeral container on the GitHub-hosted runner. It runs
|
||||
the exact image that will be promoted, while production remains the real
|
||||
Ghaymah service.
|
||||
|
||||
- A push to `main`.
|
||||
- A manual `workflow_dispatch` run.
|
||||
The workflow runs on a push to `main` and through `workflow_dispatch`. It uses:
|
||||
|
||||
It uses these GitHub Actions secrets:
|
||||
|
||||
| Secret | Purpose |
|
||||
| Setting | Purpose |
|
||||
|---|---|
|
||||
| `DOCKERHUB_USERNAME` | Docker Hub account/namespace used to authenticate and construct the image name. |
|
||||
| `DOCKERHUB_TOKEN` | Docker Hub access token used to push images. |
|
||||
| `GHAYMAH_API_TOKEN` | Reserved for the deployment adapter if a supported API or CLI authentication flow is confirmed. |
|
||||
|
||||
Optional repository or environment variables `GHAYMAH_STAGING_APP` and `GHAYMAH_PRODUCTION_APP` select the target applications. The workflow defaults conceptually to `myapp-staging` and `myapp-production`.
|
||||
| Secret `DOCKERHUB_USERNAME` | Docker Hub account and image namespace. |
|
||||
| Secret `DOCKERHUB_TOKEN` | Read/write Docker Hub access token. |
|
||||
| Variable `GHAYMAH_STAGING_APP` | Display name for the ephemeral staging service. |
|
||||
| Variable `GHAYMAH_PRODUCTION_APP` | Name of the live Ghaymah application. |
|
||||
| Variable `GHAYMAH_PRODUCTION_URL` | Public base URL used for the production verification. |
|
||||
|
||||
Images are published as:
|
||||
|
||||
```text
|
||||
docker.io/<user>/ghaymah-api:<git-commit-sha>
|
||||
docker.io/<user>/ghaymah-api:latest
|
||||
docker.io/agamy74/ghaymah-api:<git-commit-sha>
|
||||
docker.io/agamy74/ghaymah-api:latest
|
||||
```
|
||||
|
||||
The SHA tag is used for deployment because it is immutable and auditable. `latest` is a convenience tag and should not be the production source of truth.
|
||||
|
||||
Ghaymah's public documentation describes deployment from a container image URL entered in its dashboard: image URL, application name, port, public-access setting, and environment variables are supplied before selecting **Deploy**. A Ghaymah-hosted registry is not assumed. If one becomes available, only the registry login server and image prefix need to change; the build and promotion design remains the same.
|
||||
|
||||
The authenticated dashboard confirms that manual deployment supports a container image URL and an optional **Registry Pull Secret**, and that **External Integrations** currently offers a Docker Hub connection. This validates Docker Hub as the registry used by this workflow; no Ghaymah-hosted registry endpoint or push syntax was exposed in the dashboard reviewed on 2026-07-26.
|
||||
Only the SHA tag is promoted. The Dockerfile receives `RELEASE_SHA` as a build
|
||||
argument and `/health` returns it as `release`. The verification script fails if
|
||||
production is healthy but still serves an older image.
|
||||
|
||||
## Manual approval
|
||||
|
||||
Manual approval is implemented with a GitHub Environment named `production`. The workflow declares:
|
||||
The production gate is implemented through a GitHub Environment named
|
||||
`production`:
|
||||
|
||||
```yaml
|
||||
environment: production
|
||||
```
|
||||
|
||||
Required reviewers are configured in the GitHub repository UI, not in workflow YAML:
|
||||
Required reviewers are configured in **Settings → Environments → production**,
|
||||
not in YAML. This repository has `yassinelagamy` configured as the required
|
||||
reviewer. The workflow pauses after staging and cannot start the production job
|
||||
until that reviewer approves it.
|
||||
|
||||
1. Open **Settings → Environments**.
|
||||
2. Create or select the `production` environment.
|
||||
3. Enable the deployment protection rule for required reviewers.
|
||||
4. Add the people or teams authorized to approve production deployments.
|
||||
5. Store production-scoped secrets or variables in this environment where appropriate.
|
||||
Because Ghaymah CLI `0.0.24` does not document non-interactive token
|
||||
authentication or the external-image update field, promotion currently uses
|
||||
this controlled procedure:
|
||||
|
||||
After staging succeeds, the `deploy-production` job enters a waiting state. The run pauses until an authorized reviewer approves it; rejection prevents the production deployment. The approval protects only production—staging continues to deploy automatically.
|
||||
1. The workflow builds and verifies the immutable image in staging.
|
||||
2. An operator updates the `ghaymah-api` image URL in the authenticated Ghaymah
|
||||
dashboard to the printed SHA tag.
|
||||
3. The required reviewer approves the GitHub `production` Environment.
|
||||
4. The production job calls `/health` and succeeds only when `release` equals
|
||||
the workflow commit SHA.
|
||||
|
||||
This is intentionally a manual promotion with automated verification. It does
|
||||
not claim that an undocumented command deployed the application.
|
||||
|
||||
## Staging vs Production
|
||||
|
||||
Staging and production should be two separate Ghaymah applications, such as `myapp-staging` and `myapp-production`, with independent configuration and environment variables.
|
||||
|
||||
| Area | Staging | Production |
|
||||
|---|---|---|
|
||||
| Purpose | Validate the release in a production-like environment before promotion. | Serve the live customer workload. |
|
||||
| Data | Synthetic, anonymized, or disposable test data. | Real customer/business data governed by retention and privacy controls. |
|
||||
| Scale/replicas | Smaller footprint; enough replicas for functional and targeted load tests. | Sized for peak traffic, resilience, and operational headroom. |
|
||||
| Secrets | Staging-only credentials with limited permissions. | Production-only credentials, tightly scoped and independently rotated. |
|
||||
| Access control | Engineering and QA access; may be restricted from the public internet. | Least-privilege operational access; public access only where the service requires it. |
|
||||
| Alerting thresholds | Useful for validation but may be less sensitive or routed to non-paging channels. | SLO-based thresholds with paging for user-impacting failures. |
|
||||
| Deploy cadence | Automatic after each successful build from `main`. | Only after staging succeeds and a reviewer approves the deployment. |
|
||||
| Who can approve | No approval required for this pipeline. | Reviewers assigned to the GitHub `production` Environment. |
|
||||
| Purpose | Validate the exact candidate image before promotion. | Serve the public workload on Ghaymah. |
|
||||
| Data | Synthetic or disposable test data. | Real application data and traffic. |
|
||||
| Scale/replicas | One short-lived CI container for this free-tier assessment. | Ghaymah instance sized for availability and measured demand. |
|
||||
| Secrets | Only build/test credentials; no production secrets. | Production-only values stored in protected platform or environment settings. |
|
||||
| Access control | Reachable only inside the GitHub Actions runner. | Public endpoint; administrative access restricted to the Ghaymah account. |
|
||||
| Alerting thresholds | Fast feedback on startup, health, and release identity. | SLO-based uptime, latency, memory, restart, and OOM alerts. |
|
||||
| Deploy cadence | Automatic on each push to `main`. | Manual promotion after successful staging. |
|
||||
| Who can approve | No reviewer required. | Required reviewer on the GitHub `production` Environment. |
|
||||
|
||||
Both applications receive the same immutable image tag, while their data, secrets, scale, access rules, and environment variables remain isolated.
|
||||
On a paid allocation, staging should be a second Ghaymah application with its
|
||||
own variables, secrets, capacity, and URL. The image and release verification
|
||||
steps remain unchanged.
|
||||
|
||||
## Ghaymah CLI integration
|
||||
|
||||
### Verified public CLI commands
|
||||
|
||||
Ghaymah now publicly documents the `gy` CLI, including installation, interactive login, project management, Dockerfile-based application initialization and launch, and log retrieval. The currently documented flow is:
|
||||
The verified public CLI installation and inspection commands are:
|
||||
|
||||
```bash
|
||||
# Ghaymah-specific installation command confirmed from the official documentation.
|
||||
curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||
source ~/.bashrc
|
||||
|
||||
# Ghaymah-specific commands confirmed with CLI 0.0.24.
|
||||
gy version
|
||||
gy auth login
|
||||
gy auth login --email '<email>' --password '<password>'
|
||||
gy auth status
|
||||
gy resource project get
|
||||
gy resource app init --project-id <PROJECT_ID> --name <APP_NAME>
|
||||
gy resource app launch [PATH]
|
||||
gy resource app init --project-id '<project-id>' --name '<app-name>'
|
||||
gy resource app launch .
|
||||
gy resource app logs
|
||||
gy resource app update <APP_ID> --set '<CONFIRMED_UPDATE_FIELDS>'
|
||||
gy resource app update '<app-id>' --set '<documented-fields>'
|
||||
```
|
||||
|
||||
`app init` creates `.ghaymah.json`; the documented example includes the application ID/name, project ID, exposed port, public-access configuration, resource tier, and Dockerfile name. `app launch` builds the local Dockerfile and deploys that application.
|
||||
The workflow installs the CLI and runs `gy version` in both deployment jobs so
|
||||
the integration dependency is continuously checked. It does not place a user
|
||||
password in CI. Public CLI help for version `0.0.24` exposes email/password
|
||||
login, but no API-token flag, and its generic `app update --set` help does not
|
||||
identify the field for an externally built container image.
|
||||
|
||||
Official sources:
|
||||
The authenticated dashboard does provide a verified deployment route: enter a
|
||||
container image URL, application name, port, public-access choice, environment
|
||||
variables, and an optional registry pull secret, then deploy or update the
|
||||
application. That dashboard route is used for production promotion until
|
||||
Ghaymah publishes a service-account/token flow and an external-image update
|
||||
schema.
|
||||
|
||||
`scripts/verify_deployment.sh` is the CI boundary after promotion:
|
||||
|
||||
```bash
|
||||
bash scripts/verify_deployment.sh \
|
||||
--app "$GHAYMAH_APP_NAME" \
|
||||
--url "$GHAYMAH_PRODUCTION_URL" \
|
||||
--expected-release "$GITHUB_SHA"
|
||||
```
|
||||
|
||||
It retries `/health`, requires `status=ok`, compares the live `release` to the
|
||||
approved SHA, writes evidence to the GitHub job summary, and exits nonzero for
|
||||
an unavailable or stale deployment.
|
||||
|
||||
Official references:
|
||||
|
||||
- [Ghaymah CLI documentation](https://ghaymah.systems/docs)
|
||||
- [Ghaymah CLI overview](https://ghaymah.systems/cli)
|
||||
|
||||
### CI integration gap
|
||||
|
||||
Direct inspection of CLI version `0.0.24` on 2026-07-26 established that `gy auth login` accepts `--email` and `--password`; it does not expose an API-token flag. It also established that `gy resource app update <APP_ID>` accepts JSON input or dot notation through `--set`.
|
||||
|
||||
The public documentation and CLI help still do **not** specify:
|
||||
|
||||
- A `GHAYMAH_API_TOKEN` login flow suitable for an ephemeral GitHub Actions runner.
|
||||
- The JSON field used to update an existing application to a specific externally built image URL/tag.
|
||||
- A login server and push commands for a Ghaymah-hosted registry; the authenticated dashboard instead exposes Docker Hub integration.
|
||||
|
||||
<!-- VERIFY: Confirm whether Ghaymah supports API-token authentication and identify the external-image field accepted by `gy resource app update`. -->
|
||||
|
||||
Consequently, the workflow safely builds and pushes the immutable Docker Hub image, installs the documented CLI, prints the exact deployment target, and calls `scripts/ghaymah_deploy.sh`. The adapter deliberately exits unsuccessfully after producing a clear deployment handoff; it never reports success for a deployment that did not occur.
|
||||
|
||||
Until the missing CI syntax is confirmed, the image deployment procedure is:
|
||||
|
||||
1. Open the target Ghaymah application in the dashboard.
|
||||
2. Update its container image URL to `docker.io/<user>/ghaymah-api:<git-commit-sha>`.
|
||||
3. Confirm the application name, exposed port, public-access setting, and environment variables.
|
||||
4. Select **Deploy** and validate application health.
|
||||
5. Repeat for production only after the GitHub Environment approval.
|
||||
|
||||
The staging and production workflow steps print the exact SHA-tagged image and target application, then call:
|
||||
|
||||
<!-- VERIFY: Replace this fail-safe adapter with confirmed non-interactive authentication and external-image update syntax. -->
|
||||
|
||||
```bash
|
||||
bash scripts/ghaymah_deploy.sh \
|
||||
--app "$GHAYMAH_APP_NAME" \
|
||||
--image "$DEPLOY_IMAGE"
|
||||
```
|
||||
|
||||
The workflow supplies `GHAYMAH_API_TOKEN` through the environment and never places it in a command-line argument. Once Ghaymah confirms the required syntax, the adapter should authenticate without logging the token, update the application's image URL, wait for rollout completion, validate `/health`, and return a nonzero exit status if deployment or health validation fails.
|
||||
- [Docker login action](https://github.com/docker/login-action)
|
||||
- [Docker build and push action](https://github.com/docker/build-push-action)
|
||||
|
||||
@@ -103,12 +103,10 @@ This design does not clone and concurrently mount one writable volume across rep
|
||||
|
||||
### Backup, performance, and capacity
|
||||
|
||||
Crash-consistent or application-consistent volume snapshots can provide a backup building block, but database-aware backups and restore tests are still required. A snapshot should be coordinated with PostgreSQL or combined with its write-ahead log archive so the recovery point is valid.
|
||||
|
||||
<!-- VERIFY: Confirm that Ghaymah Block Storage supports snapshots, including snapshot consistency behavior, retention, encryption, restore procedure, and cross-region or cross-zone availability. -->
|
||||
Crash-consistent or application-consistent volume snapshots can provide a backup building block, but database-aware backups and restore tests are still required. A snapshot should be coordinated with PostgreSQL or combined with its write-ahead log archive so the recovery point is valid. Snapshot support, retention, encryption, restore behavior, and cross-zone availability were not specified in the dashboard or public documentation reviewed for this assessment; they must be confirmed with Ghaymah before being used as the backup design.
|
||||
|
||||
The authenticated Ghaymah dashboard displays a supported volume-size range of **50 MiB minimum to 10 GiB maximum** and allows a volume to be attached from an application's advanced deployment options. Volume sizing must account for the live dataset, indexes, temporary files, write-ahead logs, maintenance operations, expected growth, and free-space safety margin. Performance planning must account for sustained and burst IOPS, throughput, latency, queue depth, and the read/write mix. Load tests should verify database latency at expected peak traffic rather than selecting capacity from size alone.
|
||||
|
||||
<!-- VERIFY: Confirm resize behavior, supported volume classes, IOPS/throughput limits, attachment limits, access modes, and zone-binding rules before final production sizing. -->
|
||||
The reviewed interfaces also did not publish resize behavior, volume classes, IOPS/throughput guarantees, attachment limits, access modes, or zone-binding rules. Production sizing must treat these as procurement questions for Ghaymah support and validate the answers with load and restore testing.
|
||||
|
||||
Block Storage makes container replacement compatible with durable state, but it does not replace database replication, tested backups, point-in-time recovery, or a documented failover procedure.
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 62 KiB |
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Deployment adapter for the GitHub Actions workflow.
|
||||
#
|
||||
# Ghaymah publicly documents `gy resource app launch` for deploying a
|
||||
# local Dockerfile/.ghaymah.json project. It does not currently document a
|
||||
# non-interactive API-token login command. The current binary exposes
|
||||
# email/password login flags and a generic `app update` command, but its public
|
||||
# help does not identify an external-image field. This adapter therefore
|
||||
# validates and prints the exact deployment handoff, then fails deliberately
|
||||
# instead of reporting a deployment that did not happen.
|
||||
|
||||
app=""
|
||||
image=""
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 --app <application-name> --image <registry/image:tag>" >&2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--app)
|
||||
[[ $# -ge 2 ]] || { usage; exit 64; }
|
||||
app="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image)
|
||||
[[ $# -ge 2 ]] || { usage; exit 64; }
|
||||
image="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$app" && -n "$image" ]] || { usage; exit 64; }
|
||||
|
||||
echo "Ghaymah deployment handoff"
|
||||
echo " Application: $app"
|
||||
echo " Image URL: $image"
|
||||
echo
|
||||
echo "Update the target application's container image URL to the immutable tag"
|
||||
echo "shown above in the Ghaymah dashboard, then validate its health endpoint."
|
||||
echo
|
||||
echo "Automated deployment is intentionally blocked because the public CLI docs"
|
||||
echo "do not specify API-token authentication or the external-image field for"
|
||||
echo "'gy resource app update'. Replace this adapter only with confirmed syntax."
|
||||
|
||||
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
|
||||
{
|
||||
echo "### Ghaymah deployment requires confirmed integration"
|
||||
echo
|
||||
echo "- Application: \`$app\`"
|
||||
echo "- Image: \`$image\`"
|
||||
echo
|
||||
echo "The image was built and pushed, but no deployment was claimed. Confirm"
|
||||
echo "the supported API/CLI command or update this image URL in the dashboard."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
exit 78
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Verify that a deployed service is healthy and exposes the expected immutable
|
||||
# release identifier. This prevents a manually promoted Ghaymah deployment from
|
||||
# being reported as successful when it is still serving an older image.
|
||||
|
||||
app=""
|
||||
url=""
|
||||
expected_release=""
|
||||
attempts="${DEPLOY_CHECK_ATTEMPTS:-30}"
|
||||
interval_s="${DEPLOY_CHECK_INTERVAL_S:-2}"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 --app <name> --url <base-url> --expected-release <git-sha>" >&2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--app)
|
||||
[[ $# -ge 2 ]] || { usage; exit 64; }
|
||||
app="$2"
|
||||
shift 2
|
||||
;;
|
||||
--url)
|
||||
[[ $# -ge 2 ]] || { usage; exit 64; }
|
||||
url="${2%/}"
|
||||
shift 2
|
||||
;;
|
||||
--expected-release)
|
||||
[[ $# -ge 2 ]] || { usage; exit 64; }
|
||||
expected_release="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$app" && -n "$url" && -n "$expected_release" ]] || { usage; exit 64; }
|
||||
[[ "$attempts" =~ ^[1-9][0-9]*$ ]] || { echo "DEPLOY_CHECK_ATTEMPTS must be a positive integer" >&2; exit 64; }
|
||||
[[ "$interval_s" =~ ^[0-9]+$ ]] || { echo "DEPLOY_CHECK_INTERVAL_S must be a non-negative integer" >&2; exit 64; }
|
||||
|
||||
health_url="${url}/health"
|
||||
echo "Verifying ${app} at ${health_url}"
|
||||
|
||||
for ((attempt = 1; attempt <= attempts; attempt++)); do
|
||||
body="$(curl --fail --silent --show-error --max-time 5 "$health_url" 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$body" ]]; then
|
||||
status="$(sed -n 's/.*"status"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<<"$body")"
|
||||
release="$(sed -n 's/.*"release"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<<"$body")"
|
||||
|
||||
if [[ "$status" == "ok" && "$release" == "$expected_release" ]]; then
|
||||
echo "Deployment verified: status=ok release=${release}"
|
||||
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
|
||||
{
|
||||
echo "### Deployment verified"
|
||||
echo
|
||||
echo "- Application: \`${app}\`"
|
||||
echo "- URL: ${url}"
|
||||
echo "- Release: \`${release}\`"
|
||||
} >>"$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Attempt ${attempt}/${attempts}: status=${status:-missing}, release=${release:-missing}"
|
||||
else
|
||||
echo "Attempt ${attempt}/${attempts}: health endpoint unavailable"
|
||||
fi
|
||||
|
||||
((attempt == attempts)) || sleep "$interval_s"
|
||||
done
|
||||
|
||||
echo "Deployment verification failed for ${app}: expected release ${expected_release}" >&2
|
||||
exit 1
|
||||