sparkle_formation 0.2.4 → 0.2.6

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: 3f49e3aceb2896698cfb226e53c06f02ed9607d7
4
- data.tar.gz: 4db5fff53aff957f05f6e933a22a273d52548457
3
+ metadata.gz: 629f237b850e9be0623bc58499ee7576018730a4
4
+ data.tar.gz: 9b602ddb998707e16a1c45ff08e12b5a175a485c
5
5
  SHA512:
6
- metadata.gz: b5a8aea5a9c132d0f7bddf6fa537c37f2d03e7b4e4d93a58690a29c65dc441ab26e6cec640ac7965b4281f68bced0954078762f10b86c84a5e68d7af92190c58
7
- data.tar.gz: db9da97bd24fe89858a8b822ed4ebb7ec328e24c2dc3d1646d8582236aa6f41ce8d2942a64916a8d5b4d925c9294db668916c8249afd647ee342913e117a59bc
6
+ metadata.gz: d527b10cca86fb282187e4e9c6672dd89957b00b8ffa6b8c7ace56b6ab3c67e639f425a89cd9091798c0073d9a1495ed4d75b17bed50026ec78c2f9298df14ae
7
+ data.tar.gz: d4c775dcd1114e10d3c707ce863c836125ee035bc4bfbd2122261340c330844fec335ba6ca8b28b7e497b25ae914d2bb7e7b0f4b298c6a771b2130d250f90c28
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## v0.2.6
2
+ * Add initial nested stack implementation
3
+ * Update user docs generation
4
+
1
5
  ## v0.2.4
2
6
  * Update builtin registry lookup to better handle all caps types
3
7
  * Add helper methods for conditionals
data/README.md CHANGED
@@ -32,14 +32,13 @@ SparkleFormation.new('ec2_example') do
32
32
  end
33
33
 
34
34
  mappings.region_map do
35
- set!('us-east-1', :ami => 'ami-7f418316')
36
- set!('us-east-1', :ami => 'ami-7f418316')
37
- set!('us-west-1', :ami => 'ami-951945d0')
38
- set!('us-west-2', :ami => 'ami-16fd7026')
39
- set!('eu-west-1', :ami => 'ami-24506250')
40
- set!('sa-east-1', :ami => 'ami-3e3be423')
41
- set!('ap-southeast-1', :ami => 'ami-74dda626')
42
- set!('ap-northeast-1', :ami => 'ami-dcfa4edd')
35
+ set!('us-east-1'._no_hump, :ami => 'ami-7f418316')
36
+ set!('us-west-1'._no_hump, :ami => 'ami-951945d0')
37
+ set!('us-west-2'._no_hump, :ami => 'ami-16fd7026')
38
+ set!('eu-west-1'._no_hump, :ami => 'ami-24506250')
39
+ set!('sa-east-1'._no_hump, :ami => 'ami-3e3be423')
40
+ set!('ap-southeast-1'._no_hump, :ami => 'ami-74dda626')
41
+ set!('ap-northeast-1'._no_hump, :ami => 'ami-dcfa4edd')
43
42
  end
44
43
 
45
44
  dynamic!(:ec2_instance, :foobar) do
@@ -109,7 +108,7 @@ templates.
109
108
  First, create the component (components/ami.rb):
110
109
 
