Back to Greenlight

README

<p align="center"> <img src="doc/logo.png" alt="Greenlight" width="200"/> </p>

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:

shell
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

bash
# 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:

bash
# 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

shell
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:

json
{
  "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:

json
{
  "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.
Tune these to match your service's tolerance for change.

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:

StepWhat to implement
resolve-pr.shCall gh pr view to fetch PR metadata
ci-wait.shPoll gh or your CI API until the build finishes
deploy.shRun your deploy command (ArgoCD sync, kubectl apply, etc.)
smoke.shLoop through smoke-cases.json and curl each endpoint
loki-query.shQuery your Grafana/Loki instance for metrics and logs
compare.shAlready computes real deltas — just needs real input data
Each script follows the same contract: it takes CLI flags, writes JSON to stdout, and exits 0 on success. Check 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 steps
  • curl for 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:

bash
bin/run.sh --resume 20260207-143022-a1b2c3

The 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:

shell
POST /runs              Start a new run
GET  /runs/{id}         Get run status and results
POST /runs/{id}/resume  Resume a failed run

© 2026 Jonathan Leahy · v1.0.1-8-g3ed7c56