A lightweight Flask REST API for managing PostgreSQL user accounts across
multiple environments (dev, qa, uat, prod).
Built for teams that provision database roles repeatedly and want a simple,
auditable HTTP interface instead of manual psql commands.
| Feature | Details |
|---|---|
| HTTP Basic Auth | Every /api/* endpoint is protected |
| SQLite inventory | Databases registered by env + name — no static config files |
| Audit log | Every operation is recorded in SQLite |
| Multi-environment | dev / qa / uat / prod with separate hostnames per database |
| Structured JSON | Responses include hostname, database, port, env, status |
| Notification stubs | Placeholders for Webex, Slack, and email hooks |
| Postgres-only | Purpose-built for PostgreSQL / AWS RDS |
pg_user_api/
├── app.py # Flask application — all endpoints
├── auth.py # Basic auth helper (constant-time compare)
├── database.py # SQLite registry + audit log
├── notifications.py # Notification stubs (email / Webex / Slack)
├── seed_db.py # One-time setup: creates DB + sample records
├── requirements.txt
└── README.md
git clone https://github.com/YOUR_USERNAME/pg-user-api.git
cd pg-user-api
pip install -r requirements.txtpython seed_db.pyThis creates pg_registry.db and inserts sample records for dev / qa / uat / prod.
Edit seed_db.py and replace the SAMPLE_RECORDS list with your real PostgreSQL hostnames.
The API has two credential pairs — set them as environment variables before starting:
# Windows PowerShell
$env:PG_API_USER = "pgadmin" # who can call this API
$env:PG_API_PASS = "Ch@ngeMe2024!"
$env:PG_ADMIN_USER = "role_create" # PostgreSQL DBA role that executes DDL
$env:PG_ADMIN_PASS = "your_pg_password"# Linux / macOS
export PG_API_USER="pgadmin"
export PG_API_PASS="Ch@ngeMe2024!"
export PG_ADMIN_USER="role_create"
export PG_ADMIN_PASS="your_pg_password"If you don't set env vars, the defaults coded in app.py are used.
python app.pyThe API listens on http://localhost:5000.
The inventory lives in the db_registry table of pg_registry.db.
| Column | Type | Description |
|---|---|---|
| env | TEXT | dev / qa / uat / prod |
| db_name | TEXT | PostgreSQL database name |
| hostname | TEXT | FQDN or IP of the PostgreSQL host |
| port | INTEGER | PostgreSQL port (default 5432) |
| active | INTEGER | 1 = active, 0 = inactive |
| notes | TEXT | Free-text description |
sqlite3 pg_registry.dbINSERT INTO db_registry (env, db_name, hostname, port, active, notes)
VALUES
('dev', 'myapp_dev', 'pg-dev-01.example.com', 5432, 1, 'Dev main DB'),
('qa', 'myapp_qa', 'pg-qa-01.example.com', 5432, 1, 'QA main DB'),
('uat', 'myapp_uat', 'pg-uat-01.example.com', 5432, 1, 'UAT main DB'),
('prod', 'myapp_prod', 'pg-prod-01.example.com', 5432, 1, 'Production DB');UPDATE db_registry SET active = 0
WHERE env = 'dev' AND db_name = 'myapp_dev';All /api/* endpoints require HTTP Basic Auth.
curl -u pgadmin:Ch@ngeMe2024! http://localhost:5000/api/v1/registryAn unauthenticated request returns 401 Unauthorized.
All endpoints use GET with query parameters.
The DBA credentials are stored server-side — callers only need env and database
plus the user-specific fields shown below.
Every user operation returns:
{
"username": "web01_8080",
"password": "generatedSecurePassword",
"status": "user created",
"hostname": "pg-dev-01.example.com",
"database": "myapp_dev",
"port": "5432",
"env": "dev"
}curl http://localhost:5000/curl -u pgadmin:Ch@ngeMe2024! "http://localhost:5000/api/v1/registry"curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/all?env=dev&database=myapp_dev"Typical use: VM or container service accounts.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/app?env=dev&database=myapp_dev&servername=web01&port=8080"Creates user web01_8080 with connection limit 200.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/app-k8s?env=dev&database=myapp_dev&env_prefix=dv&farmname=gearservice"Creates user dv_gearservice with connection limit 200.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/devqa?env=dev&database=myapp_dev&username=jsmith"Creates user jsmith with connection limit 20.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/devlead?env=qa&database=myapp_qa&username=jdoe"Creates user jdoe with connection limit 20.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/readonly?env=prod&database=myapp_prod&username=analyst01"Creates user analyst01 with connection limit 20.
Creates user with CREATEDB CREATEROLE LOGIN.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/dba?env=dev&database=myapp_dev&username=dba_alice"Generates a new random password and applies it immediately.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/reset?env=dev&database=myapp_dev&username=jsmith"curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/search-path?env=dev&database=myapp_dev&username=jsmith&schema=public"schema defaults to public if not supplied.
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/find?env=dev&database=myapp_dev&username=jsmith"Before running against a real database, follow these steps to verify everything works.
curl http://localhost:5000/Expected response:
{"service": "PostgreSQL User Management API", "version": "2.0.0", ...}curl -u pgadmin:Ch@ngeMe2024! "http://localhost:5000/api/v1/registry"If your env + database combination is not listed, add it first:
sqlite3 pg_registry.dbINSERT INTO db_registry (env, db_name, hostname, port, active, notes)
VALUES ('qa', 'myapp_qa', 'pg-qa-01.example.com', 5432, 1, 'QA main DB');Run this first before any create operation — confirms connectivity and shows what already exists:
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/all?env=qa&database=myapp_qa"If this returns a user list, your DBA credentials and network connectivity are good.
If it errors, check PG_ADMIN_USER / PG_ADMIN_PASS and that the host is reachable.
Create a test user first to confirm the full flow works end to end:
curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/devqa?env=qa&database=myapp_qa&username=test_api_user"Expected response:
{
"username": "test_api_user",
"password": "<generated>",
"status": "user created",
"hostname": "pg-qa-01.example.com",
"database": "myapp_qa",
"port": "5432",
"env": "qa"
}curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/find?env=qa&database=myapp_qa&username=test_api_user"curl -u pgadmin:Ch@ngeMe2024! \
"http://localhost:5000/api/v1/users/reset?env=qa&database=myapp_qa&username=test_api_user"Every operation is recorded in SQLite. Verify your test actions were logged:
sqlite3 pg_registry.db "SELECT * FROM audit_log ORDER BY performed_at DESC LIMIT 10;"| Error | Cause | Fix |
|---|---|---|
401 Unauthorized |
Wrong API username/password | Check PG_API_USER / PG_API_PASS |
No registry entry found |
Database not in SQLite | Add it via seed_db.py or sqlite3 |
password authentication failed |
Wrong DBA credentials | Check PG_ADMIN_USER / PG_ADMIN_PASS |
could not connect to server |
Host unreachable | Check hostname in registry, VPN, firewall |
user already exists |
Username taken | Normal — the API is idempotent, no action taken |
| Endpoint | Required params | Creates |
|---|---|---|
/api/v1/users/all |
env, database |
— lists users |
/api/v1/users/app |
env, database, servername, port |
<servername>_<port> |
/api/v1/users/app-k8s |
env, database, env_prefix, farmname |
<env_prefix>_<farmname> |
/api/v1/users/devqa |
env, database, username |
individual dev/QA user |
/api/v1/users/devlead |
env, database, username |
individual dev-lead user |
/api/v1/users/readonly |
env, database, username |
read-only user |
/api/v1/users/dba |
env, database, username |
DBA user (CREATEDB+CREATEROLE) |
/api/v1/users/reset |
env, database, username |
— resets password |
/api/v1/users/search-path |
env, database, username, schema |
— updates search_path |
/api/v1/users/find |
env, database, username |
— looks up user |
notifications.py contains ready-to-activate stubs. Uncomment the relevant
block and add your token/webhook URL:
# Webex Teams
send_notification(
channel="webex",
recipient="YOUR_WEBEX_ROOM_ID",
message="User {} created on {} ({})".format(username, dbname, env),
)
# Slack
send_notification(
channel="slack",
message="User {} created on {} ({})".format(username, dbname, env),
)
# Email
send_notification(
channel="email",
recipient="dba-team@example.com",
message="User {} created on {} ({})".format(username, dbname, env),
)- Intended for internal / intranet use behind a VPN or firewall.
- The DBA credentials are stored server-side and never appear in API URLs or logs.
- Never commit
pg_registry.dbto a public repository — it contains hostnames. It is already listed in.gitignore. - For internet-facing deployments, upgrade to token-based auth (JWT / API key).
MIT — free to use, modify, and share.