111
110
  ```ruby
112
- SparkleFormation.build(:ami) do
111
+ SparkleFormation.build do
113
112
 
114
113
  mappings.region_map do
115
114
  set!('us-east-1', :ami => 'ami-7f418316')
@@ -11,15 +11,21 @@ FileUtils.mkdir_p('doc/UserDocs')
11
11
  Dir.glob('docs/**/*').each do |path|
12
12
  next unless File.file?(path)
13
13
  content = File.read(path)
14
- new_path = File.join('doc/UserDocs', path.sub(/.*?docs\//, ''))
14
+ rel_path = path.sub(/.*?docs\//, '')
15
+ new_path = File.join('doc/UserDocs', rel_path)
16
+ user_doc_root = (['..'] * rel_path.scan('/').size).join('/')
17
+ unless(user_doc_root.to_s.empty?)
18
+ user_doc_root << '/'
19
+ end
15
20
  FileUtils.mkdir_p(File.dirname(new_path))
16
- File.open(new_path.sub('.md', '.html'), 'w') do |file|
17
- if(path.end_with?('.md'))
18
- file.puts '<!DOCTYPE html><html><title>SparkleFormation User Documentation</title><xmp theme="simplex" style="display:none;">'
19
- file.puts content.gsub('.md', '.html')
20
- file.puts '</xmp><script src="http://strapdownjs.com/v/0.2/strapdown.js"></script></html>'
21
- else
22
- file.puts content
21
+ File.open(new_path, 'w') do |file|
22
+ file.puts content
23
+ end
24
+ if(new_path.end_with?('.md'))
25
+ File.open(new_path.sub('.md', '.html'), 'w') do |file|
26
+ file.print "<!DOCTYPE html><html><head><title>SparkleFormation User Documentation</title><script src=\"#{user_doc_root}v/0.3.2/marked.js\"></script><script src=\"#{user_doc_root}v/jquery-2.1.3.min.js\"></script><script src=\"#{user_doc_root}v/loader.js\"></script><script src=\"#{user_doc_root}v/highlight.min.js\"></script><link rel=\"stylesheet\" href=\"#{user_doc_root}v/bootstrap.min.css\"></link><link rel=\"stylesheet\" href=\"#{user_doc_root}v/highlight.min.css\"></link><link rel=\"stylesheet\" href=\"#{user_doc_root}v/finalizer.css\"></link>"
27
+ file.print "</head><body><div class=\"markdown-body\"><div class=\"navbar navbar-top\"><div class=\"navbar-inner\"><div class=\"container\"><div class=\"navbar-brand\"><a href=\"#{user_doc_root}README.html\">SparkleFormation - User documentation</a></div></div></div></div><div class=\"panel panel-default\"><div class=\"panel-body\" id=\"content\"></div></div></div>"
28
+ file.print "</body></html>"
23
29
  end
24
30
  end
25
31
  end
@@ -170,7 +170,7 @@ class SparkleFormation
170
170
  def _and(*args)
171
171
  {
172
172
  'Fn::And' => _array(
173
- args.map{|v|
173
+ *args.map{|v|
174
174
  if(v.is_a?(Symbol) || v.is_a?(String))
175
175
  _condition(v)
176
176
  else
@@ -226,6 +226,15 @@ class SparkleFormation
226
226
  end
227
227
  alias_method :or!, :_or
228
228
 
229
+ # Execute system command
230
+ #
231
+ # @param command [String]
232
+ # @return [String] result
233
+ def _system(command)
234
+ ::Kernel.send('`', command)
235
+ end
236
+ alias_method :system!, :_system
237
+
229
238
  # @return [TrueClass, FalseClass]
230
239
  def rhel?
231
240
  !!@platform[:rhel]
@@ -264,5 +273,14 @@ class SparkleFormation
264
273
  SfnRegistry.insert(name, self, *args)
265
274
  end
266
275
 
276
+ # Stack nesting helper method
277
+ #
278
+ # @param template [String, Symbol] template to nest
279
+ # @param args [String, Symbol] stringified and underscore joined for name
280
+ # @return [self]
281
+ def nest!(template, *args, &block)
282
+ SparkleFormation.nest(template, self, *args, &block)
283
+ end
284
+
267
285
  end
268
286
  end
@@ -28,6 +28,13 @@ class SparkleFormation
28
28
  extend SparkleFormation::Utils::AnimalStrings
29
29
  # @!parse extend SparkleFormation::Utils::AnimalStrings
30
30
 
31
+ # @return [Array<String>] directory names to ignore
32
+ IGNORE_DIRECTORIES = [
33
+ 'components',
34
+ 'dynamics',
35
+ 'registry'
36
+ ]
37
+
31
38
  class << self
32
39
 
33
40
  # @return [Hashish] loaded dynamics
@@ -204,6 +211,36 @@ class SparkleFormation
204
211
  result
205
212
  end
206
213
 
