Promote checks Dev to Prod
Goal
Move quality checks from one Qualytics environment to another, with Git as the system of record. Author and review checks in Dev, commit the YAML to a Git repo, and apply them to Prod through a controlled, repeatable process. The same flow scales to Test, Staging, or any other environment.
Permissions
| Step | Endpoint | Role | Team permission |
|---|---|---|---|
| Export from Dev | GET /api/quality-checks (paginated) |
Member |
Reporter on the source datastore's team |
| Read containers (for name resolution) | GET /api/containers |
Member |
Reporter |
| Import to Prod (create) | POST /api/quality-checks |
Member |
Author (or Drafter for Draft status) |
| Import to Prod (update) | PUT /api/quality-checks/{id} |
Member |
Author |
Use a separate token per environment
Don't reuse a Dev token in Prod. Each environment should have its own service account with the minimum permissions it needs.
Prerequisites
- The CLI is installed and authenticated for both environments (or you swap
QUALYTICS_URLandQUALYTICS_TOKENbetween steps). - The target datastore in Prod has been synced. Container names referenced in the YAML must resolve in Prod.
- A Git repository to hold the exported YAML (this is what makes the workflow auditable).
CLI workflow
graph LR
Dev[(Dev datastore)] -->|export| FS[checks/ folder]
FS -->|git add + commit| Repo[(Git repo)]
Repo -->|git pull| CI[CI / local]
CI -->|import --dry-run| Preview[Preview diff]
Preview --> Apply[import without --dry-run]
Apply --> Prod[(Prod datastore)]
1. Export from Dev
export QUALYTICS_URL=https://dev.qualytics.io
export QUALYTICS_TOKEN=$DEV_TOKEN
qualytics checks export --datastore-id 1 --output ./checks/
The output directory is one YAML file per check, organized by container:
checks/
orders/
isNotNull_customer_id.yaml
satisfiesExpression_total_positive.yaml
customers/
isUnique_email.yaml
2. Commit and review
A reviewer can see exactly which checks changed in the PR diff.
3. Preview import to Prod
export QUALYTICS_URL=https://prod.qualytics.io
export QUALYTICS_TOKEN=$PROD_TOKEN
qualytics checks import --datastore-id 99 --input ./checks/ --dry-run
Loaded 15 check definitions from ./checks/
[DRY RUN] Importing to datastore 99...
Import Summary
┏━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
┃ Datastore ID ┃ Created ┃ Updated ┃ Failed ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
│ 99 │ 10 │ 5 │ 0 │
└──────────────┴─────────┴─────────┴────────┘
4. Apply
5. Promote to multiple datastores in one shot
qualytics checks import \
--datastore-id 99 \
--datastore-id 100 \
--datastore-id 101 \
--input ./checks/
Behind the scenes
| CLI step | Method | Path | Notes |
|---|---|---|---|
checks export (list) |
GET | /api/quality-checks?datastore_id={id} |
Paginated. The CLI walks all pages. |
checks export (file write) |
(local) | — | One YAML per check, with _qualytics_check_uid for stable identity. |
checks import (resolve containers) |
GET | /api/containers?datastore_id={id} |
Maps container names to IDs in the target. |
checks import (existing checks) |
GET | /api/quality-checks?datastore_id={id} |
Used to match by _qualytics_check_uid. |
checks import (create new) |
POST | /api/quality-checks |
Per check that didn't exist in target. |
checks import (update existing) |
PUT | /api/quality-checks/{id} |
Per check matched on UID. |
Match-by-UID is what makes the import idempotent. Re-running the same import is always safe.
Python equivalent
The promote workflow has too many moving parts (paginated export, file layout, idempotent matching) to replicate fully in a few lines. For automation that's already in Python, the simplest pattern is to subprocess the CLI:
import os
import subprocess
env = {**os.environ, "QUALYTICS_URL": "https://dev.qualytics.io",
"QUALYTICS_TOKEN": os.environ["DEV_TOKEN"]}
subprocess.run(
["qualytics", "checks", "export", "--datastore-id", "1", "--output", "./checks/"],
env=env, check=True,
)
env = {**os.environ, "QUALYTICS_URL": "https://prod.qualytics.io",
"QUALYTICS_TOKEN": os.environ["PROD_TOKEN"]}
subprocess.run(
["qualytics", "checks", "import", "--datastore-id", "99", "--input", "./checks/", "--dry-run"],
env=env, check=True,
)
subprocess.run(
["qualytics", "checks", "import", "--datastore-id", "99", "--input", "./checks/"],
env=env, check=True,
)
If you genuinely need pure-Python, the rough sketch is: paginate GET /api/quality-checks, write per-check YAML with stable UIDs, then for each YAML in the target environment, look up by UID via GET /api/quality-checks?_qualytics_check_uid={uid} and PUT or POST accordingly.
Variations and advanced usage
Filter the export
Promote only a subset:
# Active checks for two specific containers, tagged production
qualytics checks export \
--datastore-id 1 \
--containers "10,20" \
--tags "production" \
--status Active \
--output ./checks/
Dry-run gate in CI
Make dry-run the PR check, and the real apply the merge step. See GitHub Actions pipelines.
Promote full configuration, not just checks
If connections, datastores, computed containers, and computed fields need to move too, use config export / config import instead. See Export and import full configuration.
Roll back
Roll back is git revert on the YAML, then re-import. Because import is upsert, the previous values are restored from the file.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Container 'X' not found in target datastore |
Target hasn't been synced, or table doesn't exist there yet | qualytics operations sync --datastore-id 99 first. |
| Some checks fail to update with 403 | Token has Drafter permission but check status is Active |
Re-import with Author permission, or change status to Draft in the YAML. |
| Drift after import: checks have different IDs | That's expected; IDs differ per environment. UIDs are what matter. | Confirm with additional_metadata._qualytics_check_uid matches in source and target YAML. |
| Many "Updated" entries on a fresh Prod | Some checks already existed in Prod from previous experiments | Decide whether to keep or replace; a clean dry-run should show this clearly. |
| Re-import shows changes when nothing was edited | A timestamp or auto-generated field is being persisted | File a bug with the sample diff; export should be deterministic. |
Related
- Quality Checks command reference
- Bulk-create quality checks: the precursor before there's anything to promote.
- Export and import full configuration: for promoting more than just checks.
- GitHub Actions pipelines: automate this whole flow.