sparkle_formation 1.1.14 → 1.2.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.
@@ -0,0 +1,114 @@
1
+ require 'sparkle_formation'
2
+
3
+ class SparkleFormation
4
+ # Resources helper
5
+ class Resources
6
+
7
+ autoload :Aws, 'sparkle_formation/resources/aws'
8
+
9
+ class << self
10
+
11
+ include SparkleFormation::Utils::AnimalStrings
12
+ # @!parse include SparkleFormation::Utils::AnimalStrings
13
+
14
+ # Register resource
15
+ #
16
+ # @param type [String] Orchestration resource type
17
+ # @param hash [Hash] metadata information
18
+ # @return [TrueClass]
19
+ def register(type, hash)
20
+ unless(class_variable_defined?(:@@registry))
21
+ @@registry = AttributeStruct.hashish.new
22
+ end
23
+ @@registry[type] = hash
24
+ true
25
+ end
26
+
27
+ # Resource information
28
+ #
29
+ # @param identifier [String, Symbol] resource identifier
30
+ # @param key [String, Symbol] specific data
31
+ # @return [Hashish]
32
+ def resource(identifier, key=nil)
33
+ res = lookup(identifier)
34
+ if(key && res)
35
+ res[key.to_sym]
36
+ else
37
+ res
38
+ end
39
+ end
40
+
41
+ # Register all discovered resources
42
+ #
43
+ # @param json_path_or_hash [String, Hashish] path to files or hash
44
+ # @return [TrueClass]
45
+ def load(json_path_or_hash)
46
+ if(json_path_or_hash.is_a?(String))
47
+ content = AttributeStruct.hashish.new(MultiJson.load(File.read(json_path_or_hash)))
48
+ else
49
+ content = json_path_or_hash
50
+ end
51
+ content.each do |type, hash|
52
+ register(type, hash)
53
+ end
54
+ true
55
+ end
56
+
57
+ # Load the builtin AWS resources
58
+ #
59
+ # @return [TrueClass]
60
+ def load!
61
+ true
62
+ end
63
+
64
+ # Discover registry key via part searching
65
+ #
66
+ # @param key [String, Symbol]
67
+ # @return [String, NilClass]
68
+ def registry_key(key)
69
+ o_key = key
70
+ key = key.to_s.tr('_', '')
71
+ snake_parts = nil
72
+ result = @@registry.keys.detect do |ref|
73
+ ref = ref.downcase
74
+ snake_parts = ref.split('::')
75
+ until(snake_parts.empty?)
76
+ break if snake_parts.join('') == key
77
+ snake_parts.shift
78
+ end
79
+ !snake_parts.empty?
80
+ end
81
+ if(result)
82
+ collisions = @@registry.keys.find_all do |ref|
83
+ split_ref = ref.downcase.split('::')
84
+ ref = split_ref.slice(split_ref.size - snake_parts.size, split_ref.size).join('')
85
+ key == ref
86
+ end
87
+ if(collisions.size > 1)
88
+ raise ArgumentError.new 'Ambiguous dynamic name returned multiple matches! ' \
89
+ "`#{o_key.inspect}` -> #{collisions.sort.join(', ')}"
90
+ end
91
+ end
92
+ result
93
+ end
94
+
95
+ # Registry information for given type
96
+ #
97
+ # @param key [String, Symbol]
98
+ # @return [Hashish, NilClass]
99
+ def lookup(key)
100
+ @@registry[registry_key(key)]
101
+ end
102
+
103
+ # @return [Hashish] currently loaded AWS registry
104
+ def registry
105
+ if(class_variable_defined?(:@@registry))
106
+ @@registry
107
+ else
108
+ @@registry = AttributeStruct.hashish.new
109
+ end
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -1,6 +1,8 @@
1
1
  require 'sparkle_formation'
2
2
 
3
+ # Unicorns and rainbows
3
4
  class SparkleFormation
5
+ # Independent collection of SparkleFormation items
4
6
  class Sparkle
5
7
 
6
8
  class << self
@@ -17,16 +19,15 @@ class SparkleFormation
17
19
  idx = caller.index do |item|
