stackup 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ba465cd7978cabe9b222a5053e0dcafbf8334e3
4
- data.tar.gz: 731b59587827b376025ad29b769f3495aec6361c
3
+ metadata.gz: a2f56d7cc442a15b14e0df0bd2ed9b9c39b34197
4
+ data.tar.gz: 187f7372696c5f6ae84530e218bb9c0c0bf691bd
5
5
  SHA512:
6
- metadata.gz: 32fe3ab0b69a2c80a00ef6d41baea6a75732ed0e46ed8fef37c14ac14248d2aa15203309010eb3e262f42ad018aec828ba77abf78ff47a6ae1a662931382ee56
7
- data.tar.gz: 5455f7e5e49d56e892fdc3a4964d29b7e0f9715837097a2d7afef165395897532fda6cacbadfe737cc8982eb85a818e3c44661fd619f9a599ed0f550838b748d
6
+ metadata.gz: f33df243de76851d2f63c8776b8ee44eb2634219fb838adee0d096ecca2fffc19feeb2effcc50aadded2d0f4e04dec2fd7a5640045fe4362782b19874ae81bc1
7
+ data.tar.gz: b72c70703f52d5be553ffff3a2c98e360b7cf008aa8f03c993d3920888c84d897a55d38ab8705655d567b6bec52c1b31fa7e377ee3ecb86650e7188cb28e7ae9
data/README.md CHANGED
@@ -37,6 +37,8 @@ Called with `--list`, it will list stacks:
37
37
  foo-bar-test
38
38
  zzz-production
39
39
 
40
+ The command-line support inputs (template and parameters) in either JSON or YAML format.
41
+
40
42
  ### Stack create/update
41
43
 
42
44
  Use sub-command "up" to create or update a stack, as appropriate:
@@ -79,7 +81,7 @@ Stackup integrates with Rake to generate handy tasks for managing a stack, e.g.
79
81
 
80
82
  require "stackup/rake_tasks"
81
83
 
82
- Stackup::RakeTasks("app") do |t|
84
+ Stackup::RakeTasks.new("app") do |t|
83
85
  t.stack = "my-app"
84
86
  t.template = "app-template.json"
85
87
  end
@@ -90,3 +92,12 @@ providing tasks:
90
92
  rake app:down # Delete my-app stack
91
93
  rake app:inspect # Show my-app stack outputs and resources
92
94
  rake app:up # Update my-app stack
95
+
96
+ Parameters and tags may be specified via files, or as a Hash, e.g.
97
+
98
+ Stackup::RakeTasks.new("app") do |t|
99
+ t.stack = "my-app"
100
+ t.template = "app-template.json"
101
+ t.parameters = "production-params.json"
102
+ t.tags = { "environment" => "production" }
103
+ end
data/bin/stackup CHANGED
@@ -4,9 +4,9 @@ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
4
4
 
5
5
  require "clamp"
6
6
  require "console_logger"
7
- require "diffy"
8
7
  require "multi_json"
9
8
  require "stackup"
9
+ require "stackup/differ"
10
10
  require "stackup/version"
11
11
  require "yaml"
12
12
 
@@ -22,6 +22,14 @@ Clamp do
22
22
 
23
23
  option ["-Y", "--yaml"], :flag, "output data in YAML format"
24
24
 
25
+ option ["--region"], "REGION", "set region" do |arg|
26
+ unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
27
+ fail ArgumentError, "#{arg.inspect} doesn't look like a region"
28
+ end
29
+ Aws.config.update(:region => arg)
30
+ arg
31
+ end
32
+
25
33
  option "--debug", :flag, "enable debugging"
26
34
 
27
35
  option ["--version"], :flag, "display version" do
@@ -103,6 +111,13 @@ Clamp do
103
111
  option ["-p", "--parameters"], "FILE", "parameters file",
104
112
  :attribute_name => :parameters_file
105
113
 
114
+ option ["-o", "--override"], "PARAM=VALUE", "parameters overrides",
115
+ :multivalued => true,
116
+ :attribute_name => :override_list
117
+
118
+ option "--tags", "FILE", "stack tags file",
119
+ :attribute_name => :tags_file
120
+
106
121
  option "--policy", "FILE", "stack policy file",
107
122
  :attribute_name => :policy_file
108
123
 
@@ -117,7 +132,8 @@ Clamp do
117
132
  options = {}
