feat: scaffold deployment-policies monorepo
ci / verify (push) Failing after 10m57s
ci / publish (push) Skipped

OPA/Rego deployment gateway policies: 5 rules (valid-environment,
require-approvals, no-self-approval, block-weekends, freeze-window),
a deploy-gate combining them, JSON schemas, scenario-driven tests,
Gitea Actions CI (verify + publish to generic registry), changesets
versioning, Makefile and scripts.
This commit is contained in:
jdevega
2026-09-15 19:05:03 +02:00
commit 59cacfdfab
48 changed files with 1703 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Builds deployable OPA bundles for every rule and gate.
#
# Composition model: SEPARATE EVALUATION.
# - Each rule is built as its own bundle where the module lives at data.rules.<name>.
# - Each gate bundles the policy.rego of all its dependency rules; consumers evaluate
# data.rules.<name> per rule and combine results at runtime.
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
rm -rf "$ARTIFACTS_DIR"
STAGE_DIR="$(mktemp -d)"
trap 'rm -rf "$STAGE_DIR"' EXIT
MANIFEST='{"revision":"","roots":["rules"],"rego_version":1}'
write_manifest() {
printf '%s' "$MANIFEST" > "$1/.manifest"
}
# --- rules ---
for d in "$POLICIES_DIR"/rules/*/; do
name="$(repo_name "$d")"
version="$(repo_version "$d")"
out="$ARTIFACTS_DIR/rules/$name/$version"
mkdir -p "$out"
echo "==> build rule $name@$version"
mkdir -p "$STAGE_DIR/rule/$name"
cp "$d/policy.rego" "$STAGE_DIR/rule/$name/policy.rego"
write_manifest "$STAGE_DIR/rule/$name"
(cd "$STAGE_DIR/rule/$name" && "$OPA" build -b . -o "$out/bundle.tar.gz")
done
# --- gates ---
for g in "$POLICIES_DIR"/gates/*/; do
name="$(repo_name "$g")"
version="$(repo_version "$g")"
out="$ARTIFACTS_DIR/gates/$name/$version"
mkdir -p "$out"
echo "==> build gate $name@$version"
stage="$STAGE_DIR/gate/$name"
for dep in $(node -p "Object.keys(require('$g/package.json').dependencies ?? {}).join(' ')"); do
for r in "$POLICIES_DIR"/rules/*/; do
rname="$(repo_name "$r")"
if [ "$rname" = "$dep" ]; then
mkdir -p "$stage/$rname"
cp "$r/policy.rego" "$stage/$rname/policy.rego"
break
fi
done
done
[ -d "$stage" ] || { echo "ERROR: gate $name has no buildable dependencies" >&2; exit 1; }
write_manifest "$stage"
(cd "$stage" && "$OPA" build -b . -o "$out/bundle.tar.gz")
done
echo "Artifacts written under $ARTIFACTS_DIR"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Lints, schema-checks and tests every rule and gate.
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
fail() {
echo "FAIL: $1" >&2
exit 1
}
# Rego formatting.
echo "==> opa fmt --fail"
"$OPA" fmt --fail --diff "$POLICIES_DIR" || fail "rego files are not formatted (run 'make fmt' or 'opa fmt -w')"
# Lint with regal if available.
if command -v regal >/dev/null 2>&1; then
echo "==> regal lint"
regal lint "$POLICIES_DIR" --format github || fail "regal lint found issues"
else
echo "==> regal not found, skipping"
fi
RULES_ARG=""
rule_dirs=( "$POLICIES_DIR"/rules/*/ )
[ "${#rule_dirs[@]}" -gt 0 ] && [ -d "${rule_dirs[0]}" ] || fail "no rules found"
# Schema-check + unit tests per rule (runs are isolated so each rule's scenarios.json loads at data.scenarios).
for d in "${rule_dirs[@]}"; do
name="$(basename "$d")"
echo "==> check ${name}"
"$OPA" check --strict --schema "$d/schema" "$d/policy.rego" "$d/policy_test.rego" || fail "check ${name}"
echo "==> test ${name}"
"$OPA" test "$d/policy.rego" "$d/policy_test.rego" "$d/scenarios.json" || fail "test ${name}"
RULES_ARG="$RULES_ARG $d/policy.rego"
done
# Gates: check + tests with all rule policy.rego files loaded so data.rules.* resolves.
gate_dirs=( "$POLICIES_DIR"/gates/*/ )
for g in "${gate_dirs[@]}"; do
[ -d "$g" ] || continue
name="$(basename "$g")"
echo "==> check ${name}"
"$OPA" check --strict --schema "$g/schema" "$g/gate_test.rego" || fail "check ${name}"
echo "==> test ${name}"
# shellcheck disable=SC2086
"$OPA" test "$g/gate_test.rego" "$g/scenarios.json" $RULES_ARG || fail "test ${name}"
done
echo "All checks passed."
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Shared helpers for policy management scripts.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
POLICIES_DIR="$ROOT_DIR/policies"
TOOLS_DIR="$ROOT_DIR/tools"
OPA="$TOOLS_DIR/opa"
ARTIFACTS_DIR="$ROOT_DIR/dist"
repo_name() {
node -p "require('$1/package.json').name"
}
repo_version() {
node -p "require('$1/package.json').version"
}
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Installs a pinned OPA release binary into tools/opa.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TOOLS_DIR="$ROOT_DIR/tools"
OPA_VERSION="${OPA_VERSION:-1.20.2}"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
arm64) ARCH="arm64" ;;
esac
URL="https://openpolicyagent.org/downloads/v${OPA_VERSION}/opa_${OS}_${ARCH}_static"
mkdir -p "$TOOLS_DIR"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$URL" -o "$TOOLS_DIR/opa"
else
wget -qO "$TOOLS_DIR/opa" "$URL"
fi
chmod +x "$TOOLS_DIR/opa"
echo "Installed OPA v${OPA_VERSION} (${OS}/${ARCH}) to $TOOLS_DIR/opa"
"$TOOLS_DIR/opa" version | head -1
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Publishes every built rule/gate bundle to the Gitea generic package registry.
#
# Requires:
# - GITEA_TOKEN API token with write:package scope
# - GITEA_URL e.g. https://gitea.devegamoreno.com (default: env or https://gitea.devegamoreno.com)
# - GITEA_OWNER repo/package owner (default: git remote owner or jdevega)
#
# Registry path: /api/packages/{owner}/generic/{package}/{version}/{file}
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
GITEA_URL="${GITEA_URL:-https://gitea.devegamoreno.com}"
GITEA_OWNER="${GITEA_OWNER:-jdevega}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
[ -n "$GITEA_TOKEN" ] || { echo "ERROR: GITEA_TOKEN is not set" >&2; exit 1; }
publish_file() {
local pkg="$1" version="$2" file="$3"
local url="$GITEA_URL/api/packages/$GITEA_OWNER/generic/$pkg/$version/$(basename "$file")"
echo "==> publish $pkg@$version $(basename "$file")"
curl -fsSL \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--upload-file "$file" \
"$url"
echo
}
if [ ! -d "$ARTIFACTS_DIR" ]; then
echo "No artifacts found. Run 'make build' first." >&2
exit 1
fi
for bundle in "$ARTIFACTS_DIR"/rules/*/*/bundle.tar.gz; do
path="${bundle#"$ARTIFACTS_DIR/rules/"}"
name="${path%%/*}"
version="$(basename "$(dirname "$bundle")")"
publish_file "rule-$name" "$version" "$bundle"
done
for bundle in "$ARTIFACTS_DIR"/gates/*/*/bundle.tar.gz; do
path="${bundle#"$ARTIFACTS_DIR/gates/"}"
name="${path%%/*}"
version="$(basename "$(dirname "$bundle")")"
publish_file "gate-$name" "$version" "$bundle"
done
echo "Publish complete."