jdevega cc8e9a23b3
ci / verify (push) Failing after 7m58s
ci / publish (push) Skipped
ci: bump actions to v7 and add package-lock.json
setup-node with cache: npm requires a lock file; the initial run
failed with 'Dependencies lock file is not found'. Committing the
generated package-lock.json and updating checkout/setup-node to the
latest v7 major (node24 runtime; requires act_runner >= 0.2.13).
2026-09-15 20:31:30 +02:00

deployment-policies

Small, composable OPA/Rego policies that act as deployment gateways. Each rule decides one narrow question about a deployment request (environment validity, approvals, weekends, freeze windows…), and gates bundle the rules you want to enforce together.

  • Language: Rego (OPA 1.x, rego.v1 syntax)
  • Hosting: Gitea (gitea.devegamoreno.com), repo jdevega/deployment-policies
  • CI/CD: Gitea Actions (verify + build on every push/PR, publish on main)
  • Distribution: Gitea generic package registry (rules and gates as OPA bundles)
  • Versioning: actions/Changesets over npm workspaces

Repository layout

policies/
  rules/
    <rule>/              # one deployable policy = one bundle
      policy.rego        #   module in package rules.<name>; exposes allow + violations
      policy_test.rego   #   tests driven by scenarios.json
      scenarios.json     #   input/data/expectation fixtures (loaded at data.scenarios)
      schema/            #   JSON Schemas for input and data (opa check --schema)
      package.json       #   version + identity
  gates/
    <gate>/              # one deployable gate = bundle of the rules it depends on
      gate_test.rego     #   integration tests over the combined rules
      scenarios.json
      schema/
      package.json       #   lists rule packages as dependencies
scripts/                 # install-opa, check, build, publish
.gitea/workflows/ci.yml  # Gitea Actions pipeline
Makefile                 # convenience targets

Requirements

  • Node.js ≥ 22.11 (workspaces + changesets v3)
  • make, curl (or wget)
  • OPA binary installed via make install (pinned release, no Docker)
  • regal on PATH for linting (optional, skipped if absent)

Quick start

make install   # install OPA v1.20.2 to tools/opa
npm ci         # install changesets + oxfmt
make check     # format check + regal lint + schema check + tests
make build     # build rule and gate bundles into dist/

Rego conventions

Every policy module targets separate evaluation and follows the same contract:

  • package rules.<name> — one package per rule, no cross-file imports between rules.
  • allow (boolean, default false) — this rule alone permits the deployment.
  • violations (set of {"code", "message"}) — the reasons for denial.
  • Input is read from input.deployment / input.approvals.
  • Environment configuration and freeze windows are read from data.deploygate.* (config.<env>.min_approvals, config.<env>.block_weekends, freeze_windows[]).

Combining policies is done at runtime by the consumer: load one or more bundles, evaluate each rule under data.rules.<name> separately, and fold the violations/allow results together. Gates simply ship several rules in one bundle for convenience.

Input contract

Field Type Used by
input.deployment.environment string all rules
input.deployment.service string (informational)
input.deployment.version string (informational)
input.deployment.requested_by string no-self-approval
input.deployment.created_at RFC3339 block-weekends, freeze-window
input.approvals[].by string no-self-approval
input.approvals[].active boolean require-approvals, no-self-approval
input.approvals[].at RFC3339 (informational)

Data contract

Field Type Read by
data.deploygate.config.<env>.min_approvals int require-approvals
data.deploygate.config.<env>.block_weekends bool block-weekends
data.deploygate.freeze_windows[].environment string freeze-window
data.deploygate.freeze_windows[].start RFC3339 freeze-window
data.deploygate.freeze_windows[].end RFC3339 freeze-window

Config lives in the data document (not in the bundle), so rules never embed environment-specific settings. Bundles scope their roots to rules, letting callers merge data.deploygate.*.

Available rules

Rule Package Denies when
valid-environment rules.valid_environment environment is not in data.deploygate.config
require-approvals rules.require_approvals fewer active approvals than min_approvals
no-self-approval rules.no_self_approval an active approver is the requestor
block-weekends rules.block_weekends created_at is Sat/Sun and env has block_weekends
freeze-window rules.freeze_window deploy falls inside a matching freeze window

Available gates

Gate Package Includes
deploy-gate gate.deploy_gate_test all 5 rules

A gate's package.json lists its rules under dependencies; changesets bumps the gate when any of its rules change, and scripts/build.sh bundles exactly those rule modules into the gate.

Testing

Each rule carries scenarios.json with { scenarios: { <name>: { input, data, expect } } }. opa test loads it at data.scenarios (top-level key, not filename). Tests assert both the expected violations codes and allow. See scripts/check.sh; run with make test.

Building and publishing

make build    # dist/rules/<name>/<version>/bundle.tar.gz and dist/gates/<name>/<version>/bundle.tar.gz
make publish  # upload all bundles to the Gitea generic registry

publish.sh needs:

  • GITEA_TOKEN (API token with write:package)
  • GITEA_URL (default https://gitea.devegamoreno.com)
  • GITEA_OWNER (default jdevega)

Artifacts land at:

/api/packages/{owner}/generic/rule-<rule>/<version>/bundle.tar.gz
/api/packages/{owner}/generic/gate-<gate>/<version>/bundle.tar.gz

Bundles are built fresh in CI (publish job on main) and uploaded with curl.

Versioning

On a new commit, run make changeset, select the changed packages and bump types, and commit the generated changeset. Later run make version to apply them. See .changeset/README.md.

CI

.gitea/workflows/ci.yml:

  1. verifynpm ci, install OPA, scripts/check.sh, scripts/build.sh (every push/PR).
  2. publish — on main only, rebuilds and uploads bundles with GITEA_TOKEN.

Create the GITEA_TOKEN repository secret on Gitea with at least write:package scope.

Evaluating a bundle

opa eval \
  --bundle dist/rules/valid-environment/0.0.0/bundle.tar.gz \
  --data deploygate.json \
  --input request.json \
  'data.rules.valid_environment'

Or load a gate bundle and fuse the rule results yourself:

opa eval --bundle dist/gates/deploy-gate/<v>/bundle.tar.gz --data deploygate.json \
  --input request.json \
  '{codes: {c | some r in [data.rules.valid_environment, data.rules.require_approvals]; some v in r.violations; c := v.code}}'

See the input/data contracts above for deploygate.json and request.json shapes.

S
Description
Small composable OPA/Rego deployment gateway policies, tested, linted and published to the Gitea generic package registry.
Readme
82 KiB
Languages
Open Policy Agent 47.9%
Shell 42.3%
Makefile 9.8%