stack_master 2.17.1 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/stack_master +3 -5
- data/lib/stack_master/aws_driver/cloud_formation.rb +4 -4
- data/lib/stack_master/aws_driver/s3.rb +7 -7
- data/lib/stack_master/change_set.rb +7 -11
- data/lib/stack_master/cli.rb +32 -26
- data/lib/stack_master/command.rb +1 -5
- data/lib/stack_master/commands/apply.rb +34 -19
- data/lib/stack_master/commands/delete.rb +4 -4
- data/lib/stack_master/commands/drift.rb +5 -9
- data/lib/stack_master/commands/events.rb +4 -6
- data/lib/stack_master/commands/init.rb +14 -14
- data/lib/stack_master/commands/lint.rb +1 -1
- data/lib/stack_master/commands/list_stacks.rb +1 -1
- data/lib/stack_master/commands/outputs.rb +1 -1
- data/lib/stack_master/commands/status.rb +4 -4
- data/lib/stack_master/commands/terminal_helper.rb +3 -3
- data/lib/stack_master/commands/tidy.rb +14 -16
- data/lib/stack_master/config.rb +8 -11
- data/lib/stack_master/diff.rb +2 -2
- data/lib/stack_master/parameter_loader.rb +1 -2
- data/lib/stack_master/parameter_resolver.rb +14 -17
- data/lib/stack_master/parameter_resolvers/ami_finder.rb +1 -2
- data/lib/stack_master/parameter_resolvers/ejson.rb +4 -4
- data/lib/stack_master/parameter_resolvers/latest_ami_by_tags.rb +1 -1
- data/lib/stack_master/parameter_resolvers/latest_container.rb +3 -3
- data/lib/stack_master/parameter_resolvers/one_password.rb +5 -5
- data/lib/stack_master/parameter_resolvers/sso_group_id.rb +1 -1
- data/lib/stack_master/parameter_resolvers/stack_output.rb +7 -11
- data/lib/stack_master/parameter_validator.rb +1 -5
- data/lib/stack_master/prompter.rb +1 -1
- data/lib/stack_master/resolver_array.rb +2 -2
- data/lib/stack_master/role_assumer.rb +1 -1
- data/lib/stack_master/security_group_finder.rb +4 -4
- data/lib/stack_master/sns_topic_finder.rb +1 -1
- data/lib/stack_master/sparkle_formation/compile_time/allowed_pattern_validator.rb +1 -1
- data/lib/stack_master/sparkle_formation/compile_time/definitions_validator.rb +1 -1
- data/lib/stack_master/sparkle_formation/compile_time/value_builder.rb +7 -7
- data/lib/stack_master/sparkle_formation/compile_time/value_validator.rb +1 -3
- data/lib/stack_master/sparkle_formation/template_file.rb +2 -2
- data/lib/stack_master/sso_group_id_finder.rb +3 -3
- data/lib/stack_master/stack.rb +6 -4
- data/lib/stack_master/stack_definition.rb +3 -4
- data/lib/stack_master/stack_differ.rb +32 -9
- data/lib/stack_master/template_compilers/json.rb +1 -1
- data/lib/stack_master/template_compilers/sparkle_formation.rb +2 -2
- data/lib/stack_master/template_utils.rb +2 -2
- data/lib/stack_master/test_driver/cloud_formation.rb +11 -3
- data/lib/stack_master/test_driver/s3.rb +2 -3
- data/lib/stack_master/utils.rb +3 -6
- data/lib/stack_master/validator.rb +1 -1
- data/lib/stack_master/version.rb +1 -1
- data/lib/stack_master.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffa37da4e3a6d6d80804a60be1b868180a67470009472b02a0c838354028efb6
|
|
4
|
+
data.tar.gz: e04ddf265d427425f0a1a60973eec74e2e6cc42e3cc47f00b8cbf584f1cdac93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb887f7ebe035ec7ffd808af93ad5cd69329a161dfcef8f6986e1eb79d975ce81c8b7d344403d21ca9bcaa3faafa9e91d1ef50b17cbdf79d35f88fd4cf69926f
|
|
7
|
+
data.tar.gz: d59f604ca4b6881c928525ea5c8c88f3d07a8dde8899b27ed427223f6102d78c27a59e1cbcb51588e39653221c3c90fceaa758b809ab2d161df8d6bfed3da036
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/envato/stack_master/blob/master/LICENSE.md)
|
|
4
4
|
[](https://badge.fury.io/rb/stack_master)
|
|
5
|
-
[](https://github.com/envato/stack_master/actions/workflows/test.yml)
|
|
6
6
|
|
|
7
7
|
StackMaster is a CLI tool to manage [CloudFormation](https://aws.amazon.com/cloudformation/) stacks, with the following features:
|
|
8
8
|
|
data/bin/stack_master
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'stack_master'
|
|
4
4
|
|
|
5
|
-
if ENV['STUB_AWS'] == 'true'
|
|
6
|
-
require 'stack_master/testing'
|
|
7
|
-
end
|
|
5
|
+
require 'stack_master/testing' if ENV['STUB_AWS'] == 'true'
|
|
8
6
|
|
|
9
|
-
trap(
|
|
7
|
+
trap('SIGINT') { raise StackMaster::CtrlC }
|
|
10
8
|
|
|
11
9
|
begin
|
|
12
10
|
StackMaster::CLI.new(ARGV.dup).execute!
|
|
13
11
|
rescue StackMaster::CtrlC
|
|
14
|
-
StackMaster.stdout.puts
|
|
12
|
+
StackMaster.stdout.puts 'Exiting...'
|
|
15
13
|
end
|
|
@@ -24,11 +24,11 @@ module StackMaster
|
|
|
24
24
|
prefix: prefix,
|
|
25
25
|
bucket: bucket
|
|
26
26
|
}
|
|
27
|
-
).map(&:contents).flatten.inject({})
|
|
27
|
+
).map(&:contents).flatten.inject({}) do |h, obj|
|
|
28
28
|
h.merge(obj.key => obj)
|
|
29
|
-
|
|
29
|
+
end
|
|
30
30
|
|
|
31
|
-
StackMaster.stdout.puts
|
|
31
|
+
StackMaster.stdout.puts 'Uploading files to S3:'
|
|
32
32
|
|
|
33
33
|
files.each do |template, file|
|
|
34
34
|
body = file.fetch(:body)
|
|
@@ -36,7 +36,7 @@ module StackMaster
|
|
|
36
36
|
object_key = template.dup
|
|
37
37
|
object_key.prepend("#{prefix}/") if prefix
|
|
38
38
|
compiled_template_md5 = Digest::MD5.hexdigest(body).to_s
|
|
39
|
-
s3_md5 = current_objects[object_key] ? current_objects[object_key].etag.gsub("
|
|
39
|
+
s3_md5 = current_objects[object_key] ? current_objects[object_key].etag.gsub('"', '') : nil
|
|
40
40
|
|
|
41
41
|
next if compiled_template_md5 == s3_md5
|
|
42
42
|
|
|
@@ -51,14 +51,14 @@ module StackMaster
|
|
|
51
51
|
metadata: { md5: compiled_template_md5 }
|
|
52
52
|
}
|
|
53
53
|
)
|
|
54
|
-
StackMaster.stdout.puts
|
|
54
|
+
StackMaster.stdout.puts 'done.'
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def url(bucket:, prefix:, region:, template:)
|
|
59
59
|
if region == 'us-east-1'
|
|
60
|
-
[
|
|
61
|
-
elsif region.start_with?
|
|
60
|
+
['https://s3.amazonaws.com', bucket, prefix, template].compact.join('/')
|
|
61
|
+
elsif region.start_with? 'cn-'
|
|
62
62
|
["https://s3.#{region}.amazonaws.com.cn", bucket, prefix, template].compact.join('/')
|
|
63
63
|
else
|
|
64
64
|
["https://s3-#{region}.amazonaws.com", bucket, prefix, template].compact.join('/')
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module StackMaster
|
|
2
2
|
class ChangeSet
|
|
3
|
-
END_STATES = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
END_STATES = %w[
|
|
4
|
+
CREATE_COMPLETE
|
|
5
|
+
DELETE_COMPLETE
|
|
6
|
+
FAILED
|
|
7
7
|
]
|
|
8
8
|
|
|
9
9
|
def self.generate_change_set_name(stack_name)
|
|
@@ -50,7 +50,7 @@ module StackMaster
|
|
|
50
50
|
@response.changes.each do |change|
|
|
51
51
|
display_resource_change(io, change.resource_change)
|
|
52
52
|
end
|
|
53
|
-
io.puts
|
|
53
|
+
io.puts '========================================'
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def failed?
|
|
@@ -84,13 +84,9 @@ module StackMaster
|
|
|
84
84
|
def display_resource_change_detail(io, action_name, color, detail)
|
|
85
85
|
target_name = [detail.target.attribute, detail.target.name].compact.join('.')
|
|
86
86
|
detail_messages = [target_name]
|
|
87
|
-
if action_name == 'Replace'
|
|
88
|
-
detail_messages << "#{detail.target.requires_recreation} requires recreation"
|
|
89
|
-
end
|
|
87
|
+
detail_messages << "#{detail.target.requires_recreation} requires recreation" if action_name == 'Replace'
|
|
90
88
|
triggered_by = [detail.change_source, detail.causing_entity].compact.join('.')
|
|
91
|
-
if detail.evaluation != 'Static'
|
|
92
|
-
triggered_by << "(#{detail.evaluation})"
|
|
93
|
-
end
|
|
89
|
+
triggered_by << "(#{detail.evaluation})" if detail.evaluation != 'Static'
|
|
94
90
|
detail_messages << "Triggered by: #{triggered_by}"
|
|
95
91
|
io.puts Rainbow("- #{detail_messages.join('. ')}. ").color(color)
|
|
96
92
|
end
|
data/lib/stack_master/cli.rb
CHANGED
|
@@ -6,7 +6,11 @@ module StackMaster
|
|
|
6
6
|
include Commander::Methods
|
|
7
7
|
|
|
8
8
|
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
|
|
9
|
-
@argv
|
|
9
|
+
@argv = argv
|
|
10
|
+
@stdin = stdin
|
|
11
|
+
@stdout = stdout
|
|
12
|
+
@stderr = stderr
|
|
13
|
+
@kernel = kernel
|
|
10
14
|
Commander::Runner.instance_variable_set('@instance', Commander::Runner.new(argv))
|
|
11
15
|
StackMaster.stdout = @stdout
|
|
12
16
|
StackMaster.stderr = @stderr
|
|
@@ -49,7 +53,7 @@ module StackMaster
|
|
|
49
53
|
'Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. ' \
|
|
50
54
|
"Default: ROLLBACK\n" \
|
|
51
55
|
'Note: You cannot use this option with Serverless Application Model (SAM) templates.'
|
|
52
|
-
c.option '--yes-param PARAM_NAME', String,
|
|
56
|
+
c.option '--yes-param PARAM_NAME', String, 'Auto-approve stack updates when only parameter PARAM_NAME changes'
|
|
53
57
|
c.action do |args, options|
|
|
54
58
|
options.default config: default_config_file
|
|
55
59
|
execute_stacks_command(StackMaster::Commands::Apply, args, options)
|
|
@@ -59,7 +63,7 @@ module StackMaster
|
|
|
59
63
|
command :outputs do |c|
|
|
60
64
|
c.syntax = 'stack_master outputs [region_or_alias] [stack_name]'
|
|
61
65
|
c.summary = 'Displays outputs for a stack'
|
|
62
|
-
c.description =
|
|
66
|
+
c.description = 'Displays outputs for a stack'
|
|
63
67
|
c.action do |args, options|
|
|
64
68
|
options.default config: default_config_file
|
|
65
69
|
execute_stacks_command(StackMaster::Commands::Outputs, args, options)
|
|
@@ -73,10 +77,10 @@ module StackMaster
|
|
|
73
77
|
c.option('--overwrite', 'Overwrite existing files')
|
|
74
78
|
c.action do |args, options|
|
|
75
79
|
options.default config: default_config_file
|
|
76
|
-
|
|
77
|
-
say "Invalid arguments. stack_master init [region] [stack_name]"
|
|
78
|
-
else
|
|
80
|
+
if args.size == 2
|
|
79
81
|
StackMaster::Commands::Init.perform(options, *args)
|
|
82
|
+
else
|
|
83
|
+
say 'Invalid arguments. stack_master init [region] [stack_name]'
|
|
80
84
|
end
|
|
81
85
|
end
|
|
82
86
|
end
|
|
@@ -94,8 +98,8 @@ module StackMaster
|
|
|
94
98
|
|
|
95
99
|
command :events do |c|
|
|
96
100
|
c.syntax = 'stack_master events [region_or_alias] [stack_name]'
|
|
97
|
-
c.summary =
|
|
98
|
-
c.description =
|
|
101
|
+
c.summary = 'Shows events for a stack'
|
|
102
|
+
c.description = 'Shows events for a stack'
|
|
99
103
|
c.example 'show events for myapp-vpc in us-east-1', 'stack_master events us-east-1 myapp-vpc'
|
|
100
104
|
c.option '--number Integer', Integer, 'Number of recent events to show'
|
|
101
105
|
c.option '--all', 'Show all events'
|
|
@@ -108,8 +112,8 @@ module StackMaster
|
|
|
108
112
|
|
|
109
113
|
command :resources do |c|
|
|
110
114
|
c.syntax = 'stack_master resources [region] [stack_name]'
|
|
111
|
-
c.summary =
|
|
112
|
-
c.description =
|
|
115
|
+
c.summary = 'Shows stack resources'
|
|
116
|
+
c.description = 'Shows stack resources'
|
|
113
117
|
c.action do |args, options|
|
|
114
118
|
options.default config: default_config_file
|
|
115
119
|
execute_stacks_command(StackMaster::Commands::Resources, args, options)
|
|
@@ -122,7 +126,7 @@ module StackMaster
|
|
|
122
126
|
c.description = 'List stack definitions'
|
|
123
127
|
c.action do |args, options|
|
|
124
128
|
options.default config: default_config_file
|
|
125
|
-
say
|
|
129
|
+
say 'Invalid arguments.' if args.size > 0
|
|
126
130
|
config = load_config(options.config)
|
|
127
131
|
StackMaster::Commands::ListStacks.perform(config, nil, options)
|
|
128
132
|
end
|
|
@@ -142,8 +146,8 @@ module StackMaster
|
|
|
142
146
|
|
|
143
147
|
command :lint do |c|
|
|
144
148
|
c.syntax = 'stack_master lint [region_or_alias] [stack_name]'
|
|
145
|
-
c.summary =
|
|
146
|
-
c.description =
|
|
149
|
+
c.summary = 'Check the stack definition locally'
|
|
150
|
+
c.description = 'Runs cfn-lint on the template which would be sent to AWS on apply'
|
|
147
151
|
c.example 'run cfn-lint on stack myapp-vpc with us-east-1 settings', 'stack_master lint us-east-1 myapp-vpc'
|
|
148
152
|
c.action do |args, options|
|
|
149
153
|
options.default config: default_config_file
|
|
@@ -154,7 +158,7 @@ module StackMaster
|
|
|
154
158
|
command :nag do |c|
|
|
155
159
|
c.syntax = 'stack_master nag [region_or_alias] [stack_name]'
|
|
156
160
|
c.summary = "Check this stack's template with cfn_nag"
|
|
157
|
-
c.description =
|
|
161
|
+
c.description = 'Runs SAST scan cfn_nag on the template'
|
|
158
162
|
c.example 'run cfn_nag on stack myapp-vpc with us-east-1 settings', 'stack_master nag us-east-1 myapp-vpc'
|
|
159
163
|
c.action do |args, options|
|
|
160
164
|
options.default config: default_config_file
|
|
@@ -164,7 +168,7 @@ module StackMaster
|
|
|
164
168
|
|
|
165
169
|
command :compile do |c|
|
|
166
170
|
c.syntax = 'stack_master compile [region_or_alias] [stack_name]'
|
|
167
|
-
c.summary =
|
|
171
|
+
c.summary = 'Print the compiled version of a given stack'
|
|
168
172
|
c.description = "Processes the stack and prints out a compiled version - same we'd send to AWS"
|
|
169
173
|
c.example 'print compiled stack myapp-vpc with us-east-1 settings', 'stack_master compile us-east-1 myapp-vpc'
|
|
170
174
|
c.action do |args, options|
|
|
@@ -181,7 +185,7 @@ module StackMaster
|
|
|
181
185
|
c.example 'description', 'Check the status of all stack definitions'
|
|
182
186
|
c.action do |args, options|
|
|
183
187
|
options.default config: default_config_file
|
|
184
|
-
say
|
|
188
|
+
say 'Invalid arguments. stack_master status' and return unless args.size == 0
|
|
185
189
|
|
|
186
190
|
config = load_config(options.config)
|
|
187
191
|
StackMaster::Commands::Status.perform(config, nil, options)
|
|
@@ -196,7 +200,7 @@ module StackMaster
|
|
|
196
200
|
c.example 'description', 'Check for missing or extra files'
|
|
197
201
|
c.action do |args, options|
|
|
198
202
|
options.default config: default_config_file
|
|
199
|
-
say
|
|
203
|
+
say 'Invalid arguments. stack_master tidy' and return unless args.size == 0
|
|
200
204
|
|
|
201
205
|
config = load_config(options.config)
|
|
202
206
|
StackMaster::Commands::Tidy.perform(config, nil, options)
|
|
@@ -211,7 +215,7 @@ module StackMaster
|
|
|
211
215
|
c.action do |args, options|
|
|
212
216
|
options.default config: default_config_file
|
|
213
217
|
unless args.size == 2
|
|
214
|
-
say
|
|
218
|
+
say 'Invalid arguments. stack_master delete [region] [stack_name]'
|
|
215
219
|
return
|
|
216
220
|
end
|
|
217
221
|
|
|
@@ -239,7 +243,7 @@ module StackMaster
|
|
|
239
243
|
c.syntax = 'stack_master drift [region_or_alias] [stack_name]'
|
|
240
244
|
c.summary = 'Detects and displays stack drift using the CloudFormation Drift API'
|
|
241
245
|
c.description = 'Detects and displays stack drift'
|
|
242
|
-
c.option '--timeout SECONDS', Integer,
|
|
246
|
+
c.option '--timeout SECONDS', Integer, 'The number of seconds to wait for drift detection to complete'
|
|
243
247
|
c.example 'view stack drift for a stack named myapp-vpc in us-east-1', 'stack_master drift us-east-1 myapp-vpc'
|
|
244
248
|
c.action do |args, options|
|
|
245
249
|
options.default config: default_config_file, timeout: 120
|
|
@@ -253,7 +257,7 @@ module StackMaster
|
|
|
253
257
|
private
|
|
254
258
|
|
|
255
259
|
def default_config_file
|
|
256
|
-
|
|
260
|
+
'stack_master.yml'
|
|
257
261
|
end
|
|
258
262
|
|
|
259
263
|
def load_config(file)
|
|
@@ -277,10 +281,12 @@ module StackMaster
|
|
|
277
281
|
show_other_region_candidates(config, stack_name)
|
|
278
282
|
success = false
|
|
279
283
|
end
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
+
if options.changed
|
|
285
|
+
stack_definitions = stack_definitions.select do |stack_definition|
|
|
286
|
+
running_in_allowed_account?(stack_definition.allowed_accounts) &&
|
|
287
|
+
StackStatus.new(config, stack_definition).changed?
|
|
288
|
+
end
|
|
289
|
+
end
|
|
284
290
|
stack_definitions.each do |stack_definition|
|
|
285
291
|
StackMaster.cloud_formation_driver.set_region(stack_definition.region)
|
|
286
292
|
StackMaster.stdout.puts(
|
|
@@ -295,14 +301,14 @@ module StackMaster
|
|
|
295
301
|
end
|
|
296
302
|
|
|
297
303
|
def show_other_region_candidates(config, stack_name)
|
|
298
|
-
candidates = config.filter(region =
|
|
304
|
+
candidates = config.filter(region = '', stack_name = stack_name)
|
|
299
305
|
return if candidates.empty?
|
|
300
306
|
|
|
301
307
|
StackMaster.stdout.puts "Stack name #{stack_name} exists in regions: #{candidates.map(&:region).join(', ')}"
|
|
302
308
|
end
|
|
303
309
|
|
|
304
310
|
def execute_if_allowed_account(allowed_accounts, &block)
|
|
305
|
-
raise ArgumentError,
|
|
311
|
+
raise ArgumentError, 'Block required to execute this method' unless block_given?
|
|
306
312
|
|
|
307
313
|
if running_in_allowed_account?(allowed_accounts)
|
|
308
314
|
block.call
|
data/lib/stack_master/command.rb
CHANGED
|
@@ -43,11 +43,7 @@ module StackMaster
|
|
|
43
43
|
msg = "#{e.class} #{e.message}"
|
|
44
44
|
msg << "\n Caused by: #{e.cause.class} #{e.cause.message}" if e.cause
|
|
45
45
|
msg << "\n at #{e.cause.backtrace[0..3].join("\n ")}\n ..." if e.cause && !options.trace
|
|
46
|
-
|
|
47
|
-
msg << "\n#{backtrace(e)}"
|
|
48
|
-
else
|
|
49
|
-
msg << "\n Use --trace to view backtrace"
|
|
50
|
-
end
|
|
46
|
+
msg << (options.trace ? "\n#{backtrace(e)}" : "\n Use --trace to view backtrace")
|
|
51
47
|
msg
|
|
52
48
|
end
|
|
53
49
|
|
|
@@ -44,10 +44,10 @@ module StackMaster
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def abort_if_review_in_progress
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
return unless stack_exists? && stack.stack_status == 'REVIEW_IN_PROGRESS'
|
|
48
|
+
|
|
49
|
+
StackMaster.stderr.puts "Stack currently exists and is in #{stack.stack_status}"
|
|
50
|
+
failed! "You will need to delete the stack (#{stack.stack_name}) before continuing"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def use_s3?
|
|
@@ -89,7 +89,7 @@ module StackMaster
|
|
|
89
89
|
@change_set = ChangeSet.create(stack_options.merge(change_set_type: 'CREATE'))
|
|
90
90
|
if @change_set.failed?
|
|
91
91
|
ChangeSet.delete(@change_set.id)
|
|
92
|
-
halt!(@change_set.status_reason)
|
|
92
|
+
halt!(user_friendly_changeset_error(@change_set.status_reason))
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
@change_set.display(StackMaster.stdout)
|
|
@@ -111,11 +111,11 @@ module StackMaster
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def ask_to_cancel_stack_update
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
return unless ask?('Cancel stack update?')
|
|
115
|
+
|
|
116
|
+
StackMaster.stdout.puts 'Attempting to cancel stack update'
|
|
117
|
+
cf.cancel_update_stack(stack_name: stack_name)
|
|
118
|
+
tail_stack_events
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def update_stack
|
|
@@ -123,7 +123,7 @@ module StackMaster
|
|
|
123
123
|
@change_set = ChangeSet.create(stack_options)
|
|
124
124
|
if @change_set.failed?
|
|
125
125
|
ChangeSet.delete(@change_set.id)
|
|
126
|
-
halt!(@change_set.status_reason)
|
|
126
|
+
halt!(user_friendly_changeset_error(@change_set.status_reason))
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
@change_set.display(StackMaster.stdout)
|
|
@@ -136,10 +136,10 @@ module StackMaster
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def ask_update_confirmation!
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
return if ask?('Apply change set (y/n)? ')
|
|
140
|
+
|
|
141
|
+
ChangeSet.delete(@change_set.id)
|
|
142
|
+
halt! 'Stack update aborted'
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def upload_files
|
|
@@ -177,7 +177,7 @@ module StackMaster
|
|
|
177
177
|
stack_name: stack_name,
|
|
178
178
|
parameters: proposed_stack.aws_parameters,
|
|
179
179
|
tags: proposed_stack.aws_tags,
|
|
180
|
-
capabilities: [
|
|
180
|
+
capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND],
|
|
181
181
|
role_arn: proposed_stack.role_arn,
|
|
182
182
|
notification_arns: proposed_stack.notification_arns,
|
|
183
183
|
template_method => template_value
|
|
@@ -211,9 +211,9 @@ module StackMaster
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
def ensure_valid_template_body_size!
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
return unless proposed_stack.too_big?(use_s3?)
|
|
215
|
+
|
|
216
|
+
failed! TEMPLATE_TOO_LARGE_ERROR_MESSAGE
|
|
217
217
|
end
|
|
218
218
|
|
|
219
219
|
def set_stack_policy
|
|
@@ -230,6 +230,21 @@ module StackMaster
|
|
|
230
230
|
StackMaster.stdout.puts 'done.'
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
+
def user_friendly_changeset_error(status_reason)
|
|
234
|
+
# CloudFormation returns various messages when there are no changes to apply
|
|
235
|
+
if status_reason =~ /didn'?t contain changes|no changes|no updates are to be performed/i
|
|
236
|
+
<<~MESSAGE.chomp
|
|
237
|
+
#{status_reason}
|
|
238
|
+
|
|
239
|
+
While there may be differences in the template file (e.g., whitespace, comments, or
|
|
240
|
+
formatting), CloudFormation has determined that no actual resource changes are needed.
|
|
241
|
+
The stack is already in the desired state.
|
|
242
|
+
MESSAGE
|
|
243
|
+
else
|
|
244
|
+
status_reason
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
233
248
|
extend Forwardable
|
|
234
249
|
def_delegators :@stack_definition, :stack_name, :region
|
|
235
250
|
end
|
|
@@ -15,7 +15,7 @@ module StackMaster
|
|
|
15
15
|
return unless check_exists
|
|
16
16
|
|
|
17
17
|
unless ask?("Really delete stack #{@stack_name} (y/n)? ")
|
|
18
|
-
StackMaster.stdout.puts
|
|
18
|
+
StackMaster.stdout.puts 'Stack update aborted'
|
|
19
19
|
return
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ module StackMaster
|
|
|
33
33
|
cf.describe_stacks({ stack_name: @stack_name })
|
|
34
34
|
true
|
|
35
35
|
rescue Aws::CloudFormation::Errors::ValidationError
|
|
36
|
-
failed(
|
|
36
|
+
failed('Stack does not exist')
|
|
37
37
|
false
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -43,10 +43,10 @@ module StackMaster
|
|
|
43
43
|
|
|
44
44
|
def tail_stack_events
|
|
45
45
|
StackEvents::Streamer.stream(@stack_name, @region, io: StackMaster.stdout, from: @from_time)
|
|
46
|
-
StackMaster.stdout.puts
|
|
46
|
+
StackMaster.stdout.puts 'Stack deleted'
|
|
47
47
|
rescue Aws::CloudFormation::Errors::ValidationError
|
|
48
48
|
# Unfortunately the stack as a tendency of going away before we get the final delete event.
|
|
49
|
-
StackMaster.stdout.puts
|
|
49
|
+
StackMaster.stdout.puts 'Stack deleted'
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -6,9 +6,9 @@ module StackMaster
|
|
|
6
6
|
include Command
|
|
7
7
|
include Commander::UI
|
|
8
8
|
|
|
9
|
-
DETECTION_COMPLETE_STATES = [
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
DETECTION_COMPLETE_STATES = %w[
|
|
10
|
+
DETECTION_COMPLETE
|
|
11
|
+
DETECTION_FAILED
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
def perform
|
|
@@ -41,9 +41,7 @@ module StackMaster
|
|
|
41
41
|
drift.physical_resource_id].join(' '), color)
|
|
42
42
|
return unless drift.stack_resource_drift_status == 'MODIFIED'
|
|
43
43
|
|
|
44
|
-
unless drift.property_differences.empty?
|
|
45
|
-
puts colorize(' Property differences:', color)
|
|
46
|
-
end
|
|
44
|
+
puts colorize(' Property differences:', color) unless drift.property_differences.empty?
|
|
47
45
|
drift.property_differences.each do |property_difference|
|
|
48
46
|
puts colorize(" - #{property_difference.difference_type} #{property_difference.property_path}", color)
|
|
49
47
|
end
|
|
@@ -98,9 +96,7 @@ module StackMaster
|
|
|
98
96
|
break if DETECTION_COMPLETE_STATES.include?(resp.detection_status)
|
|
99
97
|
|
|
100
98
|
elapsed_time = Time.now - start_time
|
|
101
|
-
if elapsed_time > @options.timeout
|
|
102
|
-
raise "Timeout waiting for stack drift detection"
|
|
103
|
-
end
|
|
99
|
+
raise 'Timeout waiting for stack drift detection' if elapsed_time > @options.timeout
|
|
104
100
|
|
|
105
101
|
sleep SLEEP_SECONDS
|
|
106
102
|
end
|
|
@@ -9,9 +9,9 @@ module StackMaster
|
|
|
9
9
|
filter_events(events).each do |event|
|
|
10
10
|
StackEvents::Presenter.print_event(StackMaster.stdout, event)
|
|
11
11
|
end
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
return unless @options.tail
|
|
13
|
+
|
|
14
|
+
StackEvents::Streamer.stream(@stack_definition.stack_name, @stack_definition.region, io: StackMaster.stdout)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
private
|
|
@@ -22,9 +22,7 @@ module StackMaster
|
|
|
22
22
|
else
|
|
23
23
|
n = @options.number || 25
|
|
24
24
|
from = events.count - n
|
|
25
|
-
if from < 0
|
|
26
|
-
from = 0
|
|
27
|
-
end
|
|
25
|
+
from = 0 if from < 0
|
|
28
26
|
events[from..-1]
|
|
29
27
|
end
|
|
30
28
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'erb'
|
|
2
2
|
|
|
3
3
|
module StackMaster
|
|
4
4
|
module Commands
|
|
@@ -12,22 +12,22 @@ module StackMaster
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def perform
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
return unless check_files
|
|
16
|
+
|
|
17
|
+
create_stack_master_yml
|
|
18
|
+
create_stack_json_yml
|
|
19
|
+
create_parameters_yml
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def check_files
|
|
25
|
-
@stack_master_filename =
|
|
25
|
+
@stack_master_filename = 'stack_master.yml'
|
|
26
26
|
@stack_json_filename = "templates/#{@stack_name}.json"
|
|
27
|
-
@parameters_filename = File.join(
|
|
28
|
-
@region_parameters_filename = File.join(
|
|
27
|
+
@parameters_filename = File.join('parameters', "#{@stack_name}.yml")
|
|
28
|
+
@region_parameters_filename = File.join('parameters', @region, "#{@stack_name}.yml")
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
unless @options.overwrite
|
|
31
31
|
[@stack_master_filename, @stack_json_filename, @parameters_filename,
|
|
32
32
|
@region_parameters_filename].each do |filename|
|
|
33
33
|
next unless File.exist?(filename)
|
|
@@ -50,7 +50,7 @@ module StackMaster
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def stack_json_template
|
|
53
|
-
File.join(StackMaster.base_dir,
|
|
53
|
+
File.join(StackMaster.base_dir, 'stacktemplates', 'stack.json.erb')
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def create_stack_master_yml
|
|
@@ -63,7 +63,7 @@ module StackMaster
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def stack_master_template
|
|
66
|
-
File.join(StackMaster.base_dir,
|
|
66
|
+
File.join(StackMaster.base_dir, 'stacktemplates', 'stack_master.yml.erb')
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def create_parameters_yml
|
|
@@ -83,11 +83,11 @@ module StackMaster
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def parameter_stack_name_template
|
|
86
|
-
File.join(StackMaster.base_dir,
|
|
86
|
+
File.join(StackMaster.base_dir, 'stacktemplates', 'parameter_stack_name.yml')
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def parameter_region_template
|
|
90
|
-
File.join(StackMaster.base_dir,
|
|
90
|
+
File.join(StackMaster.base_dir, 'stacktemplates', 'parameter_region.yml')
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def render(renderer)
|
|
@@ -21,11 +21,11 @@ module StackMaster
|
|
|
21
21
|
{
|
|
22
22
|
region: stack_definition.region,
|
|
23
23
|
stack_name: stack_definition.stack_name,
|
|
24
|
-
stack_status: running_in_allowed_account?(allowed_accounts) ? stack_status.status :
|
|
25
|
-
different: running_in_allowed_account?(allowed_accounts) ? stack_status.changed_message :
|
|
24
|
+
stack_status: running_in_allowed_account?(allowed_accounts) ? stack_status.status : 'Disallowed account',
|
|
25
|
+
different: running_in_allowed_account?(allowed_accounts) ? stack_status.changed_message : 'N/A'
|
|
26
26
|
}
|
|
27
27
|
end
|
|
28
|
-
tp.set :max_width,
|
|
28
|
+
tp.set :max_width, window_size
|
|
29
29
|
tp.set :io, StackMaster.stdout
|
|
30
30
|
tp status
|
|
31
31
|
StackMaster.stdout.puts " * No echo parameters can't be diffed"
|
|
@@ -34,7 +34,7 @@ module StackMaster
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def progress
|
|
37
|
-
@progress ||= ProgressBar.create(title:
|
|
37
|
+
@progress ||= ProgressBar.create(title: 'Fetching stack information',
|
|
38
38
|
total: @config.stacks.size,
|
|
39
39
|
output: StackMaster.stdout)
|
|
40
40
|
end
|