Files
Orchestrator/shared/AI-Orchestrator/scripts/pat_manager/reporter.py
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

154 lines
4.9 KiB
Python

"""Summary Reporter module for the PAT Manager system.
Generates a human-readable summary report of PAT lifecycle check results
suitable for GitHub Actions log output.
"""
from __future__ import annotations
from .models import CheckResult, PatStatus, RotationResult
class SummaryReporter:
"""Produces a formatted summary report of PAT check and rotation results.
The report includes a table of all checked PATs with their status and
details, plus a rotation results section if any tokens were rotated.
"""
def report(
self, results: list[CheckResult], rotations: list[RotationResult]
) -> str:
"""Generate a plain-text summary report.
Args:
results: List of check results for all PATs in the registry.
rotations: List of rotation results for tokens that were rotated.
Returns:
A formatted plain-text string suitable for printing to the
GitHub Actions log.
"""
lines: list[str] = []
lines.append("=== PAT Lifecycle Check Summary ===")
lines.append("")
# Build the table
lines.append(self._build_table(results))
# Add rotation section if there are rotation results
successful_rotations = [r for r in rotations if r.success]
if successful_rotations:
lines.append("")
lines.append("--- Rotation Results ---")
for rotation in successful_rotations:
lines.append(self._format_rotation(rotation))
return "\n".join(lines)
def _build_table(self, results: list[CheckResult]) -> str:
"""Build the formatted results table.
Args:
results: List of check results to display.
Returns:
Formatted table string with header, separator, and data rows.
"""
# Column headers
header_service = "Service"
header_token = "Token Name"
header_status = "Status"
header_details = "Details"
# Calculate column widths based on content
service_width = max(
len(header_service),
*(len(r.entry.service) for r in results) if results else [0],
)
token_width = max(
len(header_token),
*(len(r.entry.token_name) for r in results) if results else [0],
)
status_width = max(
len(header_status),
*(len(r.status.value) for r in results) if results else [0],
)
details_width = max(
len(header_details),
*(len(self._get_details(r)) for r in results) if results else [0],
)
# Format header
header = (
f"{header_service:<{service_width}} | "
f"{header_token:<{token_width}} | "
f"{header_status:<{status_width}} | "
f"{header_details:<{details_width}}"
)
# Format separator
separator = (
f"{'-' * service_width}-+-"
f"{'-' * token_width}-+-"
f"{'-' * status_width}-+-"
f"{'-' * details_width}"
)
# Format data rows
rows: list[str] = []
for result in results:
details = self._get_details(result)
row = (
f"{result.entry.service:<{service_width}} | "
f"{result.entry.token_name:<{token_width}} | "
f"{result.status.value:<{status_width}} | "
f"{details:<{details_width}}"
)
rows.append(row)
table_lines = [header, separator] + rows
return "\n".join(table_lines)
def _get_details(self, result: CheckResult) -> str:
"""Get the details string for a check result.
Args:
result: The check result to describe.
Returns:
A human-readable details string based on the PAT status.
"""
if result.status == PatStatus.CHECK_FAILED:
return result.error_message or "Unknown error"
if result.status == PatStatus.EXPIRED:
return "0 days remaining"
if result.days_remaining is not None:
return f"{result.days_remaining} days remaining"
return ""
def _format_rotation(self, rotation: RotationResult) -> str:
"""Format a single rotation result line.
Args:
rotation: A successful rotation result.
Returns:
Formatted string like:
"GitLab/token-name: Rotated successfully (old: 2025-07-01, new: 2025-08-01)"
"""
if rotation.entry is not None:
service = rotation.entry.service
token_name = rotation.entry.token_name
else:
service = "Unknown"
token_name = "unknown-token"
old_date = rotation.old_expiry_date or "N/A"
new_date = rotation.new_expiry_date or "N/A"
return (
f"{service}/{token_name}: "
f"Rotated successfully (old: {old_date}, new: {new_date})"
)