Skip to content

Release: fix 500s on /api/auth/user/info/ and /api/search/aimodel/ - #191

Merged
saqibmanan merged 34 commits into
mainfrom
release/dev-to-main
Sep 16, 2026
Merged

saqibmanan merged 34 commits into
mainfrom
release/dev-to-main

Conversation

@saqibmanan

Copy link
Copy Markdown
Contributor

Takes #185 to prod. Both endpoints return 500 on prod today.

What ships (6 files vs main):

  • /api/auth/user/info/ → 401 for anonymous callers instead of 500 (permission_classes = [IsAuthenticated]).
  • /api/search/aimodel/ → search results cached as plain data, so cache.set no longer fails to pickle nested InnerDoc values.
  • tests/test_search_cache_and_user_info.py — each test fails without its fix.
  • django-tests.yml + a Django Tests job in pr-gate.yml; syntax repair in test_grouped_bar_chart.py.

Verified on dev: user/info 401, search/aimodel 200; dev deploy 35066030763 fully green; Django Tests 216 passed.

Merge notes:

  • Built as a merge of dev into main rather than a direct dev→main PR, because two workflow files conflicted. pr-gate.yml takes dev (adds Django Tests). deploy-backend.yml keeps main (retains the CodeQL permissions block prod already deploys with).
  • dataset_data stays reverted on main (01bf459 Revert "Dev"). Git treats that commit as already in main's history, so this merge does not reintroduce it.
  • The Full Suite api-smoke deployed-SHA check will fail: dev runs eaf5a1c, and this PR's head is the merge commit.

Renamed the workflow's display name from "Deploy Backend to Dev EC2" to
"Deploy Backend to EC2". One workflow now handles both: push to dev
deploys to development (unchanged behavior), push to main deploys to
production -- environment and image tag derive from github.ref_name. Same
shape DataSpaceFrontend/ParakhAPI/ParakhAI-frontend's pipelines already
use.

No script changes needed: ci-deploy.sh/ci-rollback.sh/ci-finalize.sh are
already fully environment-agnostic (single docker-compose.yml, no dev/prod
split the way ParakhAPI needed ENV_SUFFIX for). prod-cds also already uses
the same ~/DataExchange/DataExBackend submodule layout as dev-cds
(confirmed via a recent read-only survey), so no host migration script is
needed either -- this is the simplest of the four sibling pipelines.

New repo var PROD_API_BASE_URL (https://api.datakeep.civicdays.in --
confirmed via a prior survey, NOT api.civicdataspace.in as the naming
pattern would suggest) for the smoke-tests job, which is a `uses:` job and
can't see environment-scoped vars.

Needs a `production` GH environment with EC2_HOST/EC2_USERNAME/
EC2_PRIVATE_KEY before the first push to main can deploy -- tracked
separately, not part of this change (touches prod-cds, not mine to set
up). Also flagged inline: prod-cds currently has 12 uncommitted files in
its checkout that an image-based deploy will silently discard -- diff
those before the first real prod run.

actionlint: zero findings.
ci: generalize deploy-backend.yml to cover dev and prod (dev companion to #155)
prod-cds's checkout had 12 uncommitted changes (found while auditing it
before trusting an image-based deploy there, which would have silently
discarded all of them). Sorted into what to keep vs. discard; these 3 are
real, confirmed-needed fixes:

- ALLOWED_HOSTS: add prod-parakh's IP (13.235.189.143). ParakhAPI/
  ParakhAI-frontend (both hosted there) make server-side calls to this
  backend; without this Django 400s them as a disallowed host.