214
+ # Nest a template into a context
215
+ #
216
+ # @param template [String, Symbol] template to nest
217
+ # @param struct [SparkleStruct] context for nesting
218
+ # @param args [String, Symbol] stringified and underscore joined for name
219
+ # @return [SparkleStruct]
220
+ # @note if symbol is provided for template, double underscores
221
+ # will be used for directory separator and dashes will match underscores
222
+ def nest(template, struct, *args, &block)
223
+ spath = SparkleFormation.new('stub').sparkle_path
224
+ resource_name = [template.to_s.gsub(/(\/|__|-)/, '_'), *args].compact.join('_').to_sym
225
+ path = template.is_a?(Symbol) ? template.to_s.gsub('__', '/') : template.to_s
226
+ file = Dir.glob(File.join(spath, '**', '**', '*.rb')).detect do |local_path|
227
+ strip_path = local_path.sub(spath, '').sub(/^\//, '').tr('-', '_').sub('.rb', '')
228
+ strip_path == path
229
+ end
230
+ unless(file)
231
+ raise ArgumentError.new("Failed to locate nested stack file! (#{template.inspect} -> #{path.inspect})")
232
+ end
233
+ instance = self.instance_eval(IO.read(file), file, 1)
234
+ struct.resources.set!(resource_name) do
235
+ type 'AWS::CloudFormation::Stack'
236
+ end
237
+ struct.resources.__send__(resource_name).properties.stack instance.compile
238
+ if(block_given?)
239
+ struct.resources.__send__(resource_name).instance_exec(&block)
240
+ end
241
+ struct.resources.__send__(resource_name)
242
+ end
243
+
207
244
  # Insert a builtin dynamic into a context
208
245
  #
209
246
  # @param dynamic_name [String, Symbol] dynamic name
@@ -300,6 +337,7 @@ class SparkleFormation
300
337
  if(block)
301
338
  load_block(block)
302
339
  end
340
+ @compiled = nil
303
341
  end
304
342
 
305
343
  # Add block to load order
@@ -344,17 +382,126 @@ class SparkleFormation
344
382
  #
345
383
  # @return [SparkleStruct]
346
384
  def compile
347
- compiled = SparkleStruct.new
348
- @load_order.each do |key|
349
- compiled._merge!(components[key])
385
+ unless(@compiled)
386
+ compiled = SparkleStruct.new
387
+ @load_order.each do |key|
388
+ compiled._merge!(components[key])
389
+ end
390
+ @overrides.each do |override|
391
+ if(override[:args] && !override[:args].empty?)
392
+ compiled._set_state(override[:args])
393
+ end
394
+ self.class.build(compiled, &override[:block])
395
+ end
396
+ @compiled = compiled
397
+ end
398
+ @compiled
399
+ end
400
+
401
+ # Clear compiled stack if cached and perform compilation again
402
+ #
403
+ # @return [SparkleStruct]
404
+ def recompile
405
+ @compiled = nil
406
+ compile
407
+ end
408
+
409
+ # @return [TrueClass, FalseClass] includes nested stacks
410
+ def nested?
411
+ !!compile.dump!['Resources'].detect do |r_name, resource|
412
+ resource['Type'] == 'AWS::CloudFormation::Stack'
350
413
  end
351
- @overrides.each do |override|
352
- if(override[:args] && !override[:args].empty?)
353
- compiled._set_state(override[:args])
414
+ end
415
+
416
+ # @return [TrueClass, FalseClass] includes _only_ nested stacks
417
+ def isolated_nests?
418
+ hash = compile.dump!
419
+ (hash.keys == ['Resources'] || hash.keys == ['Resources', 'AWSTemplateFormatVersion']) &&
420
+ !hash['Resources'].detect{|_, r| r['Type'] != 'AWS::CloudFormation::Stack'}
421
+ end
422
+
423
+ # Apply stack nesting logic. Will extract unique parameters from
424
+ # nested stacks, update refs to use sibling stack outputs where
425
+ # required and extract nested stack templates for remote persistence
426
+ #
427
+ # @yieldparam template_name [String] nested stack resource name
428
+ # @yieldparam template [Hash] nested stack template
429
+ # @yieldreturn [String] remote URL
430
+ # @return [Hash] dumped template hash
431
+ def apply_nesting
432
+ hash = compile.dump!
433
+ stacks = Hash[
434
+ hash['Resources'].find_all do |r_name, resource|
435
+ [r_name, MultiJson.load(MultiJson.dump(resource))]
436
+ end
437
+ ]
438
+ parameters = hash.fetch('Parameters', {})
439
+ output_map = {}
440
+ stacks.each do |stack_name, stack_resource|
441
+ remap_nested_parameters(hash, parameters, stack_name, stack_resource, output_map)
442
+ end
443
+ hash['Parameters'] = parameters
444
+ hash['Resources'].each do |resource_name, resource|
445
+ if(resource['Type'] == 'AWS::CloudFormation::Stack')
446
+ stack = resource['Properties'].delete('Stack')
447
+ resource['Properties']['TemplateURL'] = yield(resource_name, stack)
354
448
  end
355
- self.class.build(compiled, &override[:block])
356
449
  end
357
- compiled
450
+ hash
451
+ end
452
+
453
+ # Extract parameters from nested stacks. Check for previous nested
454
+ # stack outputs that match parameter. If match, set parameter to use
455
+ # output. If no match, check container stack parameters for match.
456
+ # If match, set to use ref. If no match, add parameter to container
457
+ # stack parameters and set to use ref.
458
+ #
459
+ # @param template [Hash] template being processed
460
+ # @param parameters [Hash] top level parameter set being built
461
+ # @param stack_name [String] name of stack resource
462
+ # @param stack_resource [Hash] duplicate of stack resource contents
463
+ # @param output_map [Hash] mapping of output names to required stack output access
464
+ # @return [TrueClass]
465
+ # @note if parameter has includes `StackUnique` a new parameter will
466
+ # be added to container stack and it will not use outputs
467
+ def remap_nested_parameters(template, parameters, stack_name, stack_resource, output_map)
468
+ stack_parameters = stack_resource['Properties']['Stack']['Parameters']
469
+ if(stack_parameters)
470
+ template['Resources'][stack_name]['Properties']['Parameters'] ||= {}
471
+ stack_parameters.each do |pname, pval|
472
+ if(pval['StackUnique'])
473
+ check_name = [stack_name, pname].join
474
+ else
475
+ check_name = pname
476
+ end
477
+ if(parameters.keys.include?(check_name))
478
+ if(parameters[check_name]['Type'] == 'CommaDelimitedList')
479
+ new_val = {'Fn::Join' => [',', {'Ref' => check_name}]}
480
+ else
481
+ new_val = {'Ref' => check_name}
482
+ end
483
+ template['Resources'][stack_name]['Properties']['Parameters'][pname] = new_val
484
+ elsif(output_map[check_name])
485
+ template['Resources'][stack_name]['Properties']['Parameters'][pname] = {
486
+ 'Fn::GetAtt' => output_map[check_name]
487
+ }
488
+ else
489
+ if(pval['Type'] == 'CommaDelimitedList')
490
+ new_val = {'Fn::Join' => [',', {'Ref' => check_name}]}
491
+ else
492
+ new_val = {'Ref' => check_name}
493
+ end
494
+ template['Resources'][stack_name]['Properties']['Parameters'][pname] = new_val
495
+ parameters[check_name] = pval
496
+ end
497
+ end
498
+ end
499
+ if(stack_resource['Properties']['Stack']['Outputs'])
500
+ stack_resource['Properties']['Stack']['Outputs'].keys.each do |oname|
501
+ output_map[oname] = [stack_name, "Outputs.#{oname}"]
502
+ end
503
+ end
504
+ true
358
505
  end
359
506
 
360
507
  # @return [Hash] dumped hash
@@ -18,5 +18,5 @@
18
18
 
19
19
  class SparkleFormation
20
20
  # Current library version
21
- VERSION = Gem::Version.new('0.2.4')
21
+ VERSION = Gem::Version.new('0.2.6')
22
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparkle_formation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-09 00:00:00.000000000 Z
11
+ date: 2014-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: attribute_struct