sparkle_formation 0.2.4 → 0.2.6

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