stackup 1.4.2 → 1.4.3

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: 8b00856895bb8013972d2861813cf8260203808afb4ee4a4a9e3e861adcf7c33
4
- data.tar.gz: 3c4abaa4fdceb95ffee76134d2b1dd189149fdc5750b3ddd2a64dd16ca44567c
3
+ metadata.gz: 01e367ca4b2044872b8bffc12075970ad962f5542e143a3d8f47cc6cd9ec6773
4
+ data.tar.gz: a3b4dd683a84e01885ffdc60ec306e18d212e24fefddec660cd451b8ca8c5d23
5
5
  SHA512:
6
- metadata.gz: eacb2f2acc276c29507ab0fc221a0205498baed30244b6c0e0480f86f2788d2c3fb0a63e937d8e7add1aee23dd25d36dbfb182e2cae34b2a88841055d393d62d
7
- data.tar.gz: 9454cc5bbfeac0f94ab398b8a2bc48e3d0fa2a4555fab3ebdfd9e3235b51776edf915a886b42652ee7f72d24d5e8f912230cb37c061023c07d90e0d66d76b60d
6
+ metadata.gz: 1fc1090d8365094a4ec26df52d8f4ba745d5ff9dc275001630d4ebb013a7673105d658749ee0d3214da89c1559c0acbaa3a5f9ee1cb17ab8e643ab16436d0ac0
7
+ data.tar.gz: 578e511731086edfe83a22245bde31c50ec8c194504772f6f4ae3cd2ac56fe5819e2adfc2c11e34b576e07071207f70b2f3f2c38f61d16a0ca74f639e1668333
data/CHANGES.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # CHANGES
2
2
 
3
+ ## 1.4.3 (2019-09-05)
4
+
5
+ * Add support for CloudFormation capabilities via CLI and Rake
6
+ * Fix: Prevent failure when creating a stack with change sets
7
+
3
8
  ## 1.4.2 (2019-01-21)
4
9
 
5
10
  * Fix #64: Create and Update functions should not mutate the options parameter
@@ -26,6 +26,7 @@ Clamp do
26
26
 
27
27
  option ["--region"], "REGION", "set region" do |arg|
28
28
  raise ArgumentError, "#{arg.inspect} doesn't look like a region" unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
29
+
29
30
  arg
30
31
  end
31
32
 
@@ -84,6 +85,7 @@ Clamp do
84
85
 
85
86
  def role_arn=(arg)
86
87
  raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
88
+
87
89
  @role_arn = arg
88
90
  end
89
91
 
@@ -102,6 +104,7 @@ Clamp do
102
104
 
103
105
  def aws_config
104
106
  return base_aws_config unless role_arn
