stackup 1.5.0 → 1.7.1

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 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