Skip to content

Latest commit

 

History

History
246 lines (193 loc) · 10.1 KB

File metadata and controls

246 lines (193 loc) · 10.1 KB

ReleaseGateStack

The live orchestration layer of ReleaseGate. It detects a container-image update, stages the upgrade against the previous version, collects evidence, asks ReleaseGateCore for a promote or hold decision, and tears the staging environment down.

Production is deliberately outside this trust boundary: a promote decision is sent as an HMAC-signed webhook to an operator-controlled deployer, and the stack never mutates production itself.

Current scope

The v1 workflow supports Diun image-update detection; a Postgres-backed application and deployment-profile registry; isolated Dokploy staging projects; baseline-to-candidate, upgrade-in-place health validation; GitHub release-note and OSV evidence collection; a single-call Core decision with conservative hold fallback; confidence gating and optional Windmill approval suspension; signed promotion-intent webhooks; and teardown, run serialization, and an S3-backed benchmark capture queue.

Golden-snapshot seeding, browser end-to-end checks, production mutation, post-deployment monitoring, and automatic rollback are not part of v1.

ReleaseGateStack/
├── releasegate_dokploy/       # installable Dokploy client and CLI
├── stack/
│   ├── core/                  # Windmill and Diun Compose definitions
│   ├── webhook_receiver_example.py
│   └── windmill/              # generated flow/app plus source scripts
├── tests/                     # network-free unit tests
└── .env.example

Generated Windmill JSON is committed because push.py consumes it. gen_flow.py, capture/gen_capture.py, and gen_app.py are its source; CI regenerates them and fails on drift.

Install the Dokploy client

Python 3.10+ (3.11–3.13 alongside the Bench or Core packages).

cd ReleaseGateStack
python -m venv .venv && . .venv/bin/activate
python -m pip install -e '.[dev,logs]'
cp .env.example .env

set -a; . ./.env; set +a
releasegate-dokploy projects
releasegate-dokploy stage --app grafana --tag 12.4.4 \
  --compose @grafana.compose.yaml --env @grafana.env
releasegate-dokploy logs-deploy <compose-id> --idle-timeout 30
releasegate-dokploy teardown <staging-project-id>

teardown verifies the remote project name starts with staging- before deleting that project's Compose stacks and project record. Each automatically named staging project and Compose app carries a random ownership token, so a duplicate app/tag run does not share cleanup targets. If Compose deletion fails, teardown raises and retains the project record as a manual recovery target. The generic get/post commands expose the wider Dokploy API and are privileged administrative operations.

from releasegate_dokploy import DokployClient
from releasegate_dokploy.staging import create_staging_compose, teardown_staging

with DokployClient() as client:
    handle = create_staging_compose(
        client, app="grafana", tag="12.4.4",
        compose_file="services:\n  app:\n    image: grafana/grafana:12.4.4\n",
    )
    # Run staging checks and pass the evidence to ReleaseGateCore.
    teardown_staging(client, handle)

Deploy the control plane

stack/core/ is a reference deployment, not an internet-ready production configuration: it exposes Windmill directly over HTTP and mounts the Docker socket into Diun. Put the services on a private network, terminate TLS in front of Windmill, restrict Dokploy credentials, and review the Docker socket risk first.

PG_PASSWORD=test DOKPLOY_URL=https://dokploy.example.com DOKPLOY_API_KEY=test \
  docker compose -f stack/core/windmill.compose.yaml config --quiet

DIUN_WEBHOOK_ENDPOINT=https://windmill.example.com/api/w/releasegate/jobs/run/f/f/releasegate/closed_loop \
  docker compose -f stack/core/diun.compose.yaml config --quiet

stack/core/deploy_core.py --help documents the bootstrap arguments.

Windmill deployment

stack/windmill/ contains the scripts, flow, and dashboard for the v1 staging-only loop plus the Bench staging-capture queue.

wmill_mcp.py       minimal JSON-RPC client for a workspace MCP endpoint
push.py            publish config, libraries, steps, flows, and app
run_wait.py        preview a script or poll a path-only flow run
gen_flow.py        generate flow/closed_loop.json
gen_app.py         generate app/ops_dashboard.json
seed_registry.py   generate registry_seed.json from ReleaseGateBench/apps
lib/               Dokploy, decision, registry, and S3 libraries
steps/             staging-only closed-loop steps
capture/           S3-backed benchmark capture flow and steps
apps/s3.compose.yaml   optional VersityGW S3 reference service

lib/sync_decide.py is a development tool and is not pushed. It vendors the dependency-free prompt/parser from ReleaseGateCore so the live workflow and the benchmarked core share the same logic.

Prerequisites

