stackup 1.5.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c15ca8477932ab203176cf7047ba309edae07fe35c36cdf797353d747bb74e5b
4
- data.tar.gz: 195ebfa65f7fa5a3836abf59e855fe803f97e1dbc28965800f89c88f5b56a22d
3
+ metadata.gz: f74cb61004ec9220d92f5f1d1ff292607b216233bc5aed7668d31027e4e2d2b3
4
+ data.tar.gz: 18cce0dfddda4b0870affec164d0d0c9cdb30e2e25f1ea675e3b64bd3385323e
5
5
  SHA512:
6
- metadata.gz: 8439adf18ef0d019ea73e2205b9c0d33848f2151c48ef45e5b6f8552492d860bf0ad9477d625d62163537da997fc18c763310731a4f8735860e6edc4cf121b91
7
- data.tar.gz: 8022894472208a085f0224c71e2e2bbfb9d896673c6fbd5a5af259c970383c8edec14cba14cc5cc576e8d203a15e1121bb682321ab5c79a224b3a6ac52ceb996
6
+ metadata.gz: 38a0621de917ddd7bc2163756c854ad89bae2f79e5b8f1d784e04042b55f4a5a9ea5a84bed89f305c4604efc7c9ccb01ec46c32ec3635e3d03f3148b5b3db0ed
7
+ data.tar.gz: ac2b19120472ed87471053394d1383d860939a562aebdf5e17fa2a3fc1290b48397c1dae1d9b6c752d6a0a4fd041716b9c753be42f135661303863e2e471397d
data/CHANGES.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGES
2
2
 
3
+ ## 1.7.1 (2021-08-31)
4
+
5
+ * Dependency upgrades. Uplift of release process.
6
+ * M1/arm64 support for docker image releases.
7
+
8
+ ## 1.7.0 (2020-11-25)
9
+
10
+ * Feature: --no-fail-on-empty-change-set
11
+
12
+ ## 1.6.0 (2020-11-19)
13
+
14
+ * Feature: Support --service-role-arn on "change-set create"
15
+
16
+ ## 1.5.1 (2020-11-16)
17
+
18
+ * Fix: Recognise that template is in S3 when URL is formatted like `s3.REGION.amazonaws.com`
19
+
3
20
  ## 1.5.0 (2020-04-21)
4
21
 