118
133
  options[:template] = load_data(template_file) if template_file
119
134
  options[:on_failure] = on_failure
120
- options[:parameters] = load_data(parameters_file) if parameters_file
135
+ options[:parameters] = parameters
136
+ options[:tags] = load_data(tags_file) if tags_file
121
137
  options[:stack_policy] = load_data(policy_file) if policy_file
122
138
  options[:use_previous_template] = use_previous_template?
123
139
  report_change do
@@ -125,6 +141,26 @@ Clamp do
125
141
  end
126
142
  end
127
143
 
144
+ private
145
+
146
+ def parameters
147
+ parameters_from_file.merge(parameter_overrides)
148
+ end
149
+
150
+ def parameters_from_file
151
+ return {} unless parameters_file
152
+ Stackup::Parameters.new(load_data(parameters_file)).to_hash
153
+ end
154
+
155
+ def parameter_overrides
156
+ {}.tap do |result|
157
+ override_list.each do |override|
158
+ key, value = override.split("=", 2)
159
+ result[key] = value
160
+ end
161
+ end
162
+ end
163
+
128
164
  end
129
165
 
130
166
  subcommand "diff", "Compare template/params to current stack." do
@@ -135,41 +171,45 @@ Clamp do
135
171
  option ["-p", "--parameters"], "FILE", "parameters file",
136
172
  :attribute_name => :parameters_file
137
173
 
174
+ option "--tags", "FILE", "stack tags file",
175
+ :attribute_name => :tags_file
176
+
138
177
  option "--diff-format", "FORMAT", "'text', 'color', or 'html'", :default => "color"
139
178
 
140
179
  def execute
141
- # rubocop:disable Style/GuardClause
180
+ current = {}
181
+ planned = {}
142
182
  if template_file
143
- template = load_data(template_file)
144
- diff_data(stack.template, template)
183
+ current["Template"] = stack.template
184
+ planned["Template"] = load_data(template_file)
145
185
  end
146
186
  if parameters_file
147
- new_parameters = hashify_parameters(load_data(parameters_file))
148
- existing_parameters = stack.parameters
149
- new_parameters = existing_parameters.merge(new_parameters)
150
- diff_data(existing_parameters.sort.to_h, new_parameters.sort.to_h)
187
+ current["Parameters"] = existing_parameters.sort.to_h
188
+ planned["Parameters"] = new_parameters.sort.to_h
189
+ end
190
+ if tags_file
191
+ current["Tags"] = stack.tags.sort.to_h
192
+ planned["Tags"] = load_data(tags_file).sort.to_h
151
193
  end
194
+ signal_usage_error "specify '--template' or '--parameters'" if planned.empty?
195
+ puts differ.diff(current, planned)
152
196
  end
153
197
 
154
198
  private
155
199
 
156
- def diff_data(existing_data, pending_data)
157
- existing = format_data(existing_data) + "\n"
158
- pending = format_data(pending_data) + "\n"
159
- diff = Diffy::Diff.new(existing, pending).to_s(diff_format.to_sym)
160
- return if diff =~ /\A\s*\Z/
161
- puts diff
200
+ def differ
201
+ Stackup::Differ.new(diff_format, &method(:format_data))
162
202
  end
163
203
 
164
- def hashify_parameters(parameters)
165
- return parameters unless parameters.is_a?(Array)
166
- {}.tap do |result|
167
- parameters.each do |p|
168
- key = p.fetch("ParameterKey") { p.fetch("parameter_key") }
169
- value = p.fetch("ParameterValue") { p.fetch("parameter_value") }
170
- result[key] = value
171
- end
172
- end
204
+ def existing_parameters
205
+ @existing_parameters ||= stack.parameters
206
+ end
207
+
208
+ def new_parameters
209
+ result = load_data(parameters_file)
210
+ result = Stackup::Parameters.new(result).to_hash
211
+ result = existing_parameters.merge(result)
212
+ result.sort
173
213
  end
174
214
 
175
215
  end
@@ -284,12 +324,13 @@ Clamp do
284
324
 
285
325
  end
286
326
 
287
- subcommand "inspect", "status + parameters + resources + outputs" do
327
+ subcommand "inspect", "Display stack particulars" do
288
328
 
289
329
  def execute
290
330
  data = {
291
331
  "Status" => stack.status,
292
332
  "Parameters" => stack.parameters,
333
+ "Tags" => stack.tags,
293
334
  "Resources" => stack.resources,
294
335
  "Outputs" => stack.outputs
295
336
  }