Provision before pushing: a Windmill workspace named releasegate; a scoped workspace MCP token; a Postgres database and Windmill postgresql resource at f/releasegate/registry_db; a Dokploy URL and scoped API key; optionally S3-compatible storage for the capture queue; optionally an LLM key and a signed promotion-webhook receiver.

The reference control-plane Compose file includes Windmill's own Postgres, but creating the separate registry database and Windmill resource is an operator action.

Push workspace objects

Generate and verify the four artifacts first (see Development and validation), then:

cd ReleaseGateStack
set -a; . ./.env; set +a
python stack/windmill/push.py all   # config | libs | steps | flow | capture | app | all

WMILL_MCP_URL must be the full workspace MCP endpoint, including its token. push.py config updates a variable only when its environment variable is explicitly present; it does not blank existing secrets. Workspace paths involved: variables under f/releasegate/, the operator-provisioned resource f/releasegate/registry_db, libraries under f/releasegate/lib/, steps under f/releasegate/steps/, flows f/releasegate/closed_loop and f/rg_staging/capture, and dashboard f/releasegate/ops_dashboard.

Seed the registry

registry_seed.json holds six application fixtures and their deployment profiles:

python stack/windmill/run_wait.py preview lib/registry.py \
  "{\"rows_json\": $(jq -Rs . < stack/windmill/registry_seed.json)}"

The seed sets each profile's baseline to the image tag in its benchmark Compose fixture. Operators must update prod_version and prod_digest to match their real production inventory before enabling autonomous detection.

Runtime behavior

detect → inventory → evidence
  → for each profile:
      provision baseline → upgrade in place → decide → gate
      → optional approval suspension → signed intent webhook → teardown → log

No configured LLM key produces a conservative hold (absent and empty are treated alike). A promote intent is dispatched only when both webhook URL and HMAC secret are configured. A 2xx response acknowledges the intent; it is not proof that deployment completed, and the flow does not advance the registry baseline on acknowledgment — the external deployer or an operator must reconcile that profile's prod_version and prod_digest.

The capture flow reads an S3 batch, processes items serially, persists each staging result, and writes a batch index. It shares libraries with the live flow but does not mutate production.

Operational cautions

  • The MCP URL contains a secret token. Do not print, commit, or log it.
  • Windmill previews do not reproduce every dependency-lock behavior of a pushed flow. Run the real flow before relying on live automation.
  • The failure handler never deletes projects by shared name prefix. If an iteration fails before teardown it reports that cleanup is required; inspect that run, identify its exact staging project ID, and run the guarded releasegate-dokploy teardown <project-id>.
  • runFlowByPath on the workspace MCP is path-only. Use a Windmill worker call or a scoped webhook when flow inputs are required.
  • Benchmark fixture credentials are deliberately weak and suitable only for isolated staging.

Signed promotion webhook

For a promote decision, 08_dispatch_promote.py sends compact JSON with X-ReleaseGate-Signature: sha256=<HMAC-SHA256>, X-ReleaseGate-Timestamp, and X-ReleaseGate-Delivery. Both PROMOTE_WEBHOOK_URL and a non-empty PROMOTE_WEBHOOK_SECRET are required.

The reference receiver verifies signatures, timestamp skew, and nonce replay, but it is an example rather than a production deployer. Its skew and nonce checks read the ts and nonce inside the signed body, since the matching headers are not covered by the HMAC.

python stack/webhook_receiver_example.py  # local sign-and-verify self-test
RELEASEGATE_WEBHOOK_SECRET='replace-me' \
  python stack/webhook_receiver_example.py serve 8099

Development and validation

python -m pytest -q
python stack/windmill/lib/sync_decide.py --check
python stack/windmill/gen_flow.py
python stack/windmill/capture/gen_capture.py
python stack/windmill/gen_app.py
python stack/windmill/seed_registry.py ../ReleaseGateBench/apps

After generation these four should have no diff: stack/windmill/flow/closed_loop.json, stack/windmill/capture/flow/capture.json, stack/windmill/app/ops_dashboard.json, stack/windmill/registry_seed.json.

All unit tests are offline. Live Dokploy and Windmill checks are intentionally separate because they create and remove real infrastructure.

Security

  • Never commit .env, database passwords, API keys, cookies, or token-bearing MCP URLs.
  • Use a scoped credential for staging; the generic API client can perform destructive administrative actions.
  • Staging Compose files can execute arbitrary container workloads. Review fixtures before running them on a shared host.
  • A failed flow cannot safely infer which staging project it owns — tear down only its exact project ID, never every staging-* project.
  • Never mount a Docker socket into an LLM-controlled worker.

License

Apache License 2.0.