cfn-stack-updater/cfn_updater/report.py
Vijaya Manne 632ac9e328 Initial commit: One-Click CloudFormation Stack Updater
- 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)
2026-05-29 14:56:59 -04:00

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