data/examples/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "stackup/rake_tasks"
2
+
3
+ Stackup::RakeTasks.new("demo") do |t|
4
+ t.stack = ENV.fetch("STACKUP_DEMO_STACK", "stackup-demo")
5
+ t.template = "template.json"
6
+ t.parameters = "parameters-verbose.json"
7
+ t.tags = {
8
+ "environment" => "dev",
9
+ "team" => "rea-oss"
10
+ }
11
+ end
File without changes
@@ -0,0 +1,32 @@
1
+ require "diffy"
2
+ require "yaml"
3
+
4
+ module Stackup
5
+
6
+ # Generates diffs of data.
7
+ #
8
+ class Differ
9
+
10
+ def initialize(diff_style = :color, &data_formatter)
11
+ @diff_style = diff_style.to_sym
12
+ @data_formatter = data_formatter || YAML.method(:dump)
13
+ end
14
+
15
+ attr_reader :diff_style
16
+
17
+ def diff(existing_data, pending_data)
18
+ existing = format(existing_data) + "\n"
19
+ pending = format(pending_data) + "\n"
20
+ diff = Diffy::Diff.new(existing, pending).to_s(diff_style)
21
+ diff unless diff =~ /\A\s*\Z/
22
+ end
23
+
24
+ private
25
+
26
+ def format(data)
27
+ @data_formatter.call(data)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,76 @@
1
+ module Stackup
2
+
3
+ # Parameters to a CloudFormation template.
4
+ #
5
+ class Parameters
6
+
7
+ class << self
8
+
9
+ def new(arg)
10
+ arg = hashify(arg) unless arg.is_a?(Hash)
11
+ super(arg)
12
+ end
13
+
14
+ private
15
+
16
+ def hashify(parameters)
17
+ {}.tap do |result|
18
+ parameters.each do |p|
19
+ p_struct = ParameterStruct.new(p)
20
+ result[p_struct.key] = p_struct.value
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ def initialize(parameter_hash)
28
+ @parameter_hash = parameter_hash
29
+ end
30
+
31
+ def to_hash
32
+ @parameter_hash.dup
33
+ end
34
+
35
+ def to_a
36
+ @parameter_hash.map do |key, value|
37
+ { :parameter_key => key }.tap do |record|
38
+ if value == :use_previous_value
39
+ record[:use_previous_value] = true
40
+ else
41
+ record[:parameter_value] = value
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ class ParameterStruct
50
+
51
+ def initialize(attributes)
52
+ attributes.each do |name, value|
53
+ if name.respond_to?(:gsub)
54
+ name = name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
55
+ end
56
+ public_send("#{name}=", value)
57
+ end
58
+ end
59
+
60
+ attr_accessor :parameter_key
61
+ attr_accessor :parameter_value
62
+ attr_accessor :use_previous_value
63
+
64
+ alias_method :key, :parameter_key
65
+
66
+ def value
67
+ if use_previous_value
68
+ :use_previous_value
69
+ else
70
+ parameter_value
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -1,4 +1,6 @@
1
1
  require "rake/tasklib"
2
+ require "tempfile"
3
+ require "yaml"
2
4
 
3
5
  module Stackup
4
6
 
@@ -10,6 +12,7 @@ module Stackup
10
12
  attr_accessor :stack
11
13
  attr_accessor :template
12
14
  attr_accessor :parameters
15
+ attr_accessor :tags
13
16
 
14
17
  alias_method :namespace=, :name=
15
18
 
@@ -23,24 +26,24 @@ module Stackup
23
26
  define
24
27
  end
25
28
 
29
+ # path to the "stackup" executable
30
+ STACKUP_CLI = File.expand_path("../../../bin/stackup", __FILE__)
31
+
26
32
  def stackup(*rest)
27
- sh "stackup", stack, *rest
33
+ sh STACKUP_CLI, "-Y", stack, *rest
28
34
  end
29
35
 
30
36
  def define
31
37
  namespace(name) do
32
38
 
33
- up_args = ["-t", template]
34
- up_deps = [template]
35
-
36
- if parameters
37
- up_args += ["-p", parameters]
38
- up_deps += [parameters]
39
- end
39
+ data_options = DataOptions.new
40
+ data_options["--template"] = template
41
+ data_options["--parameters"] = parameters if parameters
42
+ data_options["--tags"] = tags if tags
40
43
 