5
22
  * Feature: --preserve-template-formatting
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # stackup
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/stackup.png)](http://badge.fury.io/rb/stackup)
4
- [![Build Status](https://travis-ci.org/realestate-com-au/stackup.svg?branch=master)](https://travis-ci.org/realestate-com-au/stackup)
4
+ [![Build Status](https://travis-ci.com/realestate-com-au/stackup.svg?branch=main)](https://travis-ci.com/realestate-com-au/stackup)
5
5
 
6
6
  Stackup provides a CLI and a simplified Ruby API for dealing with
7
7
  AWS CloudFormation stacks.
@@ -191,7 +191,7 @@ Where a template URL references an object in S3, `stackup` leverages [CloudForma
191
191
  Non-S3 URLs are also supported, though in that case `stackup` must fetch the content itself:
192
192
 
193
193
  $ stackup mystack up \
194
- -t https://raw.githubusercontent.com/realestate-com-au/stackup/master/examples/template.yml
194
+ -t https://raw.githubusercontent.com/realestate-com-au/stackup/main/examples/template.yml
195
195
 
196
196
  ### Stack deletion
197
197
 
@@ -216,6 +216,10 @@ You can also create, list, inspect, apply and delete [change sets](http://docs.a
216
216
 
217
217
  The change-set name defaults to "pending", but can be overridden using `--name`.
218
218
 
219
+ The `change-set create` subcommand, like the `up` command, supports `--service-role-arn` to specify a service role.
220
+
221
+ It is impossible to create a change set with no changes. By default, stackup will only return successfully if a change set was actually created, and will otherwise fail. If the `--no-fail-on-empty-change-set` option is provided, stackup will return successfully if a change set was created _or_ if no change set was created because no changes were needed.
222
+
219
223
  ## Programmatic usage
220
224
 
221
225
  Get a handle to a `Stack` object as follows:
@@ -300,18 +304,23 @@ This policy grants the principal all actions required by `stackup up` for any cl
300
304
  }
301
305
  ```
302
306
 
303
- ## Releasing
307
+ ## Development
304
308
 
305
- The release process will push tags to GitHub, push the gem to rubygems and push the docker image to DockerHub.
309
+ ### Running tests
306
310
 
307
- Prerequisites:
311
+ `auto/test` will run the tests in a Docker container.
312
+ `auto/lint` will run the linter in a Docker container.
308
313
 
309
- * logged into dockerhub via `docker login`. Your user must have permission to push to `realestate/stackup`
310
- * logged into rubygems via `gem push`. Your user must have permission to push to the `stackup` gem.
314
+ ### Releasing
311
315
 
312
- To release:
316
+ Releasing is done mostly by CI. The automated process will push tags to GitHub, push the docker images to DockerHub. It won't yet push the gem to `rubygems.org`, but we're working ok it..
313
317
 
314
- ```
315
- bundle install
316
- auto/release
317
- ```
318
+ Prerequisites:
319
+
320
+ * You must have a rubygems account with permission to push to the `stackup` gem. (`auto/release-gem` will ask for your username and password)
321
+ * You have bumped the version number in `lib/stackup/version.rb`.
322
+ * You have checked the `CHANGES.md` file to make sure it is reasonable and has the changes for this new version.
323
+
324
+ To release the rubygem.
325
+ 1. `auto/release-gem`
326
+ 2. On REA's internal CI tool of choice, find the build for `stackup-ci` and trigger a new build from `HEAD`. This will always grab the latest code from here and release the rest of the parts.
data/bin/stackup CHANGED
@@ -1,553 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  $LOAD_PATH << File.expand_path("../lib", __dir__)
4
5
 
5
- require "clamp"
6
- require "console_logger"
7
- require "multi_json"
8
- require "securerandom"
9
- require "stackup"
10
- require "stackup/differ"
11
- require "stackup/source"
12
- require "stackup/version"
13
- require "stackup/yaml"
6
+ require "stackup/main_command"
14
7
 
15
8
  $stdout.sync = true
16
9
  $stderr.sync = true
17
10
 
18
- Clamp do
19
-
20
- option ["-L", "--list"], :flag, "list stacks" do
21
- list_stacks
22
- exit 0
23
- end
24
-
25
- option ["-Y", "--yaml"], :flag, "output data in YAML format"
26
-
27
- option ["--region"], "REGION", "set region" do |arg|
28
- raise ArgumentError, "#{arg.inspect} doesn't look like a region" unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
29
-
30
- arg
31
- end
32
-
33
- option ["--with-role"], "ROLE_ARN", "assume this role",
34
- :attribute_name => :role_arn
35
-
36
- option ["--retry-limit"], "N", "maximum number of retries for API calls",
37
- :environment_variable => "AWS_API_RETRY_LIMIT" do |arg|
38
- Integer(arg)
39
- end
40
-
41
- option ["--[no-]wait"], :flag, "wait for stack updates to complete",
42
- :default => true
43
-
44
- option ["--wait-poll-interval"], "N", "polling interval (in seconds) while waiting for updates",
45
- :default => 5, &method(:Integer)
46
-
47
- option "--debug", :flag, "enable debugging"
48
-
49
- option ["--version"], :flag, "display version" do
50
- puts "stackup v#{Stackup::VERSION}"
51
- exit 0
52
- end
53
-
54
- parameter "NAME", "Name of stack", :attribute_name => :stack_name
55
-
56
- def run(arguments)
57
- super(arguments)
58
- rescue Stackup::Source::ReadError => e
59
- signal_error e.message
60
- rescue Stackup::ServiceError => e
61
- signal_error e.message
62
- rescue Aws::Errors::MissingCredentialsError
63
- signal_error "no credentials provided"
64
- rescue Aws::Errors::ServiceError => e
65
- signal_error e.message
66
- end
67
-
68
- private
69
-
70
- def logger
71
- @logger ||= ConsoleLogger.new($stdout, debug?)
72
- end
73
-
74
- def format_data(data)
75
- if yaml?
76
- YAML.dump(data)
77
- else
78
- MultiJson.dump(data, :pretty => true)
79
- end
80
- end
81
-
82
- def display_data(data)
83
- puts format_data(data)
84
- end
85
-
86
- def role_arn=(arg)
87
- raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
88
-
89
- @role_arn = arg
90
- end
91
-
92
- def stackup
93
- Stackup(aws_config)
94
- end
95
-
96
- def base_aws_config
97
- {
98
- :log_level => :debug,
99
- :logger => logger,
100
- :region => region,
101
- :retry_limit => retry_limit
102
- }.reject { |_k, v| v.nil? }
103
- end
104
-
105
- def aws_config
106
- return base_aws_config unless role_arn
107
-
108
- assumed_credentials = Aws::AssumeRoleCredentials.new(
109
- :client => Aws::STS::Client.new(base_aws_config),
110
- :role_arn => role_arn,
111
- :role_session_name => "stackup-#{SecureRandom.hex(8)}"
112
- )
113
- base_aws_config.merge(:credentials => assumed_credentials)
114
- end
115
-
116
- def stack
117
- stackup.stack(stack_name, :wait => wait?, :wait_poll_interval => wait_poll_interval)
118
- end
119
-
120
- def list_stacks
121
- stackup.stack_names.each do |name|
122
- puts name
123
- end
124
- end
125
-
126
- def report_change
127
- final_status = yield
128
- puts final_status unless final_status.nil?
129
- end
130
-
131
- subcommand "status", "Print stack status." do
132
-
133
- def execute
134
- puts stack.status
135
- end
136
-
137
- end
138
-
139
- module HasParameters
140
-
141
- extend Clamp::Option::Declaration
142
-
143
- option ["-p", "--parameters"], "FILE", "parameters file (last wins)",
144
- :multivalued => true,
145
- :attribute_name => :parameter_sources,
146
- &Stackup::Source.method(:new)
147
-
148
- option ["-o", "--override"], "PARAM=VALUE", "parameter overrides",
149
- :multivalued => true,
150
- :attribute_name => :override_list
151
-
152
- private
153
-
154
- def parameters
155
- parameters_from_files.merge(parameter_overrides)
156
- end
157
-
158
- def parameters_from_files
159
- parameter_sources.map do |src|
160
- Stackup::Parameters.new(src.data).to_hash
161
- end.inject({}, :merge)
162
- end
163
-
164
- def parameter_overrides
165
- {}.tap do |result|
166
- override_list.each do |override|
167
- key, value = override.split("=", 2)
168
- result[key] = value
169
- end
170
- end
171
- end
172
-
173
- end
174
-
175
- subcommand "up", "Create/update the stack." do
176
-
177
- option ["-t", "--template"], "FILE", "template source",
178
- :attribute_name => :template_source,
179
- &Stackup::Source.method(:new)
180
-
181
- option ["-T", "--use-previous-template"], :flag,
182
- "reuse the existing template"
183
-
184
- option ["-P", "--preserve-template-formatting"], :flag,
185
- "do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
186
-
187
- include HasParameters
188
-
189
- option "--tags", "FILE", "stack tags file",
190
- :attribute_name => :tag_source,
191
- &Stackup::Source.method(:new)
192
-
193
- option "--policy", "FILE", "stack policy file",
194
- :attribute_name => :policy_source,
195
- &Stackup::Source.method(:new)
196
-
197
- option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
198
- raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
199
-
200
- arg
201
- end
202
-
203
- option "--on-failure", "ACTION",
204
- "when stack creation fails: DO_NOTHING, ROLLBACK, or DELETE",
205
- :default => "ROLLBACK"
206
-
207
- option "--capability", "CAPABILITY", "cloudformation capability",
208
- :multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
209
-
210
- def execute
211
- unless template_source || use_previous_template?
212
- signal_usage_error "Specify either --template or --use-previous-template"
213
- end
214
- options = {}
215
- if template_source
216
- if template_source.s3?
217
- options[:template_url] = template_source.location
218
- else
219
- options[:template] = template_source.data
220
- options[:template_orig] = template_source.body
221
- end
222
- end
223
- options[:on_failure] = on_failure
224
- options[:parameters] = parameters
225
- options[:tags] = tag_source.data if tag_source
226
- if policy_source
227
- if policy_source.s3?
228
- options[:stack_policy_url] = policy_source.location
229
- else
230
- options[:stack_policy] = policy_source.data
231
- end
232
- end
233
- options[:role_arn] = service_role_arn if service_role_arn
234
- options[:use_previous_template] = use_previous_template?
235
- options[:capabilities] = capability_list
236
- options[:preserve] = preserve_template_formatting?
237
- report_change do
238
- stack.create_or_update(options)
239
- end
240
- end
241
-
242
- end
243
-
244
- subcommand ["change-sets"], "List change-sets." do
245
-
246
- def execute
247
- stack.change_set_summaries.each do |change_set|
248
- puts [
249
- pad(change_set.change_set_name, 36),
250
- pad(change_set.status, 20),
251
- pad(change_set.execution_status, 24)
252
- ].join(" ")
253
- end
254
- end
255
-
256
- private
257
-
258
- def pad(s, width)
259
- (s || "").ljust(width)
260
- end
261
-
262
- end
263
-
264
- subcommand ["change-set"], "Change-set operations." do
265
-
266
- option "--name", "NAME", "Name of change-set",
267
- :attribute_name => :change_set_name,
268
- :default => "pending"
269
-
270
- subcommand "create", "Create a change-set." do
271
-
272
- option ["-d", "--description"], "DESC",
273
- "Change-set description"
274
-
275
- option ["-t", "--template"], "FILE", "template source",
276
- :attribute_name => :template_source,
277
- &Stackup::Source.method(:new)
278
-
279
- option ["-T", "--use-previous-template"], :flag,
280
- "reuse the existing template"
281
-
282
- option ["-P", "--preserve-template-formatting"], :flag,
283
- "do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
284
-
285
- option ["--force"], :flag,
286
- "replace existing change-set of the same name"
287
-
288
- include HasParameters
289
-
290
- option "--tags", "FILE", "stack tags file",
291
- :attribute_name => :tag_source,
292
- &Stackup::Source.method(:new)
293
-
294
- option "--capability", "CAPABILITY", "cloudformation capability",
295
- :multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
296
-
297
- def execute
298
- unless template_source || use_previous_template?
299
- signal_usage_error "Specify either --template or --use-previous-template"
300
- end
301
- options = {}
302
- if template_source
303
- if template_source.s3?
304
- options[:template_url] = template_source.location
305
- else
306
- options[:template] = template_source.data
307
- options[:template_orig] = template_source.body
308
- end
309
- end
310
- options[:parameters] = parameters
311
- options[:description] = description if description
312
- options[:tags] = tag_source.data if tag_source
313
- options[:use_previous_template] = use_previous_template?
314
- options[:force] = force?
315
- options[:capabilities] = capability_list
316
- options[:preserve] = preserve_template_formatting?
317
- report_change do
318
- change_set.create(options)
319
- end
320
- end
321
-
322
- end
323
-
324
- subcommand "changes", "Describe the change-set." do
325
-
326
- def execute
327
- display_data(change_set.describe.changes.map(&:to_h))
328
- end
329
-
330
- end
331
-
332
- subcommand "inspect", "Show full change-set details." do
333
-
334
- def execute
335
- display_data(change_set.describe.to_h)
336
- end
337
-
338
- end
339
-
340
- subcommand ["apply", "execute"], "Apply the change-set." do
341
-
342
- def execute
343
- report_change do
344
- change_set.execute
345
- end
346
- end
347
-
348
- end
349
-
350
- subcommand "delete", "Delete the change-set." do
351
-
352
- def execute
353
- report_change do
354
- change_set.delete
355
- end
356
- end
357
-
358
- end
359
-
360
- private
361
-
362
- def change_set
363
- stack.change_set(change_set_name)
364
- end
365
-
366
- end
367
-
368
- subcommand "diff", "Compare template/params to current stack." do
369
-
370
- option "--diff-format", "FORMAT", "'text', 'color', or 'html'", :default => "color"
371
-
372
- option ["-C", "--context-lines"], "LINES", "number of lines of context to show", :default => 10_000
373
-
374
- option ["-t", "--template"], "FILE", "template source",
375
- :attribute_name => :template_source,
376
- &Stackup::Source.method(:new)
377
-
378
- include HasParameters
379
-
380
- option "--tags", "FILE", "stack tags file",
381
- :attribute_name => :tag_source,
382
- &Stackup::Source.method(:new)
383
-
384
- def execute
385
- current = {}
386
- planned = {}
387
- if template_source
388
- current["Template"] = stack.template
389
- planned["Template"] = template_source.data
390
- end
391
- unless parameter_sources.empty?
392
- current["Parameters"] = existing_parameters.sort.to_h
393
- planned["Parameters"] = new_parameters.sort.to_h
394
- end
395
- if tag_source
396
- current["Tags"] = stack.tags.sort.to_h
397
- planned["Tags"] = tag_source.data.sort.to_h
398
- end
399
- signal_usage_error "specify '--template' or '--parameters'" if planned.empty?
400
- puts differ.diff(current, planned, context_lines)
401
- end
402
-
403
- private
404
-
405
- def differ
406
- Stackup::Differ.new(diff_format, &method(:format_data))
407
- end
408
-
409
- def existing_parameters
410
- @existing_parameters ||= stack.parameters
411
- end
412
-
413
- def new_parameters
414
- existing_parameters.merge(parameters)
415
- end
416
-
417
- end
418
-
419
- subcommand ["down", "delete"], "Remove the stack." do
420
-
421
- def execute
422
- report_change do
423
- stack.delete
424
- end
425
- end
426
-
427
- end
428
-
429
- subcommand "cancel-update", "Cancel the update in-progress." do
430
-
431
- def execute
432
- report_change do
433
- stack.cancel_update
434
- end
435
- end
436
-
437
- end
438
-
439
- subcommand "wait", "Wait until stack is stable." do
440
-
441
- def execute
442
- puts stack.wait
443
- end
444
-
445
- end
446
-
447
- subcommand "events", "List stack events." do
448
-
449
- option ["-f", "--follow"], :flag, "follow new events"
450
- option ["--data"], :flag, "display events as data"
451
-
452
- def execute
453
- stack.watch(false) do |watcher|
454
- loop do
455
- watcher.each_new_event do |event|
456
- display_event(event)
457
- end
458
- break unless follow?
459
-
460
- sleep 5
461
- end
462
- end
463
- end
464
-
465
- private
466
-
467
- def display_event(e)
468
- if data?
469
- display_data(event_data(e))
470
- else
471
- puts event_summary(e)
472
- end
473
- end
474
-
475
- def event_data(e)
476
- {
477
- "timestamp" => e.timestamp.localtime,
478
- "logical_resource_id" => e.logical_resource_id,
479
- "physical_resource_id" => e.physical_resource_id,
480
- "resource_status" => e.resource_status,
481
- "resource_status_reason" => e.resource_status_reason
482
- }.reject { |_k, v| blank?(v) }
483
- end
484
-
485
- def blank?(v)
486
- v.nil? || v.respond_to?(:empty?) && v.empty?
487
- end
488
-
489
- def event_summary(e)
490
- summary = "[#{e.timestamp.localtime.iso8601}] #{e.logical_resource_id}"
491
- summary += " - #{e.resource_status}"
492
- summary += " - #{e.resource_status_reason}" if e.resource_status_reason
493
- summary
494
- end
495
-
496
- end
497
-
498
- subcommand "template", "Display stack template." do
499
-
500
- def execute
501
- display_data(stack.template)
502
- end
503
-
504
- end
505
-
506
- subcommand ["parameters", "params"], "Display stack parameters." do
507
-
508
- def execute
509
- display_data(stack.parameters)
510
- end
511
-
512
- end
513
-
514
- subcommand "tags", "Display stack tags." do
515
-
516
- def execute
517
- display_data(stack.tags)
518
- end
519
-
520
- end
521
-
522
- subcommand "resources", "Display stack resources." do
523
-
524
- def execute
525
- display_data(stack.resources)
526
- end
527
-
528
- end
529
-
530
- subcommand "outputs", "Display stack outputs." do
531
-
532
- def execute
533
- display_data(stack.outputs)
534
- end
535
-
536
- end
537
-
538
- subcommand "inspect", "Display stack particulars." do
539
-
540
- def execute
541
- data = {
542
- "Status" => stack.status,
543
- "Parameters" => stack.parameters,
544
- "Tags" => stack.tags,
545
- "Resources" => stack.resources,
546
- "Outputs" => stack.outputs
547
- }
548
- display_data(data)
549
- end
550
-
551
- end
552
-
553
- end
11
+ Stackup::MainCommand.run