- Python CLI tool for rolling updates of CL-AppPipe-* and CL-SvcPipe-* stacks - Async update engine with configurable concurrency (asyncio.Semaphore) - Exponential backoff retry for API throttling - Dry-run mode for safe preview - IAM permission pre-validation - Comprehensive test suite (80 tests: 11 property-based + 69 unit) - Full spec documentation (requirements, design, tasks)
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""Report generator for the CloudFormation Stack Updater."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from cfn_updater.models import StackUpdateResult, UpdateRunReport
|
|
|
|
|
|
def generate_report(
|
|
results: list[StackUpdateResult],
|
|
total_found: int,
|
|
start_time: datetime,
|
|
end_time: datetime,
|
|
) -> UpdateRunReport:
|
|
"""Aggregate results into a summary report."""
|
|
succeeded = sum(1 for r in results if r.status == "succeeded")
|
|
failed = sum(1 for r in results if r.status == "failed")
|
|
skipped = sum(1 for r in results if r.status == "skipped")
|
|
no_update_needed = sum(1 for r in results if r.status == "no-update-needed")
|
|
|
|
return UpdateRunReport(
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
total_found=total_found,
|
|
succeeded=succeeded,
|
|
failed=failed,
|
|
skipped=skipped,
|
|
no_update_needed=no_update_needed,
|
|
results=list(results),
|
|
)
|
|
|
|
|
|
def format_report(report: UpdateRunReport) -> str:
|
|
"""Format the report as a human-readable string for console output."""
|
|
lines: list[str] = []
|
|
lines.append("=" * 60)
|
|
lines.append("CloudFormation Stack Update Report")
|
|
lines.append("=" * 60)
|
|
lines.append(f"Start Time : {report.start_time.isoformat()}")
|
|
lines.append(f"End Time : {report.end_time.isoformat()}")
|
|
lines.append(f"Total Found: {report.total_found}")
|
|
lines.append("")
|
|
lines.append("Per-Stack Results:")
|
|
lines.append("-" * 60)
|
|
|
|
for r in report.results:
|
|
line = f" {r.stack_name}: {r.status} ({r.duration_seconds:.1f}s)"
|
|
if r.error:
|
|
line += f" - {r.error}"
|
|
lines.append(line)
|
|
|
|
lines.append("")
|
|
lines.append("Summary:")
|
|
lines.append("-" * 60)
|
|
lines.append(f" Succeeded : {report.succeeded}")
|
|
lines.append(f" Failed : {report.failed}")
|
|
lines.append(f" Skipped : {report.skipped}")
|
|
lines.append(f" No Update Needed: {report.no_update_needed}")
|
|
lines.append("=" * 60)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def get_exit_code(report: UpdateRunReport) -> int:
|
|
"""Return 0 if no failures, 1 if any stack failed."""
|
|
return 1 if report.failed > 0 else 0
|