sparkle_formation 1.1.14 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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