Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,373 @@
#!/usr/bin/env python3
"""
generate_compose.py - Generate pinned Docker Compose files for suite releases
Sprint: CI/CD Enhancement - Suite Release Pipeline
Creates docker-compose.yml files with pinned image versions for releases.
Usage:
python generate_compose.py <version> <codename> [options]
python generate_compose.py 2026.04 Nova --output docker-compose.yml
python generate_compose.py 2026.04 Nova --airgap --output docker-compose.airgap.yml
Arguments:
version Suite version (YYYY.MM format)
codename Release codename
Options:
--output FILE Output file (default: stdout)
--airgap Generate air-gap variant
--registry URL Container registry URL
--include-deps Include infrastructure dependencies (postgres, valkey)
"""
import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional
# Repository paths
SCRIPT_DIR = Path(__file__).parent
REPO_ROOT = SCRIPT_DIR.parent.parent.parent
MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json"
# Default registry
DEFAULT_REGISTRY = "git.stella-ops.org/stella-ops.org"
# Service definitions with port mappings and dependencies
SERVICE_DEFINITIONS = {
"authority": {
"ports": ["8080:8080"],
"depends_on": ["postgres"],
"environment": {
"AUTHORITY_DB_CONNECTION": "Host=postgres;Database=authority;Username=stellaops;Password=${POSTGRES_PASSWORD}",
},
"healthcheck": {
"test": ["CMD", "curl", "-f", "http://localhost:8080/health"],
"interval": "30s",
"timeout": "10s",
"retries": 3,
},
},
"attestor": {
"ports": ["8081:8080"],
"depends_on": ["postgres", "authority"],
"environment": {
"ATTESTOR_DB_CONNECTION": "Host=postgres;Database=attestor;Username=stellaops;Password=${POSTGRES_PASSWORD}",
"ATTESTOR_AUTHORITY_URL": "http://authority:8080",
},
},
"concelier": {
"ports": ["8082:8080"],
"depends_on": ["postgres", "valkey"],
"environment": {
"CONCELIER_DB_CONNECTION": "Host=postgres;Database=concelier;Username=stellaops;Password=${POSTGRES_PASSWORD}",
"CONCELIER_CACHE_URL": "valkey:6379",
},
},
"scanner": {
"ports": ["8083:8080"],
"depends_on": ["postgres", "concelier"],
"environment": {
"SCANNER_DB_CONNECTION": "Host=postgres;Database=scanner;Username=stellaops;Password=${POSTGRES_PASSWORD}",
"SCANNER_CONCELIER_URL": "http://concelier:8080",
},
"volumes": ["/var/run/docker.sock:/var/run/docker.sock:ro"],
},
"policy": {
"ports": ["8084:8080"],
"depends_on": ["postgres"],
"environment": {
"POLICY_DB_CONNECTION": "Host=postgres;Database=policy;Username=stellaops;Password=${POSTGRES_PASSWORD}",
},
},
"signer": {
"ports": ["8085:8080"],
"depends_on": ["authority"],
"environment": {
"SIGNER_AUTHORITY_URL": "http://authority:8080",
},
},
"excititor": {
"ports": ["8086:8080"],
"depends_on": ["postgres", "concelier"],
"environment": {
"EXCITITOR_DB_CONNECTION": "Host=postgres;Database=excititor;Username=stellaops;Password=${POSTGRES_PASSWORD}",
},
},
"gateway": {
"ports": ["8000:8080"],
"depends_on": ["authority"],
"environment": {
"GATEWAY_AUTHORITY_URL": "http://authority:8080",
},
},
"scheduler": {
"ports": ["8087:8080"],
"depends_on": ["postgres", "valkey"],
"environment": {
"SCHEDULER_DB_CONNECTION": "Host=postgres;Database=scheduler;Username=stellaops;Password=${POSTGRES_PASSWORD}",
"SCHEDULER_QUEUE_URL": "valkey:6379",
},
},
}
# Infrastructure services
INFRASTRUCTURE_SERVICES = {
"postgres": {
"image": "postgres:16-alpine",
"environment": {
"POSTGRES_USER": "stellaops",
"POSTGRES_PASSWORD": "${POSTGRES_PASSWORD:-stellaops}",
"POSTGRES_DB": "stellaops",
},
"volumes": ["postgres_data:/var/lib/postgresql/data"],
"healthcheck": {
"test": ["CMD-SHELL", "pg_isready -U stellaops"],
"interval": "10s",
"timeout": "5s",
"retries": 5,
},
},
"valkey": {
"image": "valkey/valkey:8-alpine",
"volumes": ["valkey_data:/data"],
"healthcheck": {
"test": ["CMD", "valkey-cli", "ping"],
"interval": "10s",
"timeout": "5s",
"retries": 5,
},
},
}
def read_service_versions() -> Dict[str, dict]:
"""Read service versions from manifest."""
if not MANIFEST_FILE.exists():
return {}
try:
manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8"))
return manifest.get("services", {})
except json.JSONDecodeError:
return {}
def generate_compose(
version: str,
codename: str,
registry: str,
services: Dict[str, dict],
airgap: bool = False,
include_deps: bool = True,
) -> str:
"""Generate Docker Compose YAML."""
now = datetime.now(timezone.utc)
lines = [
"# Docker Compose for StellaOps Suite",
f"# Version: {version} \"{codename}\"",
f"# Generated: {now.isoformat()}",
"#",
"# Usage:",
"# docker compose up -d",
"# docker compose logs -f",
"# docker compose down",
"#",
"# Environment variables:",
"# POSTGRES_PASSWORD - PostgreSQL password (default: stellaops)",
"#",
"",
"services:",
]
# Add infrastructure services if requested
if include_deps:
for name, config in INFRASTRUCTURE_SERVICES.items():
lines.extend(generate_service_block(name, config, indent=2))
# Add StellaOps services
for svc_name, svc_def in SERVICE_DEFINITIONS.items():
# Get version info from manifest
manifest_info = services.get(svc_name, {})
docker_tag = manifest_info.get("dockerTag") or manifest_info.get("version", version)
# Build image reference
if airgap:
image = f"localhost:5000/{svc_name}:{docker_tag}"
else:
image = f"{registry}/{svc_name}:{docker_tag}"
# Build service config
config = {
"image": image,
"restart": "unless-stopped",
**svc_def,
}
# Add release labels
config["labels"] = {
"com.stellaops.release.version": version,
"com.stellaops.release.codename": codename,
"com.stellaops.service.name": svc_name,
"com.stellaops.service.version": manifest_info.get("version", "1.0.0"),
}
lines.extend(generate_service_block(svc_name, config, indent=2))
# Add volumes
lines.extend([
"",
"volumes:",
])
if include_deps:
lines.extend([
" postgres_data:",
" driver: local",
" valkey_data:",
" driver: local",
])
# Add networks
lines.extend([
"",
"networks:",
" default:",
" name: stellaops",
" driver: bridge",
])
return "\n".join(lines)
def generate_service_block(name: str, config: dict, indent: int = 2) -> List[str]:
"""Generate YAML block for a service."""
prefix = " " * indent
lines = [
"",
f"{prefix}{name}:",
]
inner_prefix = " " * (indent + 2)
# Image
if "image" in config:
lines.append(f"{inner_prefix}image: {config['image']}")
# Container name
lines.append(f"{inner_prefix}container_name: stellaops-{name}")
# Restart policy
if "restart" in config:
lines.append(f"{inner_prefix}restart: {config['restart']}")
# Ports
if "ports" in config:
lines.append(f"{inner_prefix}ports:")
for port in config["ports"]:
lines.append(f"{inner_prefix} - \"{port}\"")
# Volumes
if "volumes" in config:
lines.append(f"{inner_prefix}volumes:")
for vol in config["volumes"]:
lines.append(f"{inner_prefix} - {vol}")
# Environment
if "environment" in config:
lines.append(f"{inner_prefix}environment:")
for key, value in config["environment"].items():
lines.append(f"{inner_prefix} {key}: \"{value}\"")
# Depends on
if "depends_on" in config:
lines.append(f"{inner_prefix}depends_on:")
for dep in config["depends_on"]:
lines.append(f"{inner_prefix} {dep}:")
lines.append(f"{inner_prefix} condition: service_healthy")
# Health check
if "healthcheck" in config:
hc = config["healthcheck"]
lines.append(f"{inner_prefix}healthcheck:")
if "test" in hc:
test = hc["test"]
if isinstance(test, list):
lines.append(f"{inner_prefix} test: {json.dumps(test)}")
else:
lines.append(f"{inner_prefix} test: \"{test}\"")
for key in ["interval", "timeout", "retries", "start_period"]:
if key in hc:
lines.append(f"{inner_prefix} {key}: {hc[key]}")
# Labels
if "labels" in config:
lines.append(f"{inner_prefix}labels:")
for key, value in config["labels"].items():
lines.append(f"{inner_prefix} {key}: \"{value}\"")
return lines
def main():
parser = argparse.ArgumentParser(
description="Generate pinned Docker Compose files for suite releases",
)
parser.add_argument("version", help="Suite version (YYYY.MM format)")
parser.add_argument("codename", help="Release codename")
parser.add_argument("--output", "-o", help="Output file")
parser.add_argument(
"--airgap",
action="store_true",
help="Generate air-gap variant (localhost:5000 registry)",
)
parser.add_argument(
"--registry",
default=DEFAULT_REGISTRY,
help="Container registry URL",
)
parser.add_argument(
"--include-deps",
action="store_true",
default=True,
help="Include infrastructure dependencies",
)
parser.add_argument(
"--no-deps",
action="store_true",
help="Exclude infrastructure dependencies",
)
args = parser.parse_args()
# Read service versions
services = read_service_versions()
if not services:
print("Warning: No service versions found in manifest", file=sys.stderr)
# Generate compose file
include_deps = args.include_deps and not args.no_deps
compose = generate_compose(
version=args.version,
codename=args.codename,
registry=args.registry,
services=services,
airgap=args.airgap,
include_deps=include_deps,
)
# Output
if args.output:
Path(args.output).write_text(compose, encoding="utf-8")
print(f"Docker Compose written to: {args.output}", file=sys.stderr)
else:
print(compose)
if __name__ == "__main__":
main()