save progress

This commit is contained in:
StellaOps Bot
2025-12-26 22:03:32 +02:00
parent 9a4cd2e0f7
commit e6c47c8f50
3634 changed files with 253222 additions and 56632 deletions

View File

@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""
collect_versions.py - Collect service versions for suite release
Sprint: CI/CD Enhancement - Suite Release Pipeline
Gathers all service versions from Directory.Versions.props and service-versions.json.
Usage:
python collect_versions.py [options]
python collect_versions.py --format json
python collect_versions.py --format yaml --output versions.yaml
Options:
--format FMT Output format: json, yaml, markdown, env (default: json)
--output FILE Output file (defaults to stdout)
--include-unreleased Include services with no Docker tag
--registry URL Container registry URL
"""
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass, asdict
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
VERSIONS_FILE = REPO_ROOT / "src" / "Directory.Versions.props"
MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json"
# Default registry
DEFAULT_REGISTRY = "git.stella-ops.org/stella-ops.org"
@dataclass
class ServiceVersion:
name: str
version: str
docker_tag: Optional[str] = None
released_at: Optional[str] = None
git_sha: Optional[str] = None
image: Optional[str] = None
def read_versions_from_props() -> Dict[str, str]:
"""Read versions from Directory.Versions.props."""
if not VERSIONS_FILE.exists():
print(f"Warning: {VERSIONS_FILE} not found", file=sys.stderr)
return {}
content = VERSIONS_FILE.read_text(encoding="utf-8")
versions = {}
# Pattern: <StellaOps{Service}Version>X.Y.Z</StellaOps{Service}Version>
pattern = r"<StellaOps(\w+)Version>(\d+\.\d+\.\d+)</StellaOps\1Version>"
for match in re.finditer(pattern, content):
service_name = match.group(1)
version = match.group(2)
versions[service_name.lower()] = version
return versions
def read_manifest() -> Dict[str, dict]:
"""Read service metadata from manifest file."""
if not MANIFEST_FILE.exists():
print(f"Warning: {MANIFEST_FILE} not found", file=sys.stderr)
return {}
try:
manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8"))
return manifest.get("services", {})
except json.JSONDecodeError as e:
print(f"Warning: Failed to parse {MANIFEST_FILE}: {e}", file=sys.stderr)
return {}
def collect_all_versions(
registry: str = DEFAULT_REGISTRY,
include_unreleased: bool = False,
) -> List[ServiceVersion]:
"""Collect all service versions."""
props_versions = read_versions_from_props()
manifest_services = read_manifest()
services = []
# Merge data from both sources
all_service_keys = set(props_versions.keys()) | set(manifest_services.keys())
for key in sorted(all_service_keys):
version = props_versions.get(key, "0.0.0")
manifest = manifest_services.get(key, {})
docker_tag = manifest.get("dockerTag")
released_at = manifest.get("releasedAt")
git_sha = manifest.get("gitSha")
# Skip unreleased if not requested
if not include_unreleased and not docker_tag:
continue
# Build image reference
if docker_tag:
image = f"{registry}/{key}:{docker_tag}"
else:
image = f"{registry}/{key}:{version}"
service = ServiceVersion(
name=manifest.get("name", key.title()),
version=version,
docker_tag=docker_tag,
released_at=released_at,
git_sha=git_sha,
image=image,
)
services.append(service)
return services
def format_json(services: List[ServiceVersion]) -> str:
"""Format as JSON."""
data = {
"generatedAt": datetime.now(timezone.utc).isoformat(),
"services": [asdict(s) for s in services],
}
return json.dumps(data, indent=2, ensure_ascii=False)
def format_yaml(services: List[ServiceVersion]) -> str:
"""Format as YAML."""
lines = [
"# Service Versions",
f"# Generated: {datetime.now(timezone.utc).isoformat()}",
"",
"services:",
]
for s in services:
lines.extend([
f" {s.name.lower()}:",
f" name: {s.name}",
f" version: \"{s.version}\"",
])
if s.docker_tag:
lines.append(f" dockerTag: \"{s.docker_tag}\"")
if s.image:
lines.append(f" image: \"{s.image}\"")
if s.released_at:
lines.append(f" releasedAt: \"{s.released_at}\"")
if s.git_sha:
lines.append(f" gitSha: \"{s.git_sha}\"")
return "\n".join(lines)
def format_markdown(services: List[ServiceVersion]) -> str:
"""Format as Markdown table."""
lines = [
"# Service Versions",
"",
f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}",
"",
"| Service | Version | Docker Tag | Released |",
"|---------|---------|------------|----------|",
]
for s in services:
released = s.released_at[:10] if s.released_at else "-"
docker_tag = f"`{s.docker_tag}`" if s.docker_tag else "-"
lines.append(f"| {s.name} | {s.version} | {docker_tag} | {released} |")
return "\n".join(lines)
def format_env(services: List[ServiceVersion]) -> str:
"""Format as environment variables."""
lines = [
"# Service Versions as Environment Variables",
f"# Generated: {datetime.now(timezone.utc).isoformat()}",
"",
]
for s in services:
name_upper = s.name.upper().replace(" ", "_")
lines.append(f"STELLAOPS_{name_upper}_VERSION={s.version}")
if s.docker_tag:
lines.append(f"STELLAOPS_{name_upper}_DOCKER_TAG={s.docker_tag}")
if s.image:
lines.append(f"STELLAOPS_{name_upper}_IMAGE={s.image}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Collect service versions for suite release",
)
parser.add_argument(
"--format",
choices=["json", "yaml", "markdown", "env"],
default="json",
help="Output format",
)
parser.add_argument("--output", "-o", help="Output file")
parser.add_argument(
"--include-unreleased",
action="store_true",
help="Include services without Docker tags",
)
parser.add_argument(
"--registry",
default=DEFAULT_REGISTRY,
help="Container registry URL",
)
args = parser.parse_args()
# Collect versions
services = collect_all_versions(
registry=args.registry,
include_unreleased=args.include_unreleased,
)
if not services:
print("No services found", file=sys.stderr)
if not args.include_unreleased:
print("Hint: Use --include-unreleased to show all services", file=sys.stderr)
sys.exit(0)
# Format output
formatters = {
"json": format_json,
"yaml": format_yaml,
"markdown": format_markdown,
"env": format_env,
}
output = formatters[args.format](services)
# Write output
if args.output:
Path(args.output).write_text(output, encoding="utf-8")
print(f"Versions written to: {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == "__main__":
main()