README
Greenlight
Greenlight answers one question: "Is it safe to keep this deploy running?"
You give it a pull request and a target environment. It deploys the change, runs smoke tests, checks your Grafana dashboards for anything scary, and tells you whether things look good or not. If error rates spike or latency blows up after the deploy, Greenlight catches it.
What problem does this solve?
Deploying code to a shared environment (staging, production, etc.) usually means watching dashboards, manually curling endpoints, and hoping nothing broke. Greenlight automates all of that into a single command. It captures metrics before your deploy, deploys, runs tests, captures metrics after, and compares the two. You get a clear pass/fail answer instead of gut feeling.
How it works
Greenlight runs a 7-step pipeline. Each step is a standalone shell script that does one thing and outputs JSON. The orchestrator (bin/run.sh) chains them together and saves everything to disk so you can inspect results or resume if something fails partway through.
Here's what happens when you run it:
1. Resolve the PR → Looks up the pull request on GitHub to find the exact
commit SHA, branch name, and title.
2. Capture baseline → Queries Grafana/Loki to snapshot the current error rates,
latency, and log patterns BEFORE your code is deployed.
3. Wait for CI → Polls your CI system (CircleCI, GitHub Actions, etc.)
until the build finishes and an artifact is ready.
4. Deploy → Deploys the built artifact to the target environment
using your deploy tooling (ArgoCD, kubectl, etc.).
5. Smoke test → Sends HTTP requests to your service's endpoints and
checks that they return the expected status codes.
6. Capture post-deploy → Queries Grafana/Loki again to snapshot metrics AFTER
the deploy and smoke tests have run.
7. Compare → Diffs the baseline and post-deploy snapshots. If error
rates, latency, or new errors exceed your thresholds,
the run fails. Otherwise, you get a green light.
Quick start
# Deploy PR #42 to the ext environment and validate it
bin/run.sh --repo myorg/myservice --pr 42 --env ext
# Deploy to production instead
bin/run.sh --repo myorg/myservice --pr 42 --env prod
# If a run fails partway through, resume it (no need to re-deploy)
bin/run.sh --resume <run_id>
Each run creates a folder under runs/ with all the JSON output from every step. You can look at the results anytime:
# See the overall run status
cat runs/<run_id>/run.json | jq '.status'
# See what the compare step decided
cat runs/<run_id>/steps/07-compare.json | jq '.summary'
Project layout
bin/run.sh The orchestrator — runs all steps in order
steps/ One script per pipeline step
resolve-pr.sh Look up PR metadata via GitHub CLI
loki-query.sh Query Grafana/Loki for metrics and logs
ci-wait.sh Poll CI until the build is done
deploy.sh Deploy the artifact to an environment
smoke.sh Run HTTP test cases against the service
compare.sh Compare before/after metrics, pass or fail
config/
smoke-cases.json Defines which endpoints to hit during smoke tests
thresholds.json Defines how much degradation is acceptable
runs/<run_id>/ Output from each run (one folder per run)
run.json Run metadata and per-step status
steps/*.json Output from each individual step
doc/
spec.md Full system spec with JSON contracts for every step
Configuration
Smoke test cases (config/smoke-cases.json)
This file defines the HTTP requests that the smoke step will send after deploying. Each case specifies a method, path, and the status code you expect back:
{
"cases": [
{
"name": "health-check",
"method": "GET",
"path": "/healthz",
"expected_status": 200
},
{
"name": "get-thing",
"method": "GET",
"path": "/api/v1/thing?id=1",
"expected_status": 200
}
]
}Add more cases to cover your critical endpoints. If any case returns an unexpected status, the smoke step fails.
Thresholds (config/thresholds.json)
This file controls how sensitive the compare step is. It defines the maximum acceptable degradation between the baseline and post-deploy metrics:
{
"max_5xx_rate_increase": 0.005,
"max_p95_latency_increase_pct": 30,
"max_new_error_count": 5
}
max_5xx_rate_increase— If the 5xx error rate goes up by more than 0.5%, the run fails.max_p95_latency_increase_pct— If p95 latency increases by more than 30%, the run fails.max_new_error_count— If more than 5 new errors appear in the logs, the run fails.
Wiring up the steps
The step scripts currently have stub implementations — they output fake JSON so you can see the pipeline work end to end. To make them real, replace the stubs with actual commands:
| Step | What to implement |
|---|---|
resolve-pr.sh | Call gh pr view to fetch PR metadata |
ci-wait.sh | Poll gh or your CI API until the build finishes |
deploy.sh | Run your deploy command (ArgoCD sync, kubectl apply, etc.) |
smoke.sh | Loop through smoke-cases.json and curl each endpoint |
loki-query.sh | Query your Grafana/Loki instance for metrics and logs |
compare.sh | Already computes real deltas — just needs real input data |
doc/spec.md for the exact JSON schema each step should produce.Requirements
bash,jq,uuidgen,date(the orchestrator and steps use these)gh(GitHub CLI) for the resolve-pr and ci-wait stepscurlfor the smoke step- Access to Grafana/Loki for the loki-query step
Resuming failed runs
If a run fails at step 4 (deploy), you don't have to start over. The orchestrator saves progress to run.json as it goes. When you resume, it skips steps that already passed and picks up where it left off:
bin/run.sh --resume 20260207-143022-a1b2c3The environment and all other parameters are restored from the saved run, so you don't need to pass them again.
What's next (Phase 2)
A lightweight Go HTTP server that wraps the pipeline, so you can trigger and monitor runs via an API instead of the command line:
POST /runs Start a new run
GET /runs/{id} Get run status and results
POST /runs/{id}/resume Resume a failed run