41
44
  desc "Update #{stack} stack"
42
- task "up" => up_deps do
43
- stackup "up", *up_args
45
+ task "up" => data_options.files do
46
+ stackup "up", *data_options.to_a
44
47
  end
45
48
 
46
49
  desc "Cancel update of #{stack} stack"
@@ -49,8 +52,8 @@ module Stackup
49
52
  end
50
53
 
51
54
  desc "Show pending changes to #{stack} stack"
52
- task "diff" => up_deps do
53
- stackup "diff", *up_args
55
+ task "diff" => data_options.files do
56
+ stackup "diff", *data_options.to_a
54
57
  end
55
58
 
56
59
  desc "Show #{stack} stack outputs and resources"
@@ -66,6 +69,41 @@ module Stackup
66
69
  end
67
70
  end
68
71
 
72
+ # Options to "stackup up".
73
+ #
74
+ class DataOptions
75
+
76
+ def initialize
77
+ @options = {}
78
+ end
79
+
80
+ def []=(option, file_or_value)
81
+ @options[option] = file_or_value
82
+ end
83
+
84
+ def files
85
+ @options.values.grep(String)
86
+ end
87
+
88
+ def to_a
89
+ [].tap do |result|
90
+ @options.each do |option, file_or_data|
91
+ result << option
92
+ result << maybe_tempfile(file_or_data, option[2..-1])
93
+ end
94
+ end
95
+ end
96
+
97
+ def maybe_tempfile(file_or_data, type)
98
+ return file_or_data if file_or_data.is_a?(String)
99
+ tempfile = Tempfile.new([type, ".yml"])
100
+ tempfile.write(YAML.dump(file_or_data))
101
+ tempfile.close
102
+ tempfile.path.to_s
103
+ end
104
+
105
+ end
106
+
69
107
  end
70
108
 
71
109
  end
data/lib/stackup/stack.rb CHANGED
@@ -2,6 +2,7 @@ require "aws-sdk-resources"
2
2
  require "logger"
3
3
  require "multi_json"
4
4
  require "stackup/error_handling"
5
+ require "stackup/parameters"
5
6
  require "stackup/stack_watcher"
6
7
 
7
8
  module Stackup
@@ -65,8 +66,11 @@ module Stackup
65
66
  # @option options [String] :on_failure (ROLLBACK)
66
67
  # if stack creation fails: DO_NOTHING, ROLLBACK, or DELETE
67
68
  # @option options [Hash, Array<Hash>] :parameters
68
- # stack parameters, either as a Hash, or as an Array of
69
+ # stack parameters, either as a Hash, or an Array of
69
70
  # +Aws::CloudFormation::Types::Parameter+ structures
71
+ # @option options [Hash, Array<Hash>] :tags
72
+ # stack tags, either as a Hash, or an Array of
73
+ # +Aws::CloudFormation::Types::Tag+ structures
70
74
  # @option options [Array<String>] :resource_types
71
75
  # resource types that you have permissions to work with
72
76
  # @option options [Hash] :stack_policy
@@ -100,7 +104,10 @@ module Stackup
100
104
  options[:template_body] = MultiJson.dump(template_data)
101
105
  end
102
106
  if (parameters = options[:parameters])
103
- options[:parameters] = normalise_parameters(parameters)
107
+ options[:parameters] = Parameters.new(parameters).to_a
108
+ end
109
+ if (tags = options[:tags])
110
+ options[:tags] = normalize_tags(tags)
104
111
  end
105
112
  if (policy_data = options.delete(:stack_policy))
106
113
  options[:stack_policy_body] = MultiJson.dump(policy_data)
@@ -188,13 +195,16 @@ module Stackup
188
195
  # @raise [Stackup::NoSuchStack] if the stack doesn't exist
189
196
  #
190
197
  def parameters
191
- handling_validation_error do
192
- {}.tap do |h|
193
- cf_stack.parameters.each do |p|
194
- h[p.parameter_key] = p.parameter_value
195
- end
196
- end
197
- end
198
+ extract_hash(:parameters, :parameter_key, :parameter_value)
199
+ end
200
+
201
+ # Get the current tags.
202
+ #
203
+ # @return [Hash] current stack tags
204
+ # @raise [Stackup::NoSuchStack] if the stack doesn't exist
205
+ #
206
+ def tags
207
+ extract_hash(:tags, :key, :value)
198
208
  end
