|
25 | 25 | from typing import Dict, List, Optional |
26 | 26 |
|
27 | 27 | import botocore |
| 28 | +import click |
28 | 29 |
|
29 | 30 | from samcli.commands._utils.table_print import MIN_OFFSET, newline_per_item, pprint_column_names, pprint_columns |
30 | 31 | from samcli.commands.deploy import exceptions as deploy_exceptions |
@@ -336,95 +337,25 @@ def _display_changeset_changes( |
336 | 337 | color=row_color, |
337 | 338 | ) |
338 | 339 |
|
339 | | - # Recursively display nested stack changes with pagination |
| 340 | + # Recursively display nested stack changes |
340 | 341 | # Only process nested stacks when is_parent=True to avoid duplicates |
341 | 342 | if is_parent: |
342 | 343 | for nested in nested_changesets: |
343 | 344 | try: |
344 | 345 | # Display nested stack header |
345 | | - sys.stdout.write(f"\n[Nested Stack: {nested['logical_id']}]\n") |
346 | | - sys.stdout.flush() |
347 | | - |
348 | | - # Use paginator for nested changesets to handle large changesets |
349 | | - nested_paginator = self._client.get_paginator("describe_change_set") |
350 | | - nested_iterator = nested_paginator.paginate(ChangeSetName=nested["changeset_id"]) |
351 | | - |
352 | | - nested_has_changes = False |
353 | | - # Track nested-nested stacks for recursive processing |
354 | | - deeply_nested_changesets = [] |
355 | | - |
356 | | - for nested_item in nested_iterator: |
357 | | - nested_cf_changes = nested_item.get("Changes", []) |
358 | | - for change in nested_cf_changes: |
359 | | - nested_has_changes = True |
360 | | - resource_props = change.get("ResourceChange", {}) |
361 | | - action = resource_props.get("Action") |
362 | | - resource_type = resource_props.get("ResourceType") |
363 | | - logical_id = resource_props.get("LogicalResourceId") |
364 | | - replacement = resource_props.get("Replacement") |
365 | | - |
366 | | - # Check for deeply nested stacks (3+ levels) |
367 | | - deeply_nested_changeset_id = resource_props.get("ChangeSetId") |
368 | | - if resource_type == "AWS::CloudFormation::Stack" and deeply_nested_changeset_id: |
369 | | - deeply_nested_changesets.append( |
370 | | - {"changeset_id": deeply_nested_changeset_id, "logical_id": logical_id} |
371 | | - ) |
372 | | - |
373 | | - row_color = self.deploy_color.get_changeset_action_color(action=action) |
374 | | - pprint_columns( |
375 | | - columns=[ |
376 | | - changes_showcase.get(action, action), |
377 | | - logical_id, |
378 | | - resource_type, |
379 | | - "N/A" if replacement is None else replacement, |
380 | | - ], |
381 | | - width=kwargs["width"], |
382 | | - margin=kwargs["margin"], |
383 | | - format_string=DESCRIBE_CHANGESET_FORMAT_STRING, |
384 | | - format_args=kwargs["format_args"], |
385 | | - columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), |
386 | | - color=row_color, |
387 | | - ) |
388 | | - |
389 | | - if not nested_has_changes: |
390 | | - pprint_columns( |
391 | | - columns=["-", "-", "-", "-"], |
392 | | - width=kwargs["width"], |
393 | | - margin=kwargs["margin"], |
394 | | - format_string=DESCRIBE_CHANGESET_FORMAT_STRING, |
395 | | - format_args=kwargs["format_args"], |
396 | | - columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), |
| 346 | + click.echo(f"\n[Nested Stack: {nested['logical_id']}]") |
| 347 | + |
| 348 | + # Get the stack name from the changeset to support recursive call |
| 349 | + nested_response = self._client.describe_change_set(ChangeSetName=nested["changeset_id"]) |
| 350 | + nested_stack_name = nested_response.get("StackName") |
| 351 | + if nested_stack_name: |
| 352 | + # Recursively call to display nested changes (supports arbitrary nesting depth) |
| 353 | + self._display_changeset_changes( |
| 354 | + nested["changeset_id"], nested_stack_name, is_parent=True, **kwargs |
397 | 355 | ) |
398 | | - |
399 | | - # Recursively process deeply nested stacks (3+ levels) |
400 | | - for deeply_nested in deeply_nested_changesets: |
401 | | - # Get the stack name from the changeset to support recursive call |
402 | | - try: |
403 | | - deeply_nested_response = self._client.describe_change_set( |
404 | | - ChangeSetName=deeply_nested["changeset_id"] |
405 | | - ) |
406 | | - deeply_nested_stack_name = deeply_nested_response.get("StackName") |
407 | | - if deeply_nested_stack_name: |
408 | | - # Print header for deeply nested stack |
409 | | - sys.stdout.write(f"\n[Nested Stack: {deeply_nested['logical_id']}]\n") |
410 | | - sys.stdout.flush() |
411 | | - # Recursively call to display deeply nested changes |
412 | | - self._display_changeset_changes( |
413 | | - deeply_nested["changeset_id"], deeply_nested_stack_name, is_parent=False, **kwargs |
414 | | - ) |
415 | | - except Exception as e: |
416 | | - LOG.debug( |
417 | | - "Failed to describe deeply nested changeset %s: %s", deeply_nested["changeset_id"], e |
418 | | - ) |
419 | | - sys.stdout.write( |
420 | | - f"\n[Nested Stack: {deeply_nested['logical_id']}] - Unable to fetch changes: {str(e)}\n" |
421 | | - ) |
422 | | - sys.stdout.flush() |
423 | | - |
424 | 356 | except Exception as e: |
425 | 357 | LOG.debug("Failed to describe nested changeset %s: %s", nested["changeset_id"], e) |
426 | | - sys.stdout.write(f"Unable to fetch changes: {str(e)}\n") |
427 | | - sys.stdout.flush() |
| 358 | + click.echo(f"Unable to fetch changes: {str(e)}") |
428 | 359 |
|
429 | 360 | return changes if changeset_found else None |
430 | 361 |
|
|
0 commit comments