stackup 1.4.2 → 1.4.3

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