- rate_limit middleware: exempt health checks and keycloak login/refresh
  from rate limiting. Also fixed the exempt path list itself while
  restoring it: the original prod hotfix exempted
  "/api/auth/keycloak/refresh/", but the real route (DataSpace/urls.py ->
  api/urls.py) is "/api/auth/token/refresh/" -- that exemption never
  actually matched anything. Added the /auth/-mounted equivalents too
  (authorization.urls exposes its own keycloak/login + token/refresh under
  /auth/, separate from api.urls's copy under /api/) since it wasn't
  obvious which one is actually in current use -- exempting both is safe,
  missing the real one risks locking users out under load.
- keycloak_utils: log Elasticsearch bulk errors on a user-sync failure,
  not just the top-level exception message.

Deliberately NOT carried forward: a Dockerfile change reordering
`chmod +x docker-entrypoint.sh` earlier in the build. Both the original
and moved position chmod the file before ENTRYPOINT ever uses it, so
there's no behavioral difference either way, and nobody could recall why
it was made -- dropped rather than guess at an undocumented rationale.

Also on the host, confirmed safe to leave un-committed (backups, stray
build/debug artifacts, not source): rate_limit.py.bak.* files, a stray
`db.sqlite3;`, logs/, static/, staticfiles/, render.html, snapshot.html.
opub-kc.civicdatalab.in doesn't match the confirmed current production
Keycloak domain (auth.civicdatalab.in) -- stale across 6 files (SDK
docstrings, README, QUICKSTART, AUTHENTICATION.md, one example script).
Swept all 16 occurrences.

Also fixed AUTHENTICATION.md's troubleshooting section, which hedged
between with/without a /auth path prefix: current Keycloak versions drop
that context path by default (confirmed: auth.civicdatalab.in's real
realm URLs have no /auth segment), so state that plainly instead of
"try both", with a one-line note for anyone still on an older
standalone Keycloak that kept the legacy path.
…-dev

fix: recover 3 real hotfixes made directly on prod-cds (dev companion to #157)
…ocs-dev

docs: fix stale Keycloak domain and /auth prefix (dev companion to #159)
MIDDLEWARE conditionally added debug_toolbar's middleware when DEBUG is
True, but INSTALLED_APPS never registered the app itself -- git history
shows this pairing existed before and the INSTALLED_APPS half got dropped
somewhere along this repo's messy merge/revert history.

Broke every build since (confirmed via 3 failed CI runs today, all at the
same step): the sanity-check step's `manage.py check` runs with DEBUG
defaulting to True (no DEBUG env var set), and any request/check path that
touches debug_toolbar's models crashes with "Model class
debug_toolbar.models.HistoryEntry doesn't declare an explicit app_label
and isn't in an application in INSTALLED_APPS" -- the middleware imports
the app's models without the app being registered.
…-apps-dev

fix: register debug_toolbar in INSTALLED_APPS (dev companion to #161)
…ners

Blocked the first real prod deploy attempt today: `$COMPOSE up -d
backend_db elasticsearch redis` failed with "Conflict. The container name
'/DataExBackendElastic' is already in use" -- confirmed live via `docker
ps` compose-project labels that on prod-cds these three are long-lived
containers (DataExBackendDb up 2 months, DataExBackendElastic up 6 months,
DataExBackendRedis up 14 months) owned by the `dataexchange` project (the
top-level superproject), not this pipeline's own `dataexbackend` project.
Docker container names are host-global, not project-scoped, so compose
doesn't recognize an externally-owned same-named container as already
satisfying the service and tries (and fails) to create a new one.

The script aborted immediately on this (set -euo pipefail) before ever
touching the running `backend` app container -- confirmed live, prod
stayed healthy and untouched throughout.

Fix: check each dependency container by name first, only run `up -d` for
whichever is genuinely absent (a fresh host with nothing running yet).
Verified live against the real prod-cds container state (with sudo,
matching how the script actually runs there): correctly detects all three
as already running and would skip the `up -d` call entirely.
…ntainer-ownership-dev

fix: ci-deploy.sh dependency container ownership (dev companion to #163)
Blocked the DataSpaceBackend production deploy: the `release` step's
migrate command failed with `could not translate host name "backend_db"
to address: Temporary failure in name resolution`.

Root cause: this compose file's `backend_db`/`elasticsearch`/`redis`
services are, on both dev-cds and prod-cds, deliberately never started by
this pipeline (ci-deploy.sh's earlier fix, PR #163, correctly skips them
since same-named containers already exist under the separate `dataexchange`
superproject). Those real containers live on `dataexchange_default`, a
network this project never declared -- `backend`/`release` only ever
joined the implicit default network for this project
(`dataexbackend_default` on the real hosts), which has no route to them.

The currently-running backend container only works today because of an
undocumented `docker network connect dataexchange_default DataSpace` done
by hand on the host at some point -- confirmed live via `docker inspect`.
That connection is not captured anywhere in code, doesn't apply to the
`release` container (a fresh container per run), and would NOT survive a
real `--force-recreate` of `backend` either.

Fix: declare `dataexchange_default` as an external network and attach both
`backend` and `release` to it alongside the project's own default network
-- covers both a genuinely fresh host (where this file's own
backend_db/elasticsearch/redis come up on `default`) and dev-cds/prod-cds
(where the real ones live on dataexchange_default). Verified both networks
exist on both real hosts via `docker network ls`, and that the rendered
config (`docker compose config` / `--profile release config`) resolves
both services onto both networks correctly.

Caveat, not addressed here: `external: true` means `docker compose up`
would fail outright on a machine that has neither network (e.g. a fresh
local dev setup never connected to the DataExchange superproject) --
`docker compose config` itself still validates fine without it existing,
only `up` needs it. Not fixing speculatively since both confirmed-real
deploy targets (dev-cds, prod-cds) already have it.
…ent-dev

fix: backend network attachment (dev companion to #165)
Blocked the production deploy's release step: `search_index --populate`
crashed with `relation "publication" does not exist`. Root cause traced
much further than expected.

.gitignore had `api/migrations/*` and `authorization/migrations/*` --
every migration generated for either app since whenever that was added has
been silently invisible to git, no matter how many times someone ran
`git add`. This repo's `api/migrations/` only ever had `0001_initial.py`
committed (likely force-added once, or predates the ignore rule);
`authorization/migrations/` had nothing at all, not even `__init__.py`.

Confirmed via git log this wasn't always broken: `30480c6 "reset
migrations"` deliberately squashed history down to one clean
`0001_initial.py` at some point (a legitimate, intentional cleanup,
probably around a Postgres migration). But nothing was ever committed
again afterward -- every migration generated since (via normal
`makemigrations` + `git add`, silently swallowed by the ignore rule) only
ever existed on whichever host it was generated on. dev-cds's live
checkout had the full, real, working set: 46 files for `api`
(0002-0047, ending in the Publication/ResourceType migration this deploy
needed) and all 5 for `authorization` (0001-0004 + __init__.py, defining
`authorization.User` itself -- AUTH_USER_MODEL).

Pulled all 51 files directly from dev-cds (read-only), verified:
- Every file compiles clean (`py_compile`).
- The full api dependency chain is linear and unbroken, 0001 -> 0047, no
  gaps, no branches.
- authorization's 4 files chain cleanly from 0001, which itself correctly
  depends on api/0023 (a real cross-app FK relationship) -- confirms the
  two apps' histories are genuinely interleaved and consistent, not
  independently drifted.
- No overlap with the deliberately-excluded fetch_resource_data feature
  from the earlier dev/main reconciliation (PR #153) -- the two incidental
  substring matches found were unrelated migration filenames.

Removed both ignore rules. Django migrations must always be committed;
there is no legitimate reason to exclude them.

This also means any genuinely fresh database (a new contributor's local
setup, a CI job with --create-db, a disaster-recovery rebuild) has been
silently unable to reach current schema this whole time -- would have
stopped dead at whatever 0001_initial.py defines. That risk is now closed.
…ration-dev

fix: commit missing migrations (dev companion to #167)
The Smoke Tests job passed the repo-level HOME_URL_DEV (the dev site) on
main too, so every prod deploy's consumer and provider suites tested dev:
prod's nginx logged 0 test-browser requests during a prod run's smoke
window, dev's logged 8840. Prod rollbacks were being triggered by dev
timeouts, and prod's UI was never actually tested.

On main, pass the prod URL and set skip_provider, since provider-smoke
creates content with the test account.
…-dev

fix: point prod smoke tests at prod and skip provider-smoke there
dev: smoke first; functional runs only if smoke passes; either failing
rolls back, both passing finalizes. main: only the readonly allowlist
runs against prod (provider never), replacing skip_provider. Finalize
uses !failure() because functional-tests is skipped on main by design.
Runs the full suite (smoke, functional, regression) against dev on every
PR into main. deployed_sha is the PR head, so the gate fails unless dev
is running exactly the code being merged.
Provider functional tests fail at shifting points against dev (autosave
races, slow create flows), so gating on them would roll back dev deploys
spuriously. The gating functional stage and the PR gate now skip
provider; separate provider jobs run with only_provider and report,
without being a dependency of rollback/promote or a required check.
feat: deploy gating model — two-stage dev, readonly prod, PR gate
Finalize and rollback did not list the report-only provider job in needs,
so a deploy asked for finalize approval while provider tests were still
running. Both now wait for it, and condition on the gating jobs' results
explicitly instead of failure()/!failure(), which would count the
report-only job's result.
…tests-dev

fix: wait for every test before finalizing or rolling back
org_add_permission needs a Keycloak token to check canAdd. A called
workflow only sees secrets its caller passes, and the report-only job
passed none, so every org-create test skipped on both workers -- the
provider report looked green while never exercising org flows.
…provider-report

Mirrors main: contents/pull-requests read (the CodeQL finding), and
KEYCLOAK_CLIENT_SECRET for provider-report's org-create tests.
…cret-dev

fix: give report-only provider jobs the Keycloak secret
UserInfoView declared no permission_classes, so the project-wide AllowAny
default let anonymous requests reach the handler, where reading .email off
AnonymousUser raised AttributeError and returned 500 instead of 401.
cache.set pickles the value. Serialized hits keep AttrList/AttrDict/InnerDoc
values for nested fields, and those classes are rebuilt per document type, so
pickling raised "it's not the same object as elasticsearch_dsl.document
.InnerDoc" and the except clause turned it into a 500 on /api/search/aimodel/.
…ch-cache

fix: 500s on /api/auth/user/info/ and /api/search/aimodel/
options was passed twice and JS true was used instead of True, in five
places, so this file never ran - and it took the whole suite down with it.
The SDK workflows run a few tests with -p no:django, so views, schema and
permissions had no CI coverage at all. Known-stale chart and metadata tests
are excluded, tracked in #189.
…ch-cache

fix: 500s on /api/auth/user/info/ and /api/search/aimodel/, plus Django tests in CI
Ships the /api/auth/user/info/ and /api/search/aimodel/ 500 fixes, their tests,
and the Django Tests CI job.

Conflicts: pr-gate.yml takes dev (adds the Django Tests job);
deploy-backend.yml keeps main (retains the CodeQL permissions block).
dataset_data stays reverted on main (01bf459); this merge does not undo that.
Comment thread .github/workflows/django-tests.yml Fixed
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ntain permissions'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@saqibmanan
saqibmanan merged commit 5c829b8 into main Sep 16, 2026
10 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants