stackup 0.7.0 → 0.8.0

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