# Sample App — Tiny URL Shortener

This directory hosts the **canonical sample app** built across the
*Cloud Infrastructure & DevOps* course. Two Python services:

- `api/` — FastAPI URL-shortener (POST `/shorten`, GET `/{code}` → 302).
- `worker/` — expiry sweeper that deletes URL records past their TTL.

The app is intentionally small. The lesson is always the **platform
work around it** — Dockerfiles, compose, CI, Terraform, Kubernetes,
Helm, ingress, observability, autoscaling, GitOps — not the app code.

## Lesson → expected state map

| Lesson | What lands in `sample-app/` |
|---|---|
| **01** | First `Dockerfile` for `api/`; `docker build` + `docker run` work locally. |
| **02** | Multi-stage `Dockerfile` for `api/` (final image < 100 MB); same shape for `worker/`. |
| **03** | `docker-compose.yml` runs api + worker + Postgres + Redis side by side. |
| 04 | `.github/workflows/ci.yml` — build, test, lint on every push. |
| 05 | Trivy scan + cosign signing job in the CI pipeline. |
| 06 | Push to GHCR / ECR; deploy step gated by environment secret. |
| 07 | Cloud VM with docker-compose + Caddy in front; TLS in 30 seconds. |
| 08 | `terraform/` module replaces the by-hand VM; S3+DynamoDB remote state. |
| 09 | Raw K8s YAML deploys both services to `kind`. |
| 10 | `helm/` chart replaces the raw YAML; values per env (dev/stage/prod). |
| 11 | Ingress + cert-manager + ExternalDNS; real domain over Let's Encrypt. |
| 12 | Prometheus + Grafana + Loki; four golden signals dashboard. |
| 13 | HPA on the API; PDBs; topology spread; blue/green via Helm value flips. |
| 14 | ArgoCD App-of-Apps; sync waves; drift detection. |

## Layout (after Plan A)

```
sample-app/
  README.md          ← this file
  api/
    README.md
    app.py           ← FastAPI URL-shortener (skeleton)
    requirements.txt
  worker/
    README.md
    worker.py        ← expiry sweeper (skeleton)
    requirements.txt
```

Later plans add `helm/`, `terraform/`, `.github/workflows/`, Dockerfiles,
and a top-level `docker-compose.yml`.

## Running locally (Plan A state)

```bash
# In one terminal — API
cd cloud-devops/sample-app/api
pip install -r requirements.txt
uvicorn app:app --reload --port 8000

# In another terminal — worker (one-shot sweep)
cd cloud-devops/sample-app/worker
pip install -r requirements.txt
python worker.py --once
```

The Lesson 01 lab is the first time the reader replaces `python ...`
with `docker build && docker run`.

## End of Lesson 07 — Single-VM deploy

Added in Lesson 07 (`cloud-devops/lesson07-vm-deploy/`):

- `docker-compose.prod.yml` — production-shape compose file that **pulls** the
  CI-built `ghcr.io/<owner>/url-shortener-api:latest` and the worker tag, runs
  Caddy as the TLS-terminating reverse proxy in front, and stands up local
  Postgres + Redis sidecars. No build context, no host volumes for code, only
  `.env`-driven secrets.
- `caddy/Caddyfile` — Caddy v2 config that fronts the API at `{$DOMAIN}` and
  obtains a Let's Encrypt certificate automatically on first run.

Run on a t3.micro (or equivalent) with:

```bash
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
```

The full step-by-step provision-and-deploy walkthrough is in
`cloud-devops/lesson07-vm-deploy/lab_guide.html`.

## End of Lesson 08 — Terraform-managed VM with remote state

Added in Lesson 08 (`cloud-devops/lesson08-terraform-basics/`):

- `terraform/` — main module that provisions the t3.micro VM, security group,
  Elastic IP, and runs the L07 compose stack via `user_data.sh`. The
  `backend "s3" { ... }` block is committed COMMENTED OUT — Lab 08 Step 6
  walks the reader through uncommenting it and running
  `terraform init -migrate-state` to push the state to S3.
- `terraform/backend/` — bootstrap module (local state) that creates the S3
  bucket + DynamoDB lock table the main module's backend uses.
- `terraform/user_data.sh` — bash script run on VM first boot. Installs
  Docker, writes the compose file + Caddyfile, generates random passwords
  into `.env`, brings the stack up.

Deploy flow (full walkthrough in the lab guide):

```bash
# One-time, per environment:
cd cloud-devops/sample-app/terraform/backend
terraform init && terraform apply -auto-approve

cd ..
# Edit versions.tf to uncomment the backend "s3" block, then:
terraform init -migrate-state
terraform apply
```

## Local Kubernetes via Helm (Module 4 / Lesson 10)

Added in Lesson 10 (`cloud-devops/lesson10-helm-chart/`):

- `helm/` — Helm chart that packages the API + worker + optional in-cluster
  Postgres. Replaces the raw `k8s/` manifests from Lesson 09.
- `helm/values.yaml` — canonical defaults.
- `helm/values-dev.yaml`, `helm/values-stage.yaml`, `helm/values-prod.yaml` — per-env overrides.
- `.github/workflows/chart-testing.yml` — `ct lint` + `ct install` on PRs touching `helm/`.

```bash
kind create cluster --name shortener
helm upgrade --install shortener sample-app/helm \
  -f sample-app/helm/values-dev.yaml \
  --namespace shortener --create-namespace --wait
kubectl -n shortener port-forward svc/shortener-api 8080:80
```

Per-env values files: `values-dev.yaml`, `values-stage.yaml`, `values-prod.yaml`.

### Public endpoint with Ingress + cert-manager + ExternalDNS (Module 4 / Lesson 11)

```bash
# 1. Ingress controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.publishService.enabled=true

# 2. cert-manager
helm repo add jetstack https://charts.jetstack.io
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace --set crds.enabled=true

# 3. Shortener with ingress on (uses Let's Encrypt staging)
helm upgrade --install shortener sample-app/helm \
  -f sample-app/helm/values-dev.yaml \
  -f sample-app/helm/values-l11.yaml \
  --namespace shortener --create-namespace --wait

# 4. (Cloud only) ExternalDNS
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns
helm upgrade --install external-dns external-dns/external-dns \
  --namespace external-dns --create-namespace \
  -f sample-app/helm/externaldns-values.yaml
```
