Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ The format is based on [Keep a Changelog], and this project adheres to

### Changed

- Improve message when CloudFormation claims there are no changes to apply, even when a template diff is present. ([#398])
- Display Tags diff (stack tags) in `stack_master diff` and `stack_master apply` commands. ([#397])
- Resolve style issues identified by RuboCop. ([#396])

[Unreleased]: https://github.com/envato/stack_master/compare/v2.17.1...HEAD
[#396]: https://github.com/envato/stack_master/pull/396
[#397]: https://github.com/envato/stack_master/pull/397
[#398]: https://github.com/envato/stack_master/pull/398

## [2.17.1] - 2025-12-19

Expand Down
19 changes: 17 additions & 2 deletions lib/stack_master/commands/apply.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def create_stack_by_change_set
@change_set = ChangeSet.create(stack_options.merge(change_set_type: 'CREATE'))
if @change_set.failed?
ChangeSet.delete(@change_set.id)
halt!(@change_set.status_reason)
halt!(user_friendly_changeset_error(@change_set.status_reason))
end

@change_set.display(StackMaster.stdout)
Expand Down Expand Up @@ -123,7 +123,7 @@ def update_stack
@change_set = ChangeSet.create(stack_options)
if @change_set.failed?
ChangeSet.delete(@change_set.id)
halt!(@change_set.status_reason)
halt!(user_friendly_changeset_error(@change_set.status_reason))
end

@change_set.display(StackMaster.stdout)
Expand Down Expand Up @@ -230,6 +230,21 @@ def set_stack_policy
StackMaster.stdout.puts 'done.'
end

def user_friendly_changeset_error(status_reason)
# CloudFormation returns various messages when there are no changes to apply
if status_reason =~ /didn'?t contain changes|no changes|no updates are to be performed/i
<<~MESSAGE.chomp
#{status_reason}

While there may be differences in the template file (e.g., whitespace, comments, or
formatting), CloudFormation has determined that no actual resource changes are needed.
The stack is already in the desired state.
MESSAGE
else
status_reason
end
end

extend Forwardable
def_delegators :@stack_definition, :stack_name, :region
end
Expand Down
37 changes: 34 additions & 3 deletions spec/stack_master/commands/apply_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,42 @@ def apply
before do
allow(StackMaster::ChangeSet).to receive(:delete)
allow(change_set).to receive(:failed?).and_return(true)
allow(change_set).to receive(:status_reason).and_return('reason')
end

it 'outputs the status reason' do
expect { apply }.to output(/reason/).to_stdout
context 'with a generic error' do
before do
allow(change_set).to receive(:status_reason).and_return('reason')
end

it 'outputs the status reason' do
expect { apply }.to output(/reason/).to_stdout
end
end

context 'with a "no changes" error from CloudFormation' do
before do
allow(change_set)
.to receive(:status_reason)
.and_return("The submitted information didn't contain changes. " \
'Submit different information to create a change set.')
end

it 'outputs a user-friendly explanation' do
expect { apply }.to output(/The submitted information didn't contain changes/).to_stdout
expect { apply }.to output(/no actual resource changes are needed/).to_stdout
expect { apply }.to output(/stack is already in the desired state/).to_stdout
end
end

context 'with alternative "no changes" error message' do
before do
allow(change_set).to receive(:status_reason).and_return('No updates are to be performed.')
end

it 'outputs a user-friendly explanation' do
expect { apply }.to output(/No updates are to be performed/).to_stdout
expect { apply }.to output(/no actual resource changes are needed/).to_stdout
end
end
end

Expand Down