sparkle_formation 0.4.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +10 -5
- data/LICENSE +1 -1
- data/README.md +36 -181
- data/lib/sparkle_formation/error.rb +25 -0
- data/lib/sparkle_formation/sparkle.rb +344 -0
- data/lib/sparkle_formation/sparkle_attribute.rb +34 -19
- data/lib/sparkle_formation/sparkle_collection.rb +149 -0
- data/lib/sparkle_formation/sparkle_formation.rb +302 -174
- data/lib/sparkle_formation/sparkle_struct.rb +0 -17
- data/lib/sparkle_formation/utils.rb +0 -18
- data/lib/sparkle_formation/version.rb +1 -19
- data/lib/sparkle_formation.rb +4 -0
- data/sparkle_formation.gemspec +3 -2
- metadata +23 -5
@@ -1,25 +1,5 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Chris Roberts <chris@hw-ops.com>
|
3
|
-
# Copyright:: 2013, Heavy Water Operations, LLC
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
1
|
require 'sparkle_formation'
|
20
2
|
|
21
|
-
SparkleFormation::SparkleStruct.camel_keys = true
|
22
|
-
|
23
3
|
# Formation container
|
24
4
|
class SparkleFormation
|
25
5
|
|
@@ -35,6 +15,9 @@ class SparkleFormation
|
|
35
15
|
'registry'
|
36
16
|
]
|
37
17
|
|
18
|
+
# @return [String] default stack resource name
|
19
|
+
DEFAULT_STACK_RESOURCE = 'AWS::CloudFormation::Stack'
|
20
|
+
|
38
21
|
class << self
|
39
22
|
|
40
23
|
# @return [Hashish] loaded dynamics
|
@@ -106,12 +89,16 @@ class SparkleFormation
|
|
106
89
|
# to pass through when compiling ({:state => {}})
|
107
90
|
# @return [Hashish, SparkleStruct]
|
108
91
|
def compile(path, *args)
|
92
|
+
opts = args.detect{|i| i.is_a?(Hash) } || {}
|
93
|
+
if(spath = (opts.delete(:sparkle_path) || SparkleFormation.sparkle_path))
|
94
|
+
container = Sparkle.new(:root => spath)
|
95
|
+
path = container.get(:template, path)[:path]
|
96
|
+
end
|
109
97
|
formation = self.instance_eval(IO.read(path), path, 1)
|
110
98
|
if(args.delete(:sparkle))
|
111
99
|
formation
|
112
100
|
else
|
113
|
-
|
114
|
-
(comp_arg ? formation.compile(comp_arg) : formation.compile)._dump
|
101
|
+
formation.compile(opts)._dump
|
115
102
|
end
|
116
103
|
end
|
117
104
|
|
@@ -121,13 +108,9 @@ class SparkleFormation
|
|
121
108
|
# @yield block to execute
|
122
109
|
# @return [SparkleStruct] provided base or new struct
|
123
110
|
def build(base=nil, &block)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
@_struct = struct
|
128
|
-
else
|
129
|
-
block
|
130
|
-
end
|
111
|
+
struct = base || SparkleStruct.new
|
112
|
+
struct.instance_exec(&block)
|
113
|
+
struct
|
131
114
|
end
|
132
115
|
|
133
116
|
# Load component
|
@@ -136,6 +119,7 @@ class SparkleFormation
|
|
136
119
|
# @return [SparkleStruct] resulting struct
|
137
120
|
def load_component(path)
|
138
121
|
self.instance_eval(IO.read(path), path, 1)
|
122
|
+
@_struct
|
139
123
|
end
|
140
124
|
|
141
125
|
# Load all dynamics within a directory
|
@@ -205,17 +189,19 @@ class SparkleFormation
|
|
205
189
|
# @return [SparkleStruct]
|
206
190
|
def insert(dynamic_name, struct, *args, &block)
|
207
191
|
result = false
|
208
|
-
|
209
|
-
|
192
|
+
begin
|
193
|
+
dyn = struct._self.sparkle.get(:dynamic, dynamic_name)
|
194
|
+
raise dyn if dyn.is_a?(Exception)
|
195
|
+
result = struct.instance_exec(*args, &dyn[:block])
|
210
196
|
if(block_given?)
|
211
197
|
result.instance_exec(&block)
|
212
198
|
end
|
213
199
|
result = struct
|
214
|
-
|
200
|
+
rescue Error::NotFound::Dynamic
|
215
201
|
result = builtin_insert(dynamic_name, struct, *args, &block)
|
216
202
|
end
|
217
203
|
unless(result)
|
218
|
-
raise "Failed to locate requested dynamic block for insertion: #{dynamic_name} (valid: #{
|
204
|
+
raise "Failed to locate requested dynamic block for insertion: #{dynamic_name} (valid: #{struct._self.sparkle.dynamics.keys.sort.join(', ')})"
|
219
205
|
end
|
220
206
|
result
|
221
207
|
end
|
@@ -229,33 +215,18 @@ class SparkleFormation
|
|
229
215
|
# @note if symbol is provided for template, double underscores
|
230
216
|
# will be used for directory separator and dashes will match underscores
|
231
217
|
def nest(template, struct, *args, &block)
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
options = {}
|
237
|
-
end
|
238
|
-
spath = SparkleFormation.new('stub').sparkle_path
|
239
|
-
resource_name = [template.to_s.gsub(/(\/|__|-)/, '_'), *args].compact.join('_').to_sym
|
240
|
-
path = template.is_a?(Symbol) ? template.to_s.gsub('__', '/') : template.to_s
|
241
|
-
file = Dir.glob(File.join(spath, '**', '**', '*.rb')).detect do |local_path|
|
242
|
-
strip_path = local_path.sub(spath, '').sub(/^\//, '').tr('-', '_').sub('.rb', '')
|
243
|
-
strip_path == path
|
244
|
-
end
|
245
|
-
unless(file)
|
246
|
-
raise ArgumentError.new("Failed to locate nested stack file! (#{template.inspect} -> #{path.inspect})")
|
247
|
-
end
|
248
|
-
instance = self.instance_eval(IO.read(file), file, 1)
|
249
|
-
instance.parent = struct._self
|
250
|
-
instance.name = Bogo::Utility.camel(resource_name)
|
218
|
+
to_nest = struct._self.sparkle.get(:template, template)
|
219
|
+
resource_name = (args.empty? ? template.to_s.gsub('__', '_') : args.map{|a| Bogo::Utility.snake(a)}.join('_')).to_sym
|
220
|
+
nested_template = self.compile(to_nest[:path], :sparkle)
|
221
|
+
nested_template.parent = struct._self
|
251
222
|
struct.resources.set!(resource_name) do
|
252
|
-
type
|
223
|
+
type DEFAULT_STACK_RESOURCE
|
253
224
|
end
|
254
|
-
struct.resources
|
225
|
+
struct.resources[resource_name].properties.stack nested_template
|
255
226
|
if(block_given?)
|
256
|
-
struct.resources
|
227
|
+
struct.resources[resource_name].instance_exec(&block)
|
257
228
|
end
|
258
|
-
struct.resources
|
229
|
+
struct.resources[resource_name]
|
259
230
|
end
|
260
231
|
|
261
232
|
# Insert a builtin dynamic into a context
|
@@ -270,18 +241,19 @@ class SparkleFormation
|
|
270
241
|
_config ||= {}
|
271
242
|
return unless _name
|
272
243
|
resource_name = "#{_name}_#{_config.delete(:resource_name_suffix) || dynamic_name}".to_sym
|
273
|
-
|
244
|
+
struct.resources.set!(resource_name)
|
245
|
+
new_resource = struct.resources[resource_name]
|
274
246
|
new_resource.type lookup_key
|
275
247
|
properties = new_resource.properties
|
248
|
+
config_keys = _config.keys.zip(_config.keys.map{|k| snake(k).to_s.tr('_', '')})
|
276
249
|
SfnAws.resource(dynamic_name, :properties).each do |prop_name|
|
277
|
-
|
278
|
-
|
279
|
-
end.compact.first
|
250
|
+
key = (config_keys.detect{|k| k.last == snake(prop_name).to_s.tr('_', '')} || []).first
|
251
|
+
value = _config[key] if key
|
280
252
|
if(value)
|
281
253
|
if(value.is_a?(Proc))
|
282
|
-
properties
|
254
|
+
properties[prop_name].to_sym.instance_exec(&value)
|
283
255
|
else
|
284
|
-
properties.
|
256
|
+
properties.set!(prop_name, value)
|
285
257
|
end
|
286
258
|
end
|
287
259
|
end
|
@@ -304,8 +276,12 @@ class SparkleFormation
|
|
304
276
|
end
|
305
277
|
end
|
306
278
|
|
279
|
+
include Bogo::Memoization
|
280
|
+
|
307
281
|
# @return [Symbol] name of formation
|
308
|
-
|
282
|
+
attr_reader :name
|
283
|
+
# @return [Sparkle] parts store
|
284
|
+
attr_reader :sparkle
|
309
285
|
# @return [String] base path
|
310
286
|
attr_reader :sparkle_path
|
311
287
|
# @return [String] components path
|
@@ -320,11 +296,7 @@ class SparkleFormation
|
|
320
296
|
attr_reader :load_order
|
321
297
|
# @return [Hash] parameters for stack generation
|
322
298
|
attr_reader :parameters
|
323
|
-
# @return [
|
324
|
-
attr_accessor :compile_state
|
325
|
-
# @return [Proc] block to call for setting compile time parameters
|
326
|
-
attr_accessor :compile_time_parameter_setter
|
327
|
-
# @return [SparkleFormation] parent instance
|
299
|
+
# @return [SparkleFormation] parent stack
|
328
300
|
attr_accessor :parent
|
329
301
|
|
330
302
|
# Create new instance
|
@@ -340,65 +312,59 @@ class SparkleFormation
|
|
340
312
|
# @yield base context
|
341
313
|
def initialize(name, options={}, &block)
|
342
314
|
@name = name.to_sym
|
343
|
-
@
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
315
|
+
@component_paths = []
|
316
|
+
@sparkle = SparkleCollection.new
|
317
|
+
@sparkle.set_root(
|
318
|
+
Sparkle.new(
|
319
|
+
Smash.new.tap{|h|
|
320
|
+
s_path = options.fetch(:sparkle_path,
|
321
|
+
self.class.custom_paths[:sparkle_path]
|
322
|
+
)
|
323
|
+
if(s_path)
|
324
|
+
h[:root] = s_path
|
325
|
+
end
|
326
|
+
}
|
327
|
+
)
|
328
|
+
)
|
357
329
|
unless(options[:disable_aws_builtins])
|
358
330
|
require 'sparkle_formation/aws'
|
359
331
|
SfnAws.load!
|
360
332
|
end
|
361
|
-
@parameters = set_generation_parameters!(
|
362
|
-
|
363
|
-
options.fetch(:compile_time_parameters, {})
|
364
|
-
)
|
365
|
-
)
|
366
|
-
@components = SparkleStruct.hashish.new
|
333
|
+
@parameters = set_generation_parameters!(options.fetch(:parameters, {}))
|
334
|
+
@components = Smash.new
|
367
335
|
@load_order = []
|
368
336
|
@overrides = []
|
337
|
+
@parent = options[:parent]
|
369
338
|
if(block)
|
370
339
|
load_block(block)
|
371
340
|
end
|
372
341
|
@compiled = nil
|
373
342
|
end
|
374
343
|
|
375
|
-
#
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
# @yieldparam [SparkleFormation]
|
380
|
-
# @return [Proc, NilClass]
|
381
|
-
def compile_time_parameter_setter(&block)
|
382
|
-
if(block)
|
383
|
-
@compile_time_parameter_setter = block
|
344
|
+
# @return [SparkleFormation] root stack
|
345
|
+
def root
|
346
|
+
if(parent)
|
347
|
+
parent.root
|
384
348
|
else
|
385
|
-
|
386
|
-
@compile_time_parameter_setter
|
387
|
-
else
|
388
|
-
parent.nil? ? nil : parent.compile_time_parameter_setter
|
389
|
-
end
|
349
|
+
self
|
390
350
|
end
|
391
351
|
end
|
392
352
|
|
393
|
-
#
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
353
|
+
# @return [Array<SparkleFormation] path to root
|
354
|
+
def root_path
|
355
|
+
if(parent)
|
356
|
+
[*parent.root_path, self].compact
|
357
|
+
else
|
358
|
+
[self]
|
398
359
|
end
|
399
360
|
end
|
400
361
|
|
401
|
-
|
362
|
+
# @return [TrueClass, FalseClass] current stack is root
|
363
|
+
def root?
|
364
|
+
root == self
|
365
|
+
end
|
366
|
+
|
367
|
+
ALLOWED_GENERATION_PARAMETERS = ['type', 'default']
|
402
368
|
VALID_GENERATION_PARAMETER_TYPES = ['String', 'Number']
|
403
369
|
|
404
370
|
# Validation parameters used for template generation to ensure they
|
@@ -436,13 +402,13 @@ class SparkleFormation
|
|
436
402
|
# @return [self]
|
437
403
|
def load(*args)
|
438
404
|
args.each do |thing|
|
439
|
-
|
440
|
-
|
405
|
+
key = File.basename(thing.to_s).sub('.rb', '')
|
406
|
+
if(thing.is_a?(String))
|
407
|
+
# TODO: Test this!
|
408
|
+
components[key] = ->{ self.class.load_component(thing) }
|
441
409
|
else
|
442
|
-
|
410
|
+
components[key] = sparkle.get(:component, thing)[:block]
|
443
411
|
end
|
444
|
-
key = File.basename(path).sub('.rb', '')
|
445
|
-
components[key] = self.class.load_component(path)
|
446
412
|
@load_order << key
|
447
413
|
end
|
448
414
|
self
|
@@ -463,16 +429,11 @@ class SparkleFormation
|
|
463
429
|
# @option args [Hash] :state local state parameters
|
464
430
|
# @return [SparkleStruct]
|
465
431
|
def compile(args={})
|
466
|
-
if(args.has_key?(:state))
|
467
|
-
@compile_state = args[:state]
|
468
|
-
@compiled = nil
|
469
|
-
end
|
470
432
|
unless(@compiled)
|
471
|
-
set_compile_time_parameters!
|
472
433
|
compiled = SparkleStruct.new
|
473
434
|
compiled._set_self(self)
|
474
|
-
if(
|
475
|
-
compiled.set_state!(
|
435
|
+
if(args[:state])
|
436
|
+
compiled.set_state!(args[:state])
|
476
437
|
end
|
477
438
|
@load_order.each do |key|
|
478
439
|
self.class.build(compiled, &components[key])
|
@@ -483,9 +444,6 @@ class SparkleFormation
|
|
483
444
|
end
|
484
445
|
self.class.build(compiled, &override[:block])
|
485
446
|
end
|
486
|
-
if(compile_state)
|
487
|
-
compiled.outputs.compile_state.value MultiJson.dump(compile_state)
|
488
|
-
end
|
489
447
|
@compiled = compiled
|
490
448
|
end
|
491
449
|
@compiled
|
@@ -494,66 +452,239 @@ class SparkleFormation
|
|
494
452
|
# Clear compiled stack if cached and perform compilation again
|
495
453
|
#
|
496
454
|
# @return [SparkleStruct]
|
497
|
-
def recompile
|
498
|
-
|
499
|
-
compile
|
455
|
+
def recompile
|
456
|
+
unmemoize(:compile)
|
457
|
+
compile
|
458
|
+
end
|
459
|
+
|
460
|
+
# @return [Array<SparkleFormation>]
|
461
|
+
def nested_stacks(*args)
|
462
|
+
compile.resources.keys!.map do |key|
|
463
|
+
if(compile.resources[key].type == DEFAULT_STACK_RESOURCE)
|
464
|
+
result = [compile.resources[key].properties.stack]
|
465
|
+
if(args.include?(:with_resource))
|
466
|
+
result.push(compile[:resources][key])
|
467
|
+
end
|
468
|
+
if(args.include?(:with_name))
|
469
|
+
result.push(key)
|
470
|
+
end
|
471
|
+
result.size == 1 ? result.first : result
|
472
|
+
end
|
473
|
+
end.compact
|
500
474
|
end
|
501
475
|
|
502
476
|
# @return [TrueClass, FalseClass] includes nested stacks
|
503
|
-
def nested?
|
504
|
-
|
505
|
-
|
477
|
+
def nested?(stack_hash=nil)
|
478
|
+
stack_hash = compile.dump! unless stack_hash
|
479
|
+
!!stack_hash.fetch('Resources', {}).detect do |r_name, resource|
|
480
|
+
resource['Type'] == DEFAULT_STACK_RESOURCE
|
506
481
|
end
|
507
482
|
end
|
508
483
|
|
509
484
|
# @return [TrueClass, FalseClass] includes _only_ nested stacks
|
510
|
-
def isolated_nests?
|
511
|
-
|
512
|
-
|
513
|
-
resource['Type'] ==
|
485
|
+
def isolated_nests?(stack_hash=nil)
|
486
|
+
stack_hash = compile.dump! unless stack_hash
|
487
|
+
stack_hash.fetch('Resources', {}).all? do |name, resource|
|
488
|
+
resource['Type'] == DEFAULT_STACK_RESOURCE
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# @return [TrueClass, FalseClass] policies defined
|
493
|
+
def includes_policies?(stack_hash=nil)
|
494
|
+
stack_hash = compile.dump! unless stack_hash
|
495
|
+
stack_hash.fetch('Resources', {}).any? do |name, resource|
|
496
|
+
resource.has_key?('Policy')
|
514
497
|
end
|
515
498
|
end
|
516
499
|
|
517
|
-
#
|
518
|
-
# nested stacks, update refs to use sibling stack outputs where
|
519
|
-
# required and extract nested stack templates for remote persistence
|
500
|
+
# Generate policy for stack
|
520
501
|
#
|
521
|
-
# @
|
522
|
-
# @
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
502
|
+
# @return [Hash]
|
503
|
+
# @todo this is very AWS specific, so make this easy for swapping
|
504
|
+
def generate_policy
|
505
|
+
statements = []
|
506
|
+
compile.resources.keys!.each do |r_name|
|
507
|
+
r_object = compile.resources[r_name]
|
508
|
+
if(r_object['Policy'])
|
509
|
+
r_object['Policy'].keys!.each do |effect|
|
510
|
+
statements.push(
|
511
|
+
'Effect' => effect.to_s.capitalize,
|
512
|
+
'Action' => [r_object['Policy'][effect]].flatten.compact.map{|i| "Update:#{i}"},
|
513
|
+
'Resource' => "LogicalResourceId/#{r_name}",
|
514
|
+
'Principal' => '*'
|
515
|
+
)
|
516
|
+
end
|
517
|
+
r_object.delete!('Policy')
|
530
518
|
end
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
519
|
+
end
|
520
|
+
statements.push(
|
521
|
+
'Effect' => 'Allow',
|
522
|
+
'Action' => 'Update:*',
|
523
|
+
'Resource' => '*',
|
524
|
+
'Principal' => '*'
|
525
|
+
)
|
526
|
+
Smash.new('Statement' => statements)
|
527
|
+
end
|
528
|
+
|
529
|
+
# Apply nesting logic to stack
|
530
|
+
#
|
531
|
+
# @param nest_type [Symbol] values: :shallow, :deep (default: :deep)
|
532
|
+
# @return [Hash] dumped stack
|
533
|
+
# @note see specific version for expected block parameters
|
534
|
+
def apply_nesting(*args, &block)
|
535
|
+
if(args.include?(:shallow))
|
536
|
+
apply_shallow_nesting(&block)
|
537
|
+
else
|
538
|
+
apply_deep_nesting(&block)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
# Apply deeply nested stacks. This is the new nesting approach and
|
543
|
+
# does not bubble parameters up to the root stack. Parameters are
|
544
|
+
# isolated to the stack resource itself and output mapping is
|
545
|
+
# automatically applied.
|
546
|
+
#
|
547
|
+
# @yieldparam stack [SparkleFormation] stack instance
|
548
|
+
# @yieldparam resource [AttributeStruct] the stack resource
|
549
|
+
# @yieldparam s_name [String] stack resource name
|
550
|
+
# @yieldreturn [Hash] key/values to be merged into resource properties
|
551
|
+
# @return [Hash] dumped stack
|
552
|
+
def apply_deep_nesting(*args, &block)
|
553
|
+
outputs = collect_outputs
|
554
|
+
nested_stacks(:with_resource).each do |stack, resource|
|
555
|
+
unless(stack.nested_stacks.empty?)
|
556
|
+
stack.apply_deep_nesting(*args)
|
557
|
+
end
|
558
|
+
stack.compile.parameters.keys!.each do |parameter_name|
|
559
|
+
if(output_name = output_matched?(parameter_name, outputs.keys))
|
560
|
+
next if outputs[output_name] == stack
|
561
|
+
stack_output = stack.make_output_available(output_name, outputs)
|
562
|
+
resource.properties.parameters.set!(parameter_name, stack_output)
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|
566
|
+
if(block_given?)
|
567
|
+
extract_templates(&block)
|
568
|
+
end
|
569
|
+
compile.dump!
|
570
|
+
end
|
571
|
+
|
572
|
+
# Check if parameter name matches an output name
|
573
|
+
#
|
574
|
+
# @param p_name [String, Symbol] parameter name
|
575
|
+
# @param output_names [Array<String>] list of available outputs
|
576
|
+
# @return [String, NilClass] matching output name
|
577
|
+
# @note will auto downcase name prior to comparison
|
578
|
+
def output_matched?(p_name, output_names)
|
579
|
+
output_names.detect do |o_name|
|
580
|
+
Bogo::Utility.snake(o_name).tr('_', '') == Bogo::Utility.snake(p_name).tr('_', '')
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
# Extract output to make available for stack parameter usage at the
|
585
|
+
# current depth
|
586
|
+
#
|
587
|
+
# @param output_name [String] name of output
|
588
|
+
# @param outputs [Hash] listing of outputs
|
589
|
+
# @reutrn [Hash] reference to output value (used for setting parameter)
|
590
|
+
def make_output_available(output_name, outputs)
|
591
|
+
bubble_path = outputs[output_name].root_path - root_path
|
592
|
+
drip_path = root_path - outputs[output_name].root_path
|
593
|
+
bubble_path.each_slice(2) do |base_sparkle, ref_sparkle|
|
594
|
+
next unless ref_sparkle
|
595
|
+
base_sparkle.compile.outputs.set!(output_name).set!(:value, base_sparkle.compile.attr!(ref_sparkle.name, "Outputs.#{output_name}"))
|
596
|
+
end
|
597
|
+
if(bubble_path.empty?)
|
598
|
+
raise ArgumentError.new "Failed to detect available bubbling path for output `#{output_name}`. This may be due to a circular dependency!"
|
599
|
+
end
|
600
|
+
result = compile.attr!(bubble_path.first.name, "Outputs.#{output_name}")
|
601
|
+
if(drip_path.size > 1)
|
602
|
+
parent = drip_path.first.parent
|
603
|
+
drip_path.unshift(parent) if parent
|
604
|
+
drip_path.each_slice(2) do |base_sparkle, ref_sparkle|
|
605
|
+
next unless ref_sparkle
|
606
|
+
base_sparkle.compile.resources[ref_sparkle.name].properties.parameters.set!(output_name, result)
|
607
|
+
ref_sparkle.compile.parameters.set!(output_name){ type 'String' } # TODO <<<<------ type check and prop
|
608
|
+
result = compile.ref!(output_name)
|
542
609
|
end
|
543
610
|
end
|
544
|
-
|
611
|
+
result
|
612
|
+
end
|
613
|
+
|
614
|
+
# Extract and process nested stacks
|
615
|
+
#
|
616
|
+
# @yieldparam stack [SparkleFormation] stack instance
|
617
|
+
# @yieldparam resource [AttributeStruct] the stack resource
|
618
|
+
# @yieldparam s_name [String] stack resource name
|
619
|
+
# @yieldreturn [Hash] key/values to be merged into resource properties
|
620
|
+
def extract_templates(&block)
|
621
|
+
stack_template_extractor(nested_stacks(:with_resource, :with_name), &block)
|
622
|
+
end
|
623
|
+
|
624
|
+
# Run the stack extraction
|
625
|
+
#
|
626
|
+
# @param x_stacks [Array<Array<SparkleFormation, SparkleStruct, String>>]
|
627
|
+
def stack_template_extractor(x_stacks, &block)
|
628
|
+
x_stacks.each do |stack, resource, s_name|
|
629
|
+
unless(stack.nested_stacks.empty?)
|
630
|
+
stack_template_extractor(stack.nested_stacks(:with_resource, :with_name), &block)
|
631
|
+
end
|
632
|
+
resource.properties.set!(:stack, stack.compile.dump!)
|
633
|
+
block.call(s_name, stack, resource)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
# Apply shallow nesting. This style of nesting will bubble
|
638
|
+
# parameters up to the root stack. This type of nesting is the
|
639
|
+
# original and now deprecated, but remains for compat issues so any
|
640
|
+
# existing usage won't be automatically busted.
|
641
|
+
#
|
642
|
+
# @yieldparam resource_name [String] name of stack resource
|
643
|
+
# @yieldparam stack [SparkleFormation] nested stack
|
644
|
+
# @yieldreturn [String] Remote URL storage for template
|
645
|
+
# @return [Hash]
|
646
|
+
def apply_shallow_nesting(*args, &block)
|
647
|
+
parameters = compile[:parameters] ? compile[:parameters]._dump : {}
|
648
|
+
output_map = {}
|
649
|
+
nested_stacks(:with_resource, :with_name).each do |stack, stack_resource, stack_name|
|
650
|
+
remap_nested_parameters(compile, parameters, stack_name, stack_resource, output_map)
|
651
|
+
end
|
652
|
+
extract_templates(&block)
|
653
|
+
compile.parameters parameters
|
654
|
+
if(args.include?(:bubble_outputs))
|
545
655
|
outputs_hash = Hash[
|
546
656
|
output_map do |name, value|
|
547
657
|
[name, {'Value' => {'Fn::GetAtt' => value}}]
|
548
658
|
end
|
549
659
|
]
|
550
|
-
if(
|
551
|
-
|
660
|
+
if(compile.outputs)
|
661
|
+
compile._merge(SparkleStruct.new(outputs_hash))
|
662
|
+
else
|
663
|
+
compile.outputs output_hash
|
664
|
+
end
|
665
|
+
end
|
666
|
+
compile.dump!
|
667
|
+
end
|
668
|
+
|
669
|
+
# @return [Smash<output_name:SparkleFormation>]
|
670
|
+
def collect_outputs(*args)
|
671
|
+
if(args.include?(:force) || root?)
|
672
|
+
if(compile.outputs)
|
673
|
+
outputs = Smash[
|
674
|
+
compile.outputs.keys!.zip(
|
675
|
+
[self] * compile.outputs.keys!.size
|
676
|
+
)
|
677
|
+
]
|
552
678
|
else
|
553
|
-
|
679
|
+
outputs = Smash.new
|
554
680
|
end
|
681
|
+
nested_stacks.each do |nested_stack|
|
682
|
+
outputs = nested_stack.collect_outputs(:force).merge(outputs)
|
683
|
+
end
|
684
|
+
outputs
|
685
|
+
else
|
686
|
+
root.collect_outputs(:force)
|
555
687
|
end
|
556
|
-
hash
|
557
688
|
end
|
558
689
|
|
559
690
|
# Extract parameters from nested stacks. Check for previous nested
|
@@ -571,10 +702,9 @@ class SparkleFormation
|
|
571
702
|
# @note if parameter has includes `StackUnique` a new parameter will
|
572
703
|
# be added to container stack and it will not use outputs
|
573
704
|
def remap_nested_parameters(template, parameters, stack_name, stack_resource, output_map)
|
574
|
-
stack_parameters = stack_resource
|
575
|
-
|
576
|
-
|
577
|
-
stack_parameters.each do |pname, pval|
|
705
|
+
stack_parameters = stack_resource.properties.stack.compile.parameters
|
706
|
+
unless(stack_parameters.nil?)
|
707
|
+
stack_parameters._dump.each do |pname, pval|
|
578
708
|
if(pval['StackUnique'])
|
579
709
|
check_name = [stack_name, pname].join
|
580
710
|
else
|
@@ -586,24 +716,22 @@ class SparkleFormation
|
|
586
716
|
else
|
587
717
|
new_val = {'Ref' => check_name}
|
588
718
|
end
|
589
|
-
template
|
719
|
+
template.resources.set!(stack_name).properties.parameters.set!(pname, new_val)
|
590
720
|
elsif(output_map[check_name])
|
591
|
-
template
|
592
|
-
'Fn::GetAtt' => output_map[check_name]
|
593
|
-
}
|
721
|
+
template.resources.set!(stack_name).properties.parameters.set!(pname, 'Fn::GetAtt' => output_map[check_name])
|
594
722
|
else
|
595
723
|
if(pval['Type'] == 'CommaDelimitedList')
|
596
724
|
new_val = {'Fn::Join' => [',', {'Ref' => check_name}]}
|
597
725
|
else
|
598
726
|
new_val = {'Ref' => check_name}
|
599
727
|
end
|
600
|
-
template
|
728
|
+
template.resources.set!(stack_name).properties.parameters.set!(pname, new_val)
|
601
729
|
parameters[check_name] = pval
|
602
730
|
end
|
603
731
|
end
|
604
732
|
end
|
605
|
-
|
606
|
-
stack_resource
|
733
|
+
unless(stack_resource.properties.stack.compile.outputs.nil?)
|
734
|
+
stack_resource.properties.stack.compile.outputs.keys!.each do |oname|
|
607
735
|
output_map[oname] = [stack_name, "Outputs.#{oname}"]
|
608
736
|
end
|
609
737
|
end
|
@@ -616,8 +744,8 @@ class SparkleFormation
|
|
616
744
|
end
|
617
745
|
|
618
746
|
# @return [String] dumped hash JSON
|
619
|
-
def to_json
|
620
|
-
MultiJson.dump(compile.dump
|
747
|
+
def to_json(*args)
|
748
|
+
MultiJson.dump(compile.dump!, *args)
|
621
749
|
end
|
622
750
|
|
623
751
|
end
|