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.
154 lines
4.9 KiB
Python
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})"
|
|
)
|