199
209
 
200
210
  # Get stack outputs.
@@ -203,13 +213,7 @@ module Stackup
203
213
  # @raise [Stackup::NoSuchStack] if the stack doesn't exist
204
214
  #
205
215
  def outputs
206
- handling_validation_error do
207
- {}.tap do |h|
208
- cf_stack.outputs.each do |o|
209
- h[o.output_key] = o.output_value
210
- end
211
- end
212
- end
216
+ extract_hash(:outputs, :output_key, :output_value)
213
217
  end
214
218
 
215
219
  # Get stack outputs.
@@ -219,13 +223,7 @@ module Stackup
219
223
  # @raise [Stackup::NoSuchStack] if the stack doesn't exist
220
224
  #
221
225
  def resources
222
- handling_validation_error do
223
- {}.tap do |h|
224
- cf_stack.resource_summaries.each do |r|
225
- h[r.logical_resource_id] = r.physical_resource_id
226
- end
227
- end
228
- end
226
+ extract_hash(:resource_summaries, :logical_resource_id, :physical_resource_id)
229
227
  end
230
228
 
231
229
  def watch(zero = true)
@@ -268,6 +266,7 @@ module Stackup
268
266
  fail StackUpdateError, "stack update failed" unless status == "UPDATE_COMPLETE"
269
267
  status
270
268
  rescue NoUpdateRequired
269
+ logger.info "No update required"
271
270
  nil
272
271
  end
273
272
 
@@ -279,7 +278,8 @@ module Stackup
279
278
  def event_handler
280
279
  @event_handler ||= lambda do |e|
281
280
  fields = [e.logical_resource_id, e.resource_status, e.resource_status_reason]
282
- logger.info(fields.compact.join(" - "))
281
+ time = e.timestamp.localtime.strftime("%H:%M:%S")
282
+ logger.info("[#{time}] #{fields.compact.join(' - ')}")
283
283
  end
284
284
  end
285
285
 
@@ -302,33 +302,35 @@ module Stackup
302
302
  end
303
303
  end
304
304
 
305
- def normalise_parameters(arg)
306
- if arg.is_a?(Hash)
307
- return arg.map do |key, value|
308
- {
309
- :parameter_key => key,
310
- :parameter_value => value
311
- }
305
+ def normalize_tags(tags)
306
+ if tags.is_a?(Hash)
307
+ tags.map do |key, value|
308
+ { :key => key, :value => value }
312
309
  end
313
- end
314
- arg.map do |parameter|
315
- normalise_parameter_keys(parameter)
310
+ else
311
+ tags
316
312
  end
317
313
  end
318
314
 
319
- def normalise_parameter_keys(input)
320
- {}.tap do |output|
321
- input.each do |key, value|
322
- output[normalise_parameter_key(key)] = value
315
+ # Extract data from a collection attribute of the stack.
316
+ #
317
+ # @param [Symbol] collection_name collection attribute name
318
+ # @param [Symbol] key_name name of item attribute that provides key
319
+ # @param [Symbol] value_name name of item attribute that provides value
320
+ # @return [Hash<String, String>] mapping of collection
321
+ #
322
+ def extract_hash(collection_name, key_name, value_name)
323
+ handling_validation_error do
324
+ {}.tap do |result|
325
+ cf_stack.public_send(collection_name).each do |item|
326
+ key = item.public_send(key_name)
327
+ value = item.public_send(value_name)
328
+ result[key] = value
329
+ end
323
330
  end
324
331
  end
325
332
  end
326
333
 
327
- def normalise_parameter_key(s)
328
- return s if s.is_a?(Symbol)
329
- s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
330
- end
331
-
332
334
  end
333
335
 
334
336
  end
@@ -1,5 +1,5 @@
1
1
  module Stackup
2
2
 
3
- VERSION = "0.7.0"
3
+ VERSION = "0.8.0"
4
4
 
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ module CfStubbing
4
4
 
5
5
  def stub_cf_client
6
6
  client_options = { :stub_responses => true }
7
+ client_options[:logger] = Logger.new(nil)
7
8
  if ENV.key?("AWS_DEBUG")
8
9
  client_options[:logger] = ConsoleLogger.new(STDOUT, true)
9
10
  client_options[:log_level] = :debug
