Add Policy DSL Validator, Schema Exporter, and Simulation Smoke tools
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented PolicyDslValidator with command-line options for strict mode and JSON output.
- Created PolicySchemaExporter to generate JSON schemas for policy-related models.
- Developed PolicySimulationSmoke tool to validate policy simulations against expected outcomes.
- Added project files and necessary dependencies for each tool.
- Ensured proper error handling and usage instructions across tools.
This commit is contained in:
2025-10-27 08:00:11 +02:00
parent 651b8e0fa3
commit 96d52884e8
712 changed files with 49449 additions and 6124 deletions

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""
Ensure deployment bundles reference the images defined in a release manifest.
Usage:
./deploy/tools/check-channel-alignment.py \
--release deploy/releases/2025.10-edge.yaml \
--target deploy/helm/stellaops/values-dev.yaml \
--target deploy/compose/docker-compose.dev.yaml
For every target file, the script scans `image:` declarations and verifies that
any image belonging to a repository listed in the release manifest matches the
exact digest or tag recorded there. Images outside of the manifest (for example,
supporting services such as `nats`) are ignored.
"""
from __future__ import annotations
import argparse
import pathlib
import re
import sys
from typing import Dict, Iterable, List, Optional, Set
IMAGE_LINE = re.compile(r"^\s*image:\s*['\"]?(?P<image>\S+)['\"]?\s*$")
def extract_images(path: pathlib.Path) -> List[str]:
images: List[str] = []
for line in path.read_text(encoding="utf-8").splitlines():
match = IMAGE_LINE.match(line)
if match:
images.append(match.group("image"))
return images
def image_repo(image: str) -> str:
if "@" in image:
return image.split("@", 1)[0]
# Split on the last colon to preserve registries with ports (e.g. localhost:5000)
if ":" in image:
prefix, tag = image.rsplit(":", 1)
if "/" in tag:
# handle digestive colon inside path (unlikely)
return image
return prefix
return image
def load_release_map(release_path: pathlib.Path) -> Dict[str, str]:
release_map: Dict[str, str] = {}
for image in extract_images(release_path):
repo = image_repo(image)
release_map[repo] = image
return release_map
def check_target(
target_path: pathlib.Path,
release_map: Dict[str, str],
ignore_repos: Set[str],
) -> List[str]:
errors: List[str] = []
for image in extract_images(target_path):
repo = image_repo(image)
if repo in ignore_repos:
continue
if repo not in release_map:
continue
expected = release_map[repo]
if image != expected:
errors.append(
f"{target_path}: {image} does not match release value {expected}"
)
return errors
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--release",
required=True,
type=pathlib.Path,
help="Path to the release manifest (YAML)",
)
parser.add_argument(
"--target",
action="append",
required=True,
type=pathlib.Path,
help="Deployment profile to validate against the release manifest",
)
parser.add_argument(
"--ignore-repo",
action="append",
default=[],
help="Repository prefix to ignore (may be repeated)",
)
return parser.parse_args(argv)
def main(argv: Optional[Iterable[str]] = None) -> int:
args = parse_args(argv)
release_map = load_release_map(args.release)
ignore_repos = {repo.rstrip("/") for repo in args.ignore_repo}
if not release_map:
print(f"error: no images found in release manifest {args.release}", file=sys.stderr)
return 2
total_errors: List[str] = []
for target in args.target:
if not target.exists():
total_errors.append(f"{target}: file not found")
continue
total_errors.extend(check_target(target, release_map, ignore_repos))
if total_errors:
print("✖ channel alignment check failed:", file=sys.stderr)
for err in total_errors:
print(f" - {err}", file=sys.stderr)
return 1
print("✓ deployment profiles reference release images for the inspected repositories.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,53 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_DIR="$ROOT_DIR/compose"
HELM_DIR="$ROOT_DIR/helm/stellaops"
compose_profiles=(
"docker-compose.dev.yaml:env/dev.env.example"
"docker-compose.stage.yaml:env/stage.env.example"
"docker-compose.airgap.yaml:env/airgap.env.example"
"docker-compose.mirror.yaml:env/mirror.env.example"
)
docker_ready=false
if command -v docker >/dev/null 2>&1; then
if docker compose version >/dev/null 2>&1; then
docker_ready=true
else
echo "⚠️ docker CLI present but Compose plugin unavailable; skipping compose validation" >&2
fi
else
echo "⚠️ docker CLI not found; skipping compose validation" >&2
fi
if [[ "$docker_ready" == "true" ]]; then
for entry in "${compose_profiles[@]}"; do
IFS=":" read -r compose_file env_file <<<"$entry"
printf '→ validating %s with %s\n' "$compose_file" "$env_file"
docker compose \
--env-file "$COMPOSE_DIR/$env_file" \
-f "$COMPOSE_DIR/$compose_file" config >/dev/null
done
fi
helm_values=(
"$HELM_DIR/values-dev.yaml"
"$HELM_DIR/values-stage.yaml"
"$HELM_DIR/values-airgap.yaml"
"$HELM_DIR/values-mirror.yaml"
)
if command -v helm >/dev/null 2>&1; then
for values in "${helm_values[@]}"; do
printf '→ linting Helm chart with %s\n' "$(basename "$values")"
helm lint "$HELM_DIR" -f "$values"
helm template test-release "$HELM_DIR" -f "$values" >/dev/null
done
else
echo "⚠️ helm CLI not found; skipping Helm lint/template" >&2
fi
printf 'Profiles validated (where tooling was available).\n'
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_DIR="$ROOT_DIR/compose"
HELM_DIR="$ROOT_DIR/helm/stellaops"
compose_profiles=(
"docker-compose.dev.yaml:env/dev.env.example"
"docker-compose.stage.yaml:env/stage.env.example"
"docker-compose.prod.yaml:env/prod.env.example"
"docker-compose.airgap.yaml:env/airgap.env.example"
"docker-compose.mirror.yaml:env/mirror.env.example"
"docker-compose.telemetry.yaml:"
"docker-compose.telemetry-storage.yaml:"
)
docker_ready=false
if command -v docker >/dev/null 2>&1; then
if docker compose version >/dev/null 2>&1; then
docker_ready=true
else
echo "⚠️ docker CLI present but Compose plugin unavailable; skipping compose validation" >&2
fi
else
echo "⚠️ docker CLI not found; skipping compose validation" >&2
fi
if [[ "$docker_ready" == "true" ]]; then
for entry in "${compose_profiles[@]}"; do
IFS=":" read -r compose_file env_file <<<"$entry"
printf '→ validating %s with %s\n' "$compose_file" "$env_file"
if [[ -n "$env_file" ]]; then
docker compose \
--env-file "$COMPOSE_DIR/$env_file" \
-f "$COMPOSE_DIR/$compose_file" config >/dev/null
else
docker compose -f "$COMPOSE_DIR/$compose_file" config >/dev/null
fi
done
fi
helm_values=(
"$HELM_DIR/values-dev.yaml"
"$HELM_DIR/values-stage.yaml"
"$HELM_DIR/values-prod.yaml"
"$HELM_DIR/values-airgap.yaml"
"$HELM_DIR/values-mirror.yaml"
)
if command -v helm >/dev/null 2>&1; then
for values in "${helm_values[@]}"; do
printf '→ linting Helm chart with %s\n' "$(basename "$values")"
helm lint "$HELM_DIR" -f "$values"
helm template test-release "$HELM_DIR" -f "$values" >/dev/null
done
else
echo "⚠️ helm CLI not found; skipping Helm lint/template" >&2
fi
printf 'Profiles validated (where tooling was available).\n'