18
20
  item.end_with?("`register!'")
19
21
  end
20
- if(idx)
21
- file = caller[idx.next].split(':', 2).first
22
- path = File.join(File.dirname(file), 'sparkleformation')
23
- unless(File.directory?(path))
24
- path = nil
25
- end
26
- unless(name)
27
- name = File.basename(caller[idx.next].split(':', 2).first)
28
- name.sub!(File.extname(name), '')
29
- end
22
+ idx = idx ? idx.next : 0
23
+ file = caller[idx].split(':', 2).first
24
+ path = File.join(File.dirname(file), 'sparkleformation')
25
+ unless(File.directory?(path))
26
+ path = nil
27
+ end
28
+ unless(name)
29
+ name = File.basename(caller[idx].split(':', 2).first)
30
+ name.sub!(File.extname(name), '')
30
31
  end
31
32
  end
32
33
  unless(name)
@@ -61,6 +62,7 @@ class SparkleFormation
61
62
 
62
63
  # Wrapper for evaluating sfn files to store within sparkle
63
64
  # container and remove global application
65
+ # rubocop:disable Metrics/MethodLength
64
66
  def eval_wrapper
65
67
  klass = Class.new(BasicObject)
66
68
  klass.class_eval(<<-EOS
@@ -214,7 +216,8 @@ class SparkleFormation
214
216
  @raw_data = Smash.new(
215
217
  :dynamic => [],
216
218
  :component => [],
217
- :registry => []
219
+ :registry => [],
220
+ :template => []
218
221
  )
219
222
  @wrapper = eval_wrapper.new
220
223
  wrapper.part_data(raw_data)
@@ -245,35 +248,7 @@ class SparkleFormation
245
248
  # @return [Smash<name:path>]
246
249
  def templates
247
250
  memoize(:templates) do
248
- Smash.new.tap do |hash|
249
- Dir.glob(File.join(root, '**', '**', '*.{json,rb}')) do |path|
250
- slim_path = path.sub("#{root}/", '')
251
- next if DIRS.include?(slim_path.split('/').first)
252
- data = Smash.new(:template => [])
253
- t_wrap = eval_wrapper.new
254
- t_wrap.part_data(data)
255
- if(slim_path.end_with?('.rb'))
256
- begin
257
- t_wrap.instance_eval(IO.read(path), path, 1)
258
- rescue TypeError
259
- end
260
- end
261
- data = data[:template].first || Smash.new
262
- unless(data[:name])
263
- data[:name] = slim_path.tr('/', '__').sub(/\.(rb|json)$/, '')
264
- end
265
- if(hash[data[:name]])
266
- raise KeyError.new "Template name is already in use within pack! (`#{data[:name]}`)"
267
- end
268
- hash[data[:name]] = data.merge(
269
- Smash.new(
270
- :type => :template,
271
- :path => path,
272
- :serialized => !path.end_with?('.rb')
273
- )
274
- )
275
- end
276
- end
251
+ Smash.new
277
252
  end
278
253
  end
279
254
 
@@ -290,9 +265,9 @@ class SparkleFormation
290
265
  result = send(TYPES[type])[name]
291
266
  if(result.nil? && TYPES[type] == 'templates')
292
267
  result = (
293
- send(TYPES[type]).detect{|k,v|
268
+ send(TYPES[type]).detect{|_, v|
294
269
  name = name.to_s
295
- short_name = v[:path].sub(/#{Regexp.escape(root)}\/?/, '')
270
+ short_name = v[:path].sub(%r{#{Regexp.escape(root)}/?}, '')
296
271
  v[:path] == name ||
297
272
  short_name == name ||
298
273
  short_name.sub('.rb', '').gsub(File::SEPARATOR, '__').tr('-', '_') == name ||
@@ -323,7 +298,7 @@ class SparkleFormation
323
298
  def locate_root
324
299
  VALID_ROOT_DIRS.map do |part|
325
300
  path = File.expand_path(File.join(Dir.pwd, part))
326
- if(File.exists?(path))
301
+ if(File.exist?(path))
327
302
  path
328
303
  end
329
304
  end.compact.first
@@ -332,8 +307,28 @@ class SparkleFormation
332
307
  # Load all sparkle parts
333
308
  def load_parts!
334
309
  memoize(:load_parts) do
335
- Dir.glob(File.join(root, "{#{DIRS.join(',')}}", '**', '**', '*.rb')).each do |file|
336
- wrapper.instance_eval(IO.read(file), file, 1)
310
+ Dir.glob(File.join(root, '**', '**', '*.{json,rb}')).each do |file|
311
+ slim_path = file.sub("#{root}/", '')
312
+ if(file.end_with?('.rb'))
313
+ begin
314
+ wrapper.instance_eval(IO.read(file), file, 1)
315
+ rescue TypeError
316
+ end
317
+ end
318
+ if(file.end_with?('.json') || raw_data[:template].first)
319
+ data = raw_data[:template].pop || Smash.new
320
+ unless(data[:name])
321
+ data[:name] = slim_path.tr('/', '__').sub(/\.(rb|json)$/, '')
322
+ end
323
+ if(templates[data[:name]])
324
+ raise KeyError.new "Template name is already in use within pack! (`#{data[:name]}`)"
325
+ end
326
+ templates[data[:name]] = data.merge(
327
+ :type => :template,
328
+ :path => file,
329
+ :serialized => !file.end_with?('.rb')
330
+ )
331
+ end
337
332
  end
338
333
  raw_data.each do |key, items|
339
334
  items.each do |item|
@@ -0,0 +1,314 @@
1
+ require 'sparkle_formation'
2
+
3
+ class SparkleFormation
4
+
5
+ # Provides template helper methods
6
+ module SparkleAttribute
7
+
8
+ # AWS specific helper implementations
9
+ module Aws
10
+
11
+ # Fn::Join generator
12
+ #
13
+ # @param args [Object]
14
+ # @return [Hash]
15
+ def _cf_join(*args)
16
+ options = args.detect{|i| i.is_a?(Hash) && i[:options]} || {:options => {}}
17
+ args.delete(options)
18
+ unless(args.size == 1)
19
+ args = [args]
20
+ end
21
+ {'Fn::Join' => [options[:options][:delimiter] || '', *args]}
22
+ end
23
+ alias_method :join!, :_cf_join
24
+
25
+ # Ref generator
26
+ #
27
+ # @param thing [String, Symbol] reference name
28
+ # @return [Hash]
29
+ # @note Symbol value will force key processing
30
+ def _cf_ref(thing)
31
+ __t_stringish(thing)
32
+ thing = _process_key(thing, :force) if thing.is_a?(Symbol)
33
+ {'Ref' => thing}
34
+ end
35
+ alias_method :_ref, :_cf_ref
36
+ alias_method :ref!, :_cf_ref
37
+
38
+ # Fn::FindInMap generator
39
+ #
40
+ # @param thing [String, Symbol] thing to find
41
+ # @param key [String, Symbol] thing to search
42
+ # @param suffix [Object] additional args
43
+ # @return [Hash]
44
+ def _cf_map(thing, key, *suffix)
45
+ __t_stringish(thing)
46
+ suffix = suffix.map do |item|
47
+ if(item.is_a?(Symbol))
48
+ _process_key(item, :force)
49
+ else
50
+ item
51
+ end
52
+ end
53
+ thing = _process_key(thing, :force) if thing.is_a?(Symbol)
54
+ if(key.is_a?(Symbol))
55
+ key = ref!(key)
56
+ end
57
+ {'Fn::FindInMap' => [thing, key, *suffix]}
58
+ end
59
+ alias_method :_cf_find_in_map, :_cf_map
60
+ alias_method :find_in_map!, :_cf_map
61
+ alias_method :map!, :_cf_map
62
+
63
+ # Fn::GetAtt generator
64
+ #
65
+ # @param [Object] pass through arguments
66
+ # @return [Hash]
67
+ def _cf_attr(*args)
68
+ __t_stringish(args.first)
69
+ args = args.map do |thing|
70
+ if(thing.is_a?(Symbol))
71
+ _process_key(thing, :force)
72
+ else
73
+ thing
74
+ end
75
+ end
76
+ {'Fn::GetAtt' => args}
77
+ end
78
+ alias_method :_cf_get_att, :_cf_attr
79
+ alias_method :get_att!, :_cf_attr
80
+ alias_method :attr!, :_cf_attr
81
+
82
+ # Fn::Base64 generator
83
+ #
84
+ # @param arg [Object] pass through
85
+ # @return [Hash]
86
+ def _cf_base64(arg)
87
+ {'Fn::Base64' => arg}
88
+ end
89
+ alias_method :base64!, :_cf_base64
90
+
91
+ # Fn::GetAZs generator
92
+ #
93
+ # @param region [String, Symbol] String will pass through. Symbol will be converted to ref
94
+ # @return [Hash]
95
+ def _cf_get_azs(region=nil)
96
+ region = case region
97
+ when Symbol
98
+ _cf_ref(region)
99
+ when NilClass
100
+ ''
101
+ else
102
+ region
103
+ end
104
+ {'Fn::GetAZs' => region}
105
+ end
106
+ alias_method :get_azs!, :_cf_get_azs
107
+ alias_method :azs!, :_cf_get_azs
108
+
109
+ # Fn::Select generator
110
+ #
111
+ # @param index [String, Symbol, Integer] Symbol will be converted to ref
112
+ # @param item [Object, Symbol] Symbol will be converted to ref
113
+ # @return [Hash]
114
+ def _cf_select(index, item)
115
+ index = index.is_a?(Symbol) ? _cf_ref(index) : index
116
+ item = _cf_ref(item) if item.is_a?(Symbol)
117
+ {'Fn::Select' => [index, item]}
118
+ end
119
+ alias_method :select!, :_cf_select
120
+
121
+ # Condition generator
122
+ #
123
+ # @param name [String, Symbol] symbol will be processed
124
+ # @return [Hash]
125
+ def _condition(name)
126
+ __t_stringish(name)
127
+ {'Condition' => name.is_a?(Symbol) ? _process_key(name, :force) : name}
128
+ end
129
+ alias_method :condition!, :_condition
130
+
131
+ # Condition setter
132
+ #
133
+ # @param name [String, Symbol] condition name
134
+ # @return [SparkleStruct]
135
+ # @note this is used to set a {"Condition" => "Name"} into the
136
+ # current context, generally the top level of a resource
137
+ def _on_condition(name)
138
+ _set(*_condition(name).to_a.flatten)
139
+ end
140
+ alias_method :on_condition!, :_on_condition
141
+
142
+ # Fn::If generator
143
+ #
144
+ # @param cond [String, Symbol] symbol will be case processed
145
+ # @param true_value [Object]
146
+ # @param false_value [Object]
147
+ # @return [Hash]
148
+ def _if(cond, true_value, false_value)
149
+ cond = cond.is_a?(Symbol) ? _process_key(cond) : cond
150
+ {'Fn::If' => _array(cond, true_value, false_value)}
151
+ end
152
+ alias_method :if!, :_if
153
+
154
+ # Fn::And generator
155
+ #
156
+ # @param args [Object]
157
+ # @return [Hash]
158
+ # @note symbols will be processed and set as condition. strings
159
+ # will be set as condition directly. procs will be evaluated
160
+ def _and(*args)
161
+ {
162
+ 'Fn::And' => _array(
163
+ *args.map{|v|
164
+ if(v.is_a?(Symbol) || v.is_a?(String))
165
+ _condition(v)
166
+ else
167
+ v
168
+ end
169
+ }
170
+ )
171
+ }
172
+ end
173
+ alias_method :and!, :_and
174
+
175
+ # Fn::Equals generator
176
+ #
177
+ # @param v1 [Object]
178
+ # @param v2 [Object]
179
+ # @return [Hash]
180
+ def _equals(v1, v2)
181
+ {'Fn::Equals' => _array(v1, v2)}
182
+ end
183
+ alias_method :equals!, :_equals
184
+
185
+ # Fn::Not generator
186
+ #
187
+ # @param arg [Object]
188
+ # @return [Hash]
189
+ def _not(arg)
190
+ if(arg.is_a?(String) || arg.is_a?(Symbol))
191
+ arg = _condition(arg)
192
+ else
193
+ arg = _array(arg).first
194
+ end
195
+ {'Fn::Not' => [arg]}
196
+ end
197
+ alias_method :not!, :_not
198
+
199
+ # Fn::Or generator
200
+ #
201
+ # @param v1 [Object]
202
+ # @param v2 [Object]
203
+ # @return [Hash]
204
+ def _or(*args)
205
+ {
206
+ 'Fn::Or' => _array(
207
+ *args.map{|v|
208
+ if(v.is_a?(Symbol) || v.is_a?(String))
209
+ _condition(v)
210
+ else
211
+ v
212
+ end
213
+ }
214
+ )
215
+ }
216
+ end
217
+ alias_method :or!, :_or
218
+
219
+ # No value generator
220
+ #
221
+ # @return [String]
222
+ def _no_value
223
+ _ref('AWS::NoValue')
224
+ end
225
+ alias_method :no_value!, :_no_value
226
+
227
+ # Region generator
228
+ #
229
+ # @return [Hash]
230
+ def _region
231
+ _ref('AWS::Region')
232
+ end
233
+ alias_method :region!, :_region
234
+
235
+ # Notification ARNs generator
236
+ #
237
+ # @return [Hash]
238
+ def _notification_arns
239
+ _ref('AWS::NotificationARNs')
240
+ end
241
+ alias_method :notification_arns!, :_notification_arns
242
+
243
+ # Account ID generator
244
+ #
245
+ # @return [Hash]
246
+ def _account_id
247
+ _ref('AWS::AccountId')
248
+ end
249
+ alias_method :account_id!, :_account_id
250
+
251
+ # Stack ID generator
252
+ #
253
+ # @return [Hash]
254
+ def _stack_id
255
+ _ref('AWS::StackId')
256
+ end
257
+ alias_method :stack_id!, :_stack_id
258
+
259
+ # Stack name generator
260
+ #
261
+ # @return [Hash]
262
+ def _stack_name
263
+ _ref('AWS::StackName')
264
+ end
265
+ alias_method :stack_name!, :_stack_name
266
+
267
+ # Resource dependency generator
268
+ #
269
+ # @param [Symbol, String, Array<Symbol, String>] resource names
270
+ # @return [Array<String>]
271
+ def _depends_on(*args)
272
+ _set('DependsOn', [args].flatten.compact.map{|s| _process_key(s)})
273
+ end
274
+ alias_method :depends_on!, :_depends_on
275
+
276
+ # Reference output value from nested stack
277
+ #
278
+ # @param stack_name [String, Symbol] logical resource name of stack
279
+ # @apram output_name [String, Symbol] stack output name
280
+ def _stack_output(stack_name, output_name)
281
+ _cf_attr(_process_key(stack_name), "Outputs.#{_process_key(output_name)}")
282
+ end
283
+ alias_method :stack_output!, :_stack_output
284
+
285
+ # @return [TrueClass, FalseClass] resource can be tagged
286
+ def taggable?
287
+ if(self[:type])
288
+ resource = _self.lookup(self[:type].gsub('::', '_').downcase)
289
+ resource && resource[:properties].include?('Tags')
290
+ else
291
+ if(_parent)
292
+ _parent.taggable?
293
+ end
294
+ end
295
+ end
296
+
297
+ # Set tags on a resource
298
+ #
299
+ # @param hash [Hash] Key/value pair tags
300
+ # @return [SparkleStruct]
301
+ def _tags(hash)
302
+ _set('Tags',
303
+ hash.map{ |k, v|
304
+ key = k.is_a?(Symbol) ? _process_key(k, :force) : k
305
+ {'Key' => key, 'Value' => v}
306
+ }
307
+ )
308
+ end
309
+ alias_method :tags!, :_tags
310
+
311
+ end
312
+
313
+ end
314
+ end