@@ -0,0 +1,149 @@
1
+ require "spec_helper"
2
+
3
+ require "stackup/parameters"
4
+
5
+ describe Stackup::Parameters do
6
+
7
+ context "constructed with a Hash" do
8
+
9
+ let(:input_hash) do
10
+ {
11
+ "Ami" => "ami-123",
12
+ "VpcId" => "vpc-456"
13
+ }
14
+ end
15
+
16
+ subject(:parameters) { Stackup::Parameters.new(input_hash) }
17
+
18
+ describe "#to_hash" do
19
+
20
+ it "returns the original Hash" do
21
+ expect(parameters.to_hash).to eql(input_hash)
22
+ end
23
+
24
+ end
25
+
26
+ describe "#to_a" do
27
+
28
+ it "returns an array of parameter records" do
29
+ expected = [
30
+ { :parameter_key => "Ami", :parameter_value => "ami-123" },
31
+ { :parameter_key => "VpcId", :parameter_value => "vpc-456" }
32
+ ]
33
+ expect(parameters.to_a).to eql(expected)
34
+ end
35
+
36
+ end
37
+
38
+ context "with a :use_previous_value" do
39
+
40
+ let(:input_hash) do
41
+ {
42
+ "Ami" => :use_previous_value
43
+ }
44
+ end
45
+
46
+ describe "#to_hash" do
47
+
48
+ it "returns the original Hash" do
49
+ expect(parameters.to_hash).to eql(input_hash)
50
+ end
51
+
52
+ end
53
+
54
+ describe "#to_a" do
55
+
56
+ it "returns a record with :use_previous_value true" do
57
+ expected = [
58
+ { :parameter_key => "Ami", :use_previous_value => true }
59
+ ]
60
+ expect(parameters.to_a).to eql(expected)
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ context "constructed with an array of parameter records" do
70
+
71
+ context "with symbol keys" do
72
+
73
+ let(:input_records) do
74
+ [
75
+ { :parameter_key => "Ami", :parameter_value => "ami-123" },
76
+ { :parameter_key => "VpcId", :parameter_value => "vpc-456" }
77
+ ]
78
+ end
79
+
80
+ subject(:parameters) { Stackup::Parameters.new(input_records) }
81
+
82
+ describe "#to_hash" do
83
+
84
+ it "returns an equivalent Hash" do
85
+ expected = {
86
+ "Ami" => "ami-123",
87
+ "VpcId" => "vpc-456"
88
+ }
89
+ expect(parameters.to_hash).to eql(expected)
90
+ end
91
+
92
+ end
93
+
94
+ describe "#to_a" do
95
+
96
+ it "returns the array form" do
97
+ expect(parameters.to_a).to eql(input_records)
98
+ end
99
+
100
+ end
101
+
102
+ context "with :use_previous_value" do
103
+
104
+ let(:input_records) do
105
+ [
106
+ { :parameter_key => "Ami", :use_previous_value => true }
107
+ ]
108
+ end
109
+
110
+ describe "#to_hash" do
111
+
112
+ it "marks the corresponding parameter as :use_previous_value" do
113
+ expect(parameters.to_hash).to eql("Ami" => :use_previous_value)
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ context "with String keys" do
123
+
124
+ let(:input_records) do
125
+ [
126
+ { "ParameterKey" => "Ami", "ParameterValue" => "ami-123" },
127
+ { "ParameterKey" => "VpcId", "ParameterValue" => "vpc-456" }
128
+ ]
129
+ end
130
+
131
+ subject(:parameters) { Stackup::Parameters.new(input_records) }
132
+
133
+ describe "#to_hash" do
134
+
135
+ it "returns an equivalent Hash" do
136
+ expected = {
137
+ "Ami" => "ami-123",
138
+ "VpcId" => "vpc-456"
139
+ }
140
+ expect(parameters.to_hash).to eql(expected)
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -33,7 +33,7 @@ describe Stackup::Stack do
33
33
  }
34
34
  end
35
35
 
36
- def stack_description(stack_status)
36
+ def stack_description(stack_status, details = {})
37
37
  {
38
38
  :stacks => [
39
39
  {
@@ -41,7 +41,7 @@ describe Stackup::Stack do
41
41
  :stack_id => unique_stack_id,
42
42
  :stack_name => stack_name,
43
43
  :stack_status => stack_status
44
- }
44
+ }.merge(details)
45
45
  ]
