Skip to content

Commit

Permalink
feat: skip no-op changesets from markdown summary
Browse files Browse the repository at this point in the history
No-op changesets to stack templates are now explicitly mentioned in
target summaries; details for these are omitted in the markdown summary
report. This reduces noise, and the need to perform manual sync
operations when CloudFormation refuses to forcefully update a stack
template.

As part of this change, the bot will now wait for changesets to be ready
whenever they're generated, and not just when the markdown summary is
requested.

Information about no-op changesets is only available in non `--dry-run`
mode as we rely on CloudFormation to determine whether an updated
template results in a no-op changeset.

This change allows for templates in a repository to silently diverge
from what's deployed by CloudFormation, as long as changes remain no-op
from CloudFormation's point-of-view. For instance, updates to mappings
that don't affect a particular account/region translate to no-op
changesets.
  • Loading branch information
biochimia committed Aug 22, 2023
1 parent bd4db57 commit 04e43be
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 7 deletions.
3 changes: 3 additions & 0 deletions cfn_review_bot/cfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ def wait_for_ready(self, target):
for change_set in target.change_sets:
self.wait_for_change_set(change_set)

if change_set.is_noop:
target.analysis_results.stack_summary.noop += 1

def wait_for_change_set(self, change_set: ChangeSet):
if change_set.id is None:
return
Expand Down
14 changes: 9 additions & 5 deletions cfn_review_bot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,26 @@ def _main():
targets=params.target, regions=params.region, stacks=params.stack))

for target in targets:
print(target.header, file=sys.stderr, flush=True)
print(target.header + ' [ANALYSING]', file=sys.stderr, flush=True)

target.cfn_session = setup_session(
target, session, session_prefix, params.project)
target.cfn_session.analyse_target(target)

if not params.dry_run:
print(target.header + ' [PREPARING CHANGE SETS]', file=sys.stderr, flush=True)
target.cfn_session.prepare_change_sets(target)

if not params.dry_run:
for target in targets:
print(target.header + ' [WAITING FOR CHANGE SETS]', file=sys.stderr, flush=True)
target.cfn_session.wait_for_ready(target)

for target in targets:
print(target.header, file=sys.stderr, flush=True)
print(target, file=sys.stderr, flush=True)

if params.markdown_summary:
if not params.dry_run:
for target in targets:
target.cfn_session.wait_for_ready(target)

print(markdown.summary(targets), end='', flush=True)


Expand Down
5 changes: 5 additions & 0 deletions cfn_review_bot/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class StackStats:
adopted: int = 0
orphaned: int = 0
unmanaged: int = 0
noop: int = 0

def __str__(self):
parts = []
Expand All @@ -123,6 +124,8 @@ def __str__(self):
parts += [f'{self.updated} updated']
if self.adopted:
parts[-1] += f' ({self.adopted} adopted)'
if self.noop:
parts[-1] += f', {self.noop} with no-op changes'
if self.orphaned:
parts += [f'{self.orphaned} orphaned']

Expand Down Expand Up @@ -228,6 +231,8 @@ def __str__(self):
lines += [' (change set was not created)']
else:
lines += [f' {change_set.id}']
if change_set.is_noop:
lines[-1] += ' [NO-OP]'

lines += ['']

Expand Down
4 changes: 2 additions & 2 deletions cfn_review_bot/templates/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
{% if target.analysis_results.failed_stacks %}
**Failed Stacks:** `{{ '`, `'.join(target.analysis_results.failed_stacks) }}`
{% endif %}
{% for change_set in target.change_sets %}
{% for change_set in target.change_sets if not change_set.is_noop %}
{% if loop.first %}

{% endif %}
<details>
<summary>{% if change_set.is_failed %}{{ ':woman_shrugging:' if change_set.is_noop else ':x:' }}{% endif %}{{ ':sparkles:' if change_set.type == change_set.type.CREATE }}<code>{{ change_set.detail.StackName }}</code> [<a href="{{ change_set.url }}">change set</a>]</summary>
<summary>{% if change_set.is_failed %}:x:{% endif %}{{ ':sparkles:' if change_set.type == change_set.type.CREATE }}<code>{{ change_set.detail.StackName }}</code> [<a href="{{ change_set.url }}">change set</a>]</summary>

{% if change_set.detail.Status != 'CREATE_COMPLETE' %}
#### Change Set Status: `{{ change_set.detail.Status }}`
Expand Down

0 comments on commit 04e43be

Please sign in to comment.