# Cheat sheet — CI/CD & Delivery

Dense revision sheet. Workflow syntax + interview one-liners at the bottom.

---

## CI vs CD
- **CI** = merge often; auto **build + test** every change. Catch integration bugs early.
- **CD** = **Delivery** (every green change is *ready* — human clicks deploy) vs **Deployment**
  (releases *automatically*). The gap is the manual gate.
- **Push-based** (this repo) = CI/CD runs `helm upgrade`/`skaffold deploy` to push changes.
  **GitOps** (Argo/Flux) = a controller *pulls* git and reconciles continuously.

## GitHub Actions model
```
Workflow  (.github/workflows/*.yml, runs on an event)
  └─ Job   (runs on one runner; parallel by default; needs: to sequence)
       └─ Step  (run: shell  OR  uses: an action)
```
- **Events:** `push`, `pull_request`, `workflow_dispatch` (manual), `schedule` (cron),
  `workflow_call` (reusable), `repository_dispatch` (one workflow triggers another).
- **Runner:** `runs-on:` — GitHub-hosted or self-hosted (ARC).
- **Action:** JavaScript / Docker / **composite** (bundled steps).

## Reuse: two shapes
| | Reusable workflow | Composite action |
|---|---|---|
| Shape | a **pipeline** (jobs, gates, secrets) | a **step** sequence |
| Invoked | `uses:` a `.yml` with `on: workflow_call` | `uses:` a `.github/actions/…` |
| Repo | **not used** | **76 of them** |
- This repo composes via **composite actions + `repository_dispatch`**, not `workflow_call`.

## This repo's delivery chain (memorise)
```
push to develop
   └─ tbd.create_release_tag        → a release tag
        └─ cron every 2h (Mon–Fri)  → picks latest tag
             └─ tbd.build.yaml       → docker buildx → asia.gcr.io/…/backend:<tag>
                                        → scan → binauthz sign (audit-only)
                  └─ tbd.deploy.yml   → matrix per org → tbd.deploy-backend
                       └─ skaffold deploy -f skaffold.backend.yaml  (= helm upgrade --install)
```
- **A merge does NOT deploy directly.** Staging = cron-auto; **prod = manual `workflow_dispatch`**.
- Env ∈ {staging, uat, preproduction(=dorp), production, trial}; orgs {manabie, jprep, tokyo, …}.
- Deploy gate = the `tbd.privileges-check` action (not GitHub Environments).

## The CI pipeline (`tiered.pre_merge.yml`)
- Trigger: `pull_request: labeled`, gated on **`Ok to test`**.
- **`requirements` router:** a `diff` binary inspects the PR diff → decides *which jobs run* +
  their runner labels (most PRs skip most jobs — monorepo efficiency).
- Jobs: `unit-test` (`go test -count=3 ./internal/... -cover`), `lint` (reviewdog golangci-lint),
  `build-test` (`go build -o backend ./cmd/server/`), `proto-check` (buf lint + breaking),
  `dbschema-test` + `gen-dags-test` (drift guards), `gitleaks-scan`, `conclusion` (required-checks).

## Build & push
- `docker buildx` + `docker/build-push-action@v6`, Dockerfile `developments/release.Dockerfile`.
- Registry `asia.gcr.io/student-coach-e1e95/backend`; **tag = git release tag**.
- `docker manifest inspect` → skip build if the tag already exists (idempotent).
- Cache `type=gha`.

## Runners (ARC)
- Self-hosted via **Actions Runner Controller** (runners = K8s pods, autoscale on queued jobs).
- `dockerdWithinRunnerContainer: true` = **dind** (docker/kind inside the runner).
- `runs-on:` is **dynamic** — `runners.js` maps job → label (e.g. `build-backend → 4-8-large-runner`).

## Security
- **Keyless GCP auth (OIDC/WIF):** job sets `permissions: id-token: write`;
  `google-github-actions/auth@v2` exchanges GitHub's OIDC token for a short-lived GCP credential.
  No static SA keys. Per-purpose bots (build-bot, deploy, integration-test-bot).
- **Binary Authorization:** `gcloud beta container binauthz attestations sign-and-create` (KMS
  attestor). Policy in Terraform. **Currently `DRYRUN_AUDIT_LOG_ONLY` — audited, not blocking.**
- **Drift guards:** `ensure-make-gen-db-schema.bash` / `ensure-make-generate-dags.bash` —
  regenerate, fail on `git diff`. Secret scan: gitleaks.

## Ops levers
- **Concurrency:** CI `cancel-in-progress: true`; deploy `cancel-in-progress: false` (never cancel
  a live deploy).
- **Matrix:** org fan-out, `fail-fast: false`.
- **Cache:** GCS-backed `gcs-cache` (not `actions/cache`).
- **Path filters:** static `paths`/`paths-ignore` + the dynamic `diff` binary.
- **Rollback:** redeploy a previous release tag (`workflow_dispatch`) — git is the source of truth.

---

## Interview one-liners
- **CI vs CD?** *"CI auto-builds and tests every change; CD makes a passing change deployable
  (delivery) or auto-deploys it (deployment). We're push-based — CI/CD runs the deploy, not a
  GitOps controller."*
- **How does your code ship?** *"Merge to develop cuts a release tag; a 2-hour cron builds it and
  auto-deploys to staging; prod is a manual dispatch. A merge doesn't deploy prod directly."*
- **Reusable workflow vs composite action?** *"Workflow = pipeline-shaped (jobs/gates/secrets);
  composite action = step-shaped. We use composite actions + repository_dispatch, not
  workflow_call."*
- **How do you avoid building everything in a monorepo?** *"A `diff` binary in the requirements
  job inspects the PR and turns individual jobs on/off; most PRs skip most jobs."*
- **How does CI authenticate to GCP?** *"OIDC / Workload Identity Federation — GitHub mints a
  short-lived token, GCP exchanges it for a credential. No static keys."*
- **What's binary authorization?** *"GKE admission control that only runs images with valid
  attestations. We sign attestations in the build, but the policy is DRYRUN — audited, not
  blocking yet."*
- **Self-hosted runners?** *"ARC — runners as autoscaling K8s pods, with dind so we can run
  docker/kind for E2E. `runs-on` labels are chosen dynamically per job."*