46
46
  }
47
47
  end
@@ -166,11 +166,11 @@ describe Stackup::Stack do
166
166
  }]
167
167
  end
168
168
 
169
- it "converts them to an Array" do
169
+ it "converts the keys to aws-sdk form" do
170
170
  expected_parameters = [
171
171
  {
172
- "parameter_key" => "foo",
173
- "parameter_value" => "bar"
172
+ :parameter_key => "foo",
173
+ :parameter_value => "bar"
174
174
  }
175
175
  ]
176
176
  create_or_update
@@ -181,6 +181,44 @@ describe Stackup::Stack do
181
181
 
182
182
  end
183
183
 
184
+ context "with :tags as Hash" do
185
+
186
+ before do
187
+ options[:tags] = { "foo" => "bar" }
188
+ end
189
+
190
+ it "converts them to an Array" do
191
+ expected_tags = [
192
+ { :key => "foo", :value => "bar" }
193
+ ]
194
+ create_or_update
195
+ expect(cf_client).to have_received(:create_stack) do |options|
196
+ expect(options[:tags]).to eq(expected_tags)
197
+ end
198
+ end
199
+
200
+ end
201
+
202
+ context "with :tags in SDK form" do
203
+
204
+ before do
205
+ options[:tags] = [
206
+ { :key => "foo", :value => "bar" }
207
+ ]
208
+ end
209
+
210
+ it "passes them through" do
211
+ expected_tags = [
212
+ { :key => "foo", :value => "bar" }
213
+ ]
214
+ create_or_update
215
+ expect(cf_client).to have_received(:create_stack) do |options|
216
+ expect(options[:tags]).to eq(expected_tags)
217
+ end
218
+ end
219
+
220
+ end
221
+
184
222
  end
185
223
 
186
224
  end
@@ -207,6 +245,26 @@ describe Stackup::Stack do
207
245
  end
208
246
  end
209
247
 
248
+ describe "#tags" do
249
+
250
+ let(:tags) do
251
+ [
252
+ { :key => "foo", :value => "bar" }
253
+ ]
254
+ end
255
+
256
+ let(:describe_stacks_responses) do
257
+ [
258
+ stack_description(stack_status, :tags => tags)
259
+ ]
260
+ end
261
+
262
+ it "returns tags as a Hash" do
263
+ expect(stack.tags).to eq("foo" => "bar")
264
+ end
265
+
266
+ end
267
+
210
268
  describe "#delete" do
211
269
 
212
270
  context "if successful" do
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: 0.7.0
4
+ version: 0.8.0
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: 2016-02-19 00:00:00.000000000 Z
12
+ date: 2016-03-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk-resources
@@ -99,18 +99,22 @@ files:
99
99
  - README.md
100
100
  - Rakefile
101
101
  - bin/stackup
102
- - examples/parameters.json
103
- - examples/parameters.yml
102
+ - examples/Rakefile
103
+ - examples/parameters-terse.yml
104
+ - examples/parameters-verbose.json
104
105
  - examples/template.json
105
106
  - lib/stackup.rb
107
+ - lib/stackup/differ.rb
106
108
  - lib/stackup/error_handling.rb
107
109
  - lib/stackup/errors.rb
110
+ - lib/stackup/parameters.rb
108
111
  - lib/stackup/rake_tasks.rb
109
112
  - lib/stackup/service.rb
110
113
  - lib/stackup/stack.rb
111
114
  - lib/stackup/stack_watcher.rb
112
115
  - lib/stackup/version.rb
113
116
  - spec/spec_helper.rb
117
+ - spec/stackup/parameters_spec.rb
114
118
  - spec/stackup/stack_spec.rb
115
119
  - spec/stackup/stack_watcher_spec.rb
116
120
  - stackup.gemspec
@@ -134,11 +138,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
138
  version: '0'
135
139
  requirements: []
136
140
  rubyforge_project:
137
- rubygems_version: 2.5.2
141
+ rubygems_version: 2.6.1
138
142
  signing_key:
139
143
  specification_version: 4
140
144
  summary: Manage CloudFormation stacks
141
145
  test_files:
142
146
  - spec/spec_helper.rb
147
+ - spec/stackup/parameters_spec.rb
143
148
  - spec/stackup/stack_spec.rb
144
149
  - spec/stackup/stack_watcher_spec.rb