feat: scaffold deployment-policies monorepo
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:
Executable
+59
@@ -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"
|
||||
Executable
+51
@@ -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."
|
||||
Executable
+18
@@ -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"
|
||||
}
|
||||
Executable
+28
@@ -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
|
||||
Executable
+51
@@ -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."
|
||||
Reference in New Issue
Block a user