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.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
"""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})"
|
||||
)
|
||||
Reference in New Issue
Block a user