107
+
105
108
  assumed_credentials = Aws::AssumeRoleCredentials.new(
106
109
  :client => Aws::STS::Client.new(base_aws_config),
107
110
  :role_arn => role_arn,
@@ -190,6 +193,7 @@ Clamp do
190
193
 
191
194
  option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
192
195
  raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
196
+
193
197
  arg
194
198
  end
195
199
 
@@ -197,6 +201,9 @@ Clamp do
197
201
  "when stack creation fails: DO_NOTHING, ROLLBACK, or DELETE",
198
202
  :default => "ROLLBACK"
199
203
 
204
+ option "--capability", "CAPABILITY", "cloudformation capability",
205
+ :multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
206
+
200
207
  def execute
201
208
  unless template_source || use_previous_template?
202
209
  signal_usage_error "Specify either --template or --use-previous-template"
@@ -221,6 +228,7 @@ Clamp do
221
228
  end
222
229
  options[:role_arn] = service_role_arn if service_role_arn
223
230
  options[:use_previous_template] = use_previous_template?
231
+ options[:capabilities] = capability_list
224
232
  report_change do
225
233
  stack.create_or_update(options)
226
234
  end
@@ -275,6 +283,9 @@ Clamp do
275
283
  :attribute_name => :tag_source,
276
284
  &Stackup::Source.method(:new)
277
285
 
286
+ option "--capability", "CAPABILITY", "cloudformation capability",
287
+ :multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
288
+
278
289
  def execute
279
290
  unless template_source || use_previous_template?
280
291
  signal_usage_error "Specify either --template or --use-previous-template"
@@ -292,6 +303,7 @@ Clamp do
292
303
  options[:tags] = tag_source.data if tag_source
293
304
  options[:use_previous_template] = use_previous_template?
294
305
  options[:force] = force?
306
+ options[:capabilities] = capability_list
295
307
  report_change do
296
308
  change_set.create(options)
297
309
  end
@@ -434,6 +446,7 @@ Clamp do
434
446
  display_event(event)
435
447
  end
436
448
  break unless follow?
449
+
437
450
  sleep 5
438
451
  end
439
452
  end
@@ -87,7 +87,7 @@ module Stackup
87
87
  # @raise [Stackup::StackUpdateError] if operation fails
88
88
  #
89
89
  def execute
90
- modify_stack("UPDATE_COMPLETE", "update failed") do
90
+ modify_stack(/(CREATE|UPDATE)_COMPLETE/, "update failed") do
91
91
  cf_client.execute_change_set(:stack_name => stack.name, :change_set_name => name)
92
92
  end
93
93
  end
@@ -20,7 +20,7 @@ module Stackup
20
20
  def diff(existing_data, pending_data, context_lines = nil)
21
21
  existing = format(normalize_data(existing_data)) + "\n"
22
22
  pending = format(normalize_data(pending_data)) + "\n"
23
- diff = Diffy::Diff.new(existing, pending, context: context_lines).to_s(diff_style)
23
+ diff = Diffy::Diff.new(existing, pending, :context => context_lines).to_s(diff_style)
24
24
  diff unless diff =~ /\A\s*\Z/
25
25
  end
26
26
 
@@ -58,6 +58,7 @@ module Stackup
58
58
  name = name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase if name.respond_to?(:gsub)
59
59
  writer = "#{name}="
60
60
  raise(ArgumentError, "invalid attribute: #{name.inspect}") unless respond_to?(writer)
61
+
61
62
  public_send(writer, value)
62
63
  end
63
64
  end
@@ -1,6 +1,7 @@
1
1
  require "rake/tasklib"
2
2
  require "tempfile"
3
3
  require "yaml"
4
+ require "Pathname"
4
5
 
5
6
  module Stackup
6
7
 
@@ -13,6 +14,7 @@ module Stackup
13
14
  attr_accessor :template
14
15
  attr_accessor :parameters
15
16
  attr_accessor :tags
17
+ attr_accessor :capabilities
16
18
 
17
19
  alias namespace= name=
18
20
 
@@ -23,6 +25,7 @@ module Stackup
23
25
  yield self if block_given?
24
26
  raise ArgumentError, "no name provided" unless @name
25
27
  raise ArgumentError, "no template provided" unless @template
28
+
26
29
  define
27
30
  end
28
31
 
@@ -36,14 +39,15 @@ module Stackup
36
39
  def define
37
40
  namespace(name) do
38
41
 
39
- data_options = DataOptions.new
40
- data_options["--template"] = template
41
- data_options["--parameters"] = parameters if parameters
42
- data_options["--tags"] = tags if tags
42
+ data_options = []
43
+ data_options << DataOption.for("--template", template)
44
+ data_options << DataOption.for("--parameters", parameters) if parameters
45
+ data_options << DataOption.for("--tags", tags) if tag
43
46
 
44
47
  desc "Update #{stack} stack"
45
- task "up" => data_options.files do
46
- stackup "up", *data_options.to_a
48
+ task "up" => data_options.grep(DataOptionFile).map(&:argument) do
49
+ data_options << DataOption.for("--capability", capabilities) if capabilities
50
+ stackup "up", *data_options.map(&:to_a).flatten
47
51
  end
48
52
 
49
53
  desc "Cancel update of #{stack} stack"
@@ -52,8 +56,8 @@ module Stackup
52
56
  end
53
57
 
54
58
  desc "Show pending changes to #{stack} stack"
55
- task "diff" => data_options.files do
56
- stackup "diff", *data_options.to_a
59
+ task "diff" => data_options.grep(DataOptionFile).map(&:argument) do
60
+ stackup "diff", *data_options.map(&:to_a).flatten
57
61
  end
58
62
 
59
63
  desc "Show #{stack} stack outputs and resources"
@@ -74,41 +78,66 @@ module Stackup
74
78
  end
75
79
  end
76
80
 
77
- # Options to "stackup up".
78
- #
79
- class DataOptions
80
-
81
- def initialize
82
- @options = {}
83
- end
81
+ # A flag with optional argument that will be passed to stackup
82
+ class DataOption
84
83
 
85
- def []=(option, file_or_value)
86
- @options[option] = file_or_value
84
+ def initialize(flag, argument)
85
+ @flag = flag
86
+ @argument = argument
87
87
  end
88
88
 
89
- def files
90
- @options.values.grep(String)
89
+ def to_a
90
+ [@flag, @argument]
91
91
  end
92
92
 
93
- def to_a
94
- [].tap do |result|
95
- @options.each do |option, file_or_data|
96
- result << option
97
- result << maybe_tempfile(file_or_data, option[2..-1])
98
- end
93
+ # Factory method for initialising DataOptions based on class
94
+ def self.for(flag, argument)
95
+ if argument.is_a?(Hash)
96
+ DataOptionHash.new(flag, argument)
97
+ elsif argument.is_a?(Array)
98
+ DataOptionArray.new(flag, argument)
99
+ elsif argument.is_a?(String) && File.exist?(argument)
100
+ DataOptionFile.new(flag, argument)
101
+ else
102
+ DataOption.new(flag, argument)
99
103
  end
100
104
  end
101
105
 
102
- def maybe_tempfile(file_or_data, type)
103
- return file_or_data if file_or_data.is_a?(String)
104
- tempfile = Tempfile.new([type, ".yml"])
105
- tempfile.write(YAML.dump(file_or_data))
106
+ attr_reader :flag
107
+ attr_reader :argument
108
+
109
+ end
110
+
111
+ # An option with a Hash argument
112
+ # Hash content is stored in a temporary file upon conversion
113
+ class DataOptionHash < DataOption
114
+
115
+ def as_tempfile(filename, data)
116
+ tempfile = Tempfile.new([filename, ".yml"])
117
+ tempfile.write(YAML.dump(data))
106
118
  tempfile.close
107
119
  tempfile.path.to_s
108
120
  end
109
121
 
122
+ def to_a
123
+ [@flag, as_tempfile(@flag[2..-1], @argument)]
124
+ end
125
+
110
126
  end
111
127
 
128
+ # An option with an Array argument
129
+ # Flag is repeated for every Array member upon conversion
130
+ class DataOptionArray < DataOption
131
+
132
+ def to_a
133
+ @argument.map { |a| [@flag, a] }.flatten
134
+ end
135
+
136
+ end
137
+
138
+ # An option with a File argument
139
+ class DataOptionFile < DataOption; end
140
+
112
141
  end
113
142
 
114
143
  end
@@ -28,7 +28,7 @@ module Stackup
28
28
 
29
29
  private
30
30
 
31
- LOOKS_LIKE_JSON = /\A\s*[\{\[]/
31
+ LOOKS_LIKE_JSON = /\A\s*[\{\[]/.freeze
32
32
 
33
33
  def uri
34
34
  URI(location)
@@ -41,6 +41,7 @@ module Stackup
41
41
  def on_event(event_handler = nil, &block)
42
42
  event_handler ||= block
43
43
  raise ArgumentError, "no event_handler provided" if event_handler.nil?
44
+
44
45
  @event_handler = event_handler
45
46
  end
46
47
 
@@ -324,6 +325,7 @@ module Stackup
324
325
  if wait?
325
326
  status = modify_stack_synchronously(&block)
326
327
  raise StackUpdateError, failure_message unless target_status === status
328
+
327
329
  status
328
330
  else
329
331
  modify_stack_asynchronously(&block)
@@ -344,6 +346,7 @@ module Stackup
344
346
  status = self.status
345
347
  logger.debug("stack_status=#{status}")
346
348
  return status if status.nil? || status =~ /_(COMPLETE|FAILED)$/
349
+
347
350
  sleep(wait_poll_interval)
348
351
  end
349
352
  end
@@ -22,6 +22,7 @@ module Stackup
22
22
  new_events = []
23
23
  stack.events.each do |event|
24
24
  break if event.id == @last_processed_event_id
25
+
25
26
  new_events.unshift(event)
26
27
  end
27
28
  new_events.each do |event|
@@ -1,5 +1,5 @@
1
1
  module Stackup
2
2
 
3
- VERSION = "1.4.2".freeze
3
+ VERSION = "1.4.3".freeze
4
4
 
5
5
  end
@@ -24,6 +24,7 @@ module Stackup
24
24
  def load(yaml, filename = nil)
25
25
  tree = ::YAML.parse(yaml, filename)
26
26
  return tree unless tree
27
+
27
28
  CloudFormationToRuby.create.accept(tree)
28
29
  end
29
30
 
@@ -280,6 +280,22 @@ describe Stackup::Stack do
280
280
 
281
281
  end
282
282
 
283
+ context "with :capabilities as an Array" do
284
+
285
+ before do
286
+ options[:capabilities] = %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM]
287
+ end
288
+
289
+ it "passes them through" do
290
+ expected_capabilities = %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM]
291
+ create_or_update
292
+ expect(cf_client).to have_received(:create_stack) do |options|
293
+ expect(options[:capabilities]).to eq(expected_capabilities)
294
+ end
295
+ end
296
+
297
+ end
298
+
283
299
  end
284
300
 
285
301
  describe "#change_set#create" do
@@ -360,6 +376,53 @@ describe Stackup::Stack do
360
376
 
361
377
  end
362
378
 
379
+ context "with :capabilities as an Array" do
380
+
381
+ before do
382
+ options[:capabilities] = %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM]
383
+ end
384
+
385
+ it "passes them through" do
386
+ expected_capabilities = %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM]
387
+ create_change_set
388
+ expect(cf_client).to have_received(:create_change_set) do |options|
389
+ expect(options[:capabilities]).to eq(expected_capabilities)
390
+ end
391
+ end
392
+
393
+ end
394
+
395
+ end
396
+
397
+ describe "#change_set#execute" do
398
+
399
+ let(:change_set_name) { "create-it" }
400
+
401
+ let(:describe_stacks_responses) do
402
+ [
403
+ stack_description("CREATE_IN_PROGRESS"),
404
+ stack_description("CREATE_COMPLETE")
405
+ ]
406
+ end
407
+
408
+ def execute_change_set
409
+ stack.change_set(change_set_name).execute
410
+ end
411
+
412
+ it "calls :execute_change_set" do
413
+ execute_change_set
414
+ expected_args = {
415
+ :stack_name => stack_name,
416
+ :change_set_name => change_set_name
417
+ }
418
+ expect(cf_client).to have_received(:execute_change_set)
419
+ .with(hash_including(expected_args))
420
+ end
421
+
422
+ it "returns status" do
423
+ expect(execute_change_set).to eq("CREATE_COMPLETE")
424
+ end
425
+
363
426
  end
364
427
 
365
428
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackup
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Williams
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-01-21 00:00:00.000000000 Z
12
+ date: 2019-09-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk-cloudformation
@@ -134,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  - !ruby/object:Gem::Version
135
135
  version: '0'
136
136
  requirements: []
137
- rubyforge_project:
138
- rubygems_version: 2.7.6
137
+ rubygems_version: 3.0.3
139
138
  signing_key:
140
139
  specification_version: 4
141
140
  summary: Manage CloudFormation stacks