knife-cloudformation 0.1.22 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +56 -2
  4. data/knife-cloudformation.gemspec +4 -7
  5. data/lib/chef/knife/cloudformation_create.rb +105 -245
  6. data/lib/chef/knife/cloudformation_describe.rb +50 -26
  7. data/lib/chef/knife/cloudformation_destroy.rb +17 -18
  8. data/lib/chef/knife/cloudformation_events.rb +48 -14
  9. data/lib/chef/knife/cloudformation_export.rb +117 -34
  10. data/lib/chef/knife/cloudformation_import.rb +124 -18
  11. data/lib/chef/knife/cloudformation_inspect.rb +159 -71
  12. data/lib/chef/knife/cloudformation_list.rb +20 -24
  13. data/lib/chef/knife/cloudformation_promote.rb +40 -0
  14. data/lib/chef/knife/cloudformation_update.rb +132 -15
  15. data/lib/chef/knife/cloudformation_validate.rb +35 -0
  16. data/lib/knife-cloudformation.rb +28 -0
  17. data/lib/knife-cloudformation/cache.rb +213 -35
  18. data/lib/knife-cloudformation/knife.rb +9 -0
  19. data/lib/knife-cloudformation/knife/base.rb +179 -0
  20. data/lib/knife-cloudformation/knife/stack.rb +94 -0
  21. data/lib/knife-cloudformation/knife/template.rb +174 -0
  22. data/lib/knife-cloudformation/monkey_patch.rb +8 -0
  23. data/lib/knife-cloudformation/monkey_patch/stack.rb +195 -0
  24. data/lib/knife-cloudformation/provider.rb +225 -0
  25. data/lib/knife-cloudformation/utils.rb +18 -98
  26. data/lib/knife-cloudformation/utils/animal_strings.rb +28 -0
  27. data/lib/knife-cloudformation/utils/debug.rb +31 -0
  28. data/lib/knife-cloudformation/utils/json.rb +64 -0
  29. data/lib/knife-cloudformation/utils/object_storage.rb +28 -0
  30. data/lib/knife-cloudformation/utils/output.rb +79 -0
  31. data/lib/knife-cloudformation/utils/path_selector.rb +99 -0
  32. data/lib/knife-cloudformation/utils/ssher.rb +29 -0
  33. data/lib/knife-cloudformation/utils/stack_exporter.rb +271 -0
  34. data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +35 -0
  35. data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +124 -0
  36. data/lib/knife-cloudformation/version.rb +2 -4
  37. metadata +47 -94
  38. data/Gemfile +0 -3
  39. data/Gemfile.lock +0 -90
  40. data/knife-cloudformation-0.1.20.gem +0 -0
  41. data/lib/knife-cloudformation/aws_commons.rb +0 -267
  42. data/lib/knife-cloudformation/aws_commons/stack.rb +0 -435
  43. data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +0 -79
  44. data/lib/knife-cloudformation/cloudformation_base.rb +0 -168
  45. data/lib/knife-cloudformation/export.rb +0 -174
Binary file
@@ -1,267 +0,0 @@
1
- require 'fog'
2
- require 'knife-cloudformation/utils'
3
- require 'knife-cloudformation/cache'
4
-
5
- Dir.glob(File.join(File.dirname(__FILE__), 'aws_commons/*.rb')).each do |item|
6
- require "knife-cloudformation/aws_commons/#{File.basename(item).sub('.rb', '')}"
7
- end
8
-
9
- module KnifeCloudformation
10
- class AwsCommons
11
-
12
- class << self
13
- def logger=(l)
14
- @logger = l
15
- end
16
-
17
- def logger
18
- unless(@logger)
19
- require 'logger'
20
- @logger = Logger.new($stdout)
21
- @logger.level = ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO
22
- end
23
- @logger
24
- end
25
- end
26
-
27
- include KnifeCloudformation::Utils::AnimalStrings
28
- include KnifeCloudformation::Utils::Debug
29
-
30
- FOG_MAP = {
31
- :ec2 => :compute
32
- }
33
-
34
- attr_reader :credentials
35
-
36
- def initialize(args={})
37
- @ui = args[:ui]
38
- @credentials = @creds = args[:fog]
39
- @disconnect_long_jobs = args[:disconnect_long_jobs]
40
- @throttled = []
41
- @connections = {}
42
- @memo = Cache.new(credentials)
43
- @local = {:stacks => {}}
44
- end
45
-
46
- def logger
47
- @logger || self.class.logger
48
- end
49
-
50
- def logger=(l)
51
- @logger = l
52
- end
53
-
54
- def cache
55
- @memo
56
- end
57
-
58
- def clear_cache(*types)
59
- @memo.clear!(*types)
60
- true
61
- end
62
-
63
- def build_connection(type)
64
- type = type.to_sym
65
- type = FOG_MAP[type] if FOG_MAP[type]
66
- unless(@connections[type])
67
- case type
68
- when :compute
69
- @connections[:compute] = Fog::Compute::AWS.new(@creds)
70
- when :dns
71
- dns_creds = @creds.dup
72
- dns_creds.delete(:region) || dns_creds.delete('region')
73
- @connections[:dns] = Fog::DNS::AWS.new(dns_creds)
74
- else
75
- begin
76
- Fog.credentials = Fog.symbolize_credentials(@creds)
77
- @connections[type] = Fog::AWS[type]
78
- Fog.credentials = {}
79
- rescue NameError
80
- klass = [camel(type.to_s), 'AWS'].inject(Fog) do |memo, item|
81
- memo.const_defined?(item) ? memo.const_get(item) : break
82
- end
83
- if(klass)
84
- @connections[type] = klass.new(Fog.symbolize_credentials(@creds))
85
- else
86
- raise
87
- end
88
- end
89
- end
90
- end
91
- if(block_given?)
92
- throttleable do
93
- yield @connections[type]
94
- end
95
- else
96
- @connections[type]
97
- end
98
- end
99
- alias_method :aws, :build_connection
100
-
101
- DEFAULT_STACK_STATUS = %w(
102
- CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED
103
- ROLLBACK_IN_PROGRESS ROLLBACK_COMPLETE ROLLBACK_FAILED
104
- UPDATE_IN_PROGRESS UPDATE_COMPLETE UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
105
- UPDATE_ROLLBACK_IN_PROGRESS UPDATE_ROLLBACK_FAILED
106
- UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_COMPLETE
107
- DELETE_IN_PROGRESS DELETE_FAILED
108
- )
109
-
110
- def stacks(args={})
111
- status = Array(args[:status] || DEFAULT_STACK_STATUS).flatten.compact.map do |stat|
112
- stat.to_s.upcase
113
- end
114
- @memo.init(:stacks_lock, :lock)
115
- @memo.init(:stacks, :stamped)
116
- if(args[:cache_time])
117
- @memo[:stacks].stamp
118
- else
119
- if(args[:refresh_every])
120
- cache.apply_limit(:stacks, args[:refresh_every].to_i)
121
- end
122
- if(@memo[:stacks].update_allowed? || args[:force_refresh])
123
- long_running_job(:stacks) do
124
- logger.debug 'Populating full cloudformation list from remote end point'
125
- stack_result = throttleable do
126
- aws(:cloud_formation).describe_stacks.body['Stacks']
127
- end
128
- if(stack_result)
129
- @memo[:stacks_lock].lock do
130
- @memo[:stacks].value = stack_result
131
- end
132
- end
133
- logger.debug 'Full cloudformation list from remote end point complete'
134
- end
135
- end
136
- end
137
- if(@memo[:stacks].value)
138
- @memo[:stacks].value.find_all do |s|
139
- status.include?(s['StackStatus'])
140
- end
141
- else
142
- []
143
- end
144
- end
145
-
146
- def throttleable
147
- if(@throttled.size > 0)
148
- if(Time.now.to_i - @throttled.last < Time.now.to_i - @throttled.size * 15)
149
- logger.error "Currently being throttled. Not running request!"
150
- return nil
151
- end
152
- end
153
- begin
154
- result = yield
155
- @throttled.clear
156
- result
157
- rescue Fog::Service::Error => e
158
- if(e.message == 'Throttling => Rate exceeded')
159
- logger.error "Remote end point is is currently throttling. Rate has been exceeded."
160
- @throttled << Time.now.to_i
161
- end
162
- nil
163
- end
164
- end
165
-
166
- def long_running_job(name)
167
- if(@disconnect_long_jobs)
168
- logger.debug "Disconnected long running jobs enabled. Starting: #{name}"
169
- lock_key = "long_jobs_lock_#{name}".to_sym
170
- @memo.init(lock_key, :lock)
171
- Thread.new do
172
- begin
173
- @memo[lock_key].lock do
174
- begin
175
- logger.info "Long running job started disconnected (#{name})"
176
- yield
177
- rescue => e
178
- logger.error "Long running job failure (#{name}): #{e.class} - #{e}\n#{e.backtrace.join("\n")}"
179
- end
180
- end
181
- rescue Redis::Lock::Timeout
182
- logger.warn "Long running process failed to aquire lock. Request not run (#{name})"
183
- end
184
- end
185
- else
186
- logger.debug "Disconnected long running jobs disabled. Starting #{name} inline"
187
- yield
188
- end
189
- end
190
-
191
- def name_from_stack_id(s_id)
192
- found = stacks.detect do |s|
193
- s['StackId'] == s_id
194
- end
195
- found ? found['StackName'] : raise(IndexError.new("Failed to locate stack with ID: #{s_id}"))
196
- end
197
-
198
- def id_from_stack_name(name)
199
- found = stacks.detect do |s|
200
- s['StackName'] == name
201
- end
202
- found ? found['StackId'] : raise(IndexError.new("Failed to locate stack with name: #{name}"))
203
- end
204
-
205
- def stack(*names)
206
- direct_load = names.delete(:ignore_seeds)
207
- result = names.map do |name|
208
- [name, name.start_with?('arn:') || direct_load ? name : id_from_stack_name(name)]
209
- end.map do |name, s_id|
210
- unless(@local[:stacks][s_id])
211
- unless(direct_load)
212
- seed = stacks.detect do |stk|
213
- stk['StackId'] == s_id
214
- end
215
- end
216
- if(seed)
217
- logger.debug "Requested stack (#{name}) loaded via cached seed"
218
- else
219
- logger.debug "Requested stack (#{name}) loaded directly with no seed"
220
- end
221
- @local[:stacks][s_id] = Stack.new(name, self, seed)
222
- end
223
- @local[:stacks][s_id]
224
- end
225
- result.size == 1 ? result.first : result
226
- end
227
-
228
- def create_stack(name, definition)
229
- Stack.create(name, definition, self)
230
- end
231
-
232
- # Output Helpers
233
-
234
- def process(things, args={})
235
- @event_ids ||= []
236
- processed = things.reverse.map do |thing|
237
- next if @event_ids.include?(thing['EventId'])
238
- @event_ids.push(thing['EventId']).compact!
239
- if(args[:attributes])
240
- args[:attributes].map do |key|
241
- thing[key].to_s
242
- end
243
- else
244
- thing.values
245
- end
246
- end
247
- args[:flat] ? processed.flatten : processed
248
- end
249
-
250
- def get_titles(thing, args={})
251
- attrs = args[:attributes] || []
252
- if(attrs.empty?)
253
- hash = thing.is_a?(Array) ? thing.first : thing
254
- hash ||= {}
255
- attrs = hash.keys
256
- end
257
- titles = attrs.map do |key|
258
- key.gsub(/([a-z])([A-Z])/, '\1 \2')
259
- end.compact
260
- if(args[:format])
261
- titles.map{|s| @ui.color(s, :bold)}
262
- else
263
- titles
264
- end
265
- end
266
- end
267
- end
@@ -1,435 +0,0 @@
1
- require 'knife-cloudformation/cache'
2
- require 'knife-cloudformation/aws_commons'
3
- require 'digest/sha2'
4
-
5
- module KnifeCloudformation
6
- class AwsCommons
7
- class Stack
8
-
9
- include KnifeCloudformation::Utils::Debug
10
- include KnifeCloudformation::Utils::JSON
11
- include KnifeCloudformation::Utils::AnimalStrings
12
-
13
- attr_reader :name, :raw_stack, :raw_resources, :common
14
-
15
- class << self
16
-
17
- ALLOWED_PARAMETER_ATTRIBUTES = %w(
18
- Type Default NoEcho AllowedValues AllowedPattern
19
- MaxLength MinLength MaxValue MinValue Description
20
- ConstraintDescription
21
- )
22
-
23
- include KnifeCloudformation::Utils::JSON
24
-
25
- def create(name, definition, aws_common)
26
- aws_common.aws(:cloud_formation).create_stack(name, definition)
27
- new(name, aws_common)
28
- end
29
-
30
- def build_stack_definition(template, options={})
31
- stack = Mash.new
32
- options.each do |key, value|
33
- format_key = key.to_s.split('_').map do |k|
34
- "#{k.slice(0,1).upcase}#{k.slice(1,k.length)}"
35
- end.join
36
- stack[format_key] = value
37
- end
38
- enable_capabilities!(stack, template)
39
- clean_parameters!(template)
40
- stack['TemplateBody'] = _to_json(template)
41
- stack
42
- end
43
-
44
- # Currently only checking for IAM resources since that's all
45
- # that is supported for creation
46
- def enable_capabilities!(stack, template)
47
- found = Array(template['Resources']).detect do |resource_name, resource|
48
- resource['Type'].start_with?('AWS::IAM')
49
- end
50
- stack['Capabilities'] = ['CAPABILITY_IAM'] if found
51
- nil
52
- end
53
-
54
- def clean_parameters!(template)
55
- template['Parameters'].each do |name, options|
56
- options.delete_if do |attribute, value|
57
- !ALLOWED_PARAMETER_ATTRIBUTES.include?(attribute)
58
- end
59
- end
60
- end
61
-
62
- end
63
-
64
- def initialize(name, common, raw_stack=nil)
65
- @name = name
66
- @common = common
67
- @memo = Cache.new(common.credentials.merge(:stack => name))
68
- reset_local
69
- @memo.init(:raw_stack, :stamped)
70
- if(raw_stack)
71
- if(@memo[:stacks])
72
- if(@memo[:stacks].stamp > @memo[:raw_stack].stamp)
73
- @memo[:raw_stack].value = raw_stack
74
- end
75
- else
76
- @memo[:raw_stack].value = raw_stack
77
- end
78
- end
79
- load_stack
80
- @force_refresh = false
81
- @force_refresh = in_progress?
82
- end
83
-
84
- ## Actions ##
85
-
86
- def update(definition)
87
- if(definition.keys.detect{|k|k.is_a?(Symbol)})
88
- definition = format_definition(definition)
89
- end
90
- res = common.aws(:cloud_formation).update_stack(name, definition)
91
- reload!
92
- res
93
- end
94
-
95
- def format_definition(def_hash)
96
- new_hash = {}
97
- def_hash.each do |k,v|
98
- new_hash[camel(k)] = v
99
- end
100
- if(new_hash['TemplateBody'].is_a?(Hash))
101
- new_hash['TemplateBody'] = _to_json(new_hash['TemplateBody'])
102
- end
103
- new_hash
104
- end
105
-
106
- def destroy
107
- res = common.aws(:cloud_formation).delete_stack(name)
108
- reload!
109
- res
110
- end
111
-
112
- def load_stack(*args)
113
- @memo.init(:raw_stack, :stamped)
114
- begin
115
- @memo.init(:raw_stack_lock, :lock)
116
- @memo[:raw_stack_lock].lock do
117
- if(args.include?(:force) || @memo[:raw_stack].update_allowed?)
118
- @memo[:raw_stack].value = common.aws(:cloud_formation)
119
- .describe_stacks('StackName' => name)
120
- .body['Stacks'].first
121
- end
122
- end
123
- rescue => e
124
- if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
125
- # someone else is updating
126
- debug 'Got lock timeout on stack load'
127
- else
128
- raise
129
- end
130
- end
131
- end
132
-
133
- def load_resources
134
- @memo.init(:raw_resources, :stamped)
135
- begin
136
- @memo.init(:raw_resources_lock, :lock)
137
- @memo[:raw_resources_lock].lock do
138
- if(@memo[:raw_resources].update_allowed?)
139
- @memo[:raw_resources].value = common.aws(:cloud_formation)
140
- .describe_stack_resources('StackName' => name)
141
- .body['StackResources']
142
- end
143
- end
144
- rescue => e
145
- if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
146
- debug 'Got lock timeout on resource load'
147
- else
148
- raise e
149
- end
150
- end
151
- end
152
-
153
- def refresh?(bool=nil)
154
- bool || (bool.nil? && @force_refresh)
155
- end
156
-
157
- def reset_local
158
- @local = {
159
- :nodes => []
160
- }
161
- end
162
-
163
- def reload!
164
- @memo.clear! do
165
- load_stack(:force)
166
- load_resources
167
- @force_refresh = in_progress?
168
- end
169
- true
170
- end
171
-
172
- ## Information ##
173
-
174
- def serialize
175
- _to_json(to_hash)
176
- end
177
-
178
- def to_hash(extra_data={})
179
- {
180
- :template_body => template,
181
- :parameters => parameters,
182
- :capabilities => capabilities,
183
- :disable_rollback => disable_rollback,
184
- :notification_ARNs => notification_arns,
185
- :timeout_in_minutes => timeout
186
- }.merge(extra_data)
187
- end
188
-
189
- def template
190
- @memo.init(:template, :value)
191
- unless(@memo[:template].value)
192
- @memo[:template].value = _from_json(
193
- common.aws(:cloud_formation)
194
- .get_template(name).body['TemplateBody']
195
- )
196
- end
197
- @memo[:template].value
198
- end
199
-
200
- ## Stack metadata ##
201
- def parameters(raw=false)
202
- if(raw)
203
- @memo[:raw_stack].value['Parameters']
204
- else
205
- @memo.init(:parameters, :value)
206
- unless(@memo[:parameters].value)
207
- @memo[:parameters].value = Hash[*(
208
- @memo[:raw_stack].value['Parameters'].map do |ary|
209
- [ary['ParameterKey'], ary['ParameterValue']]
210
- end.flatten
211
- )]
212
- end
213
- @memo[:parameters].value
214
- end
215
- end
216
-
217
- def capabilities
218
- @memo[:raw_stack].value['Capabilities']
219
- end
220
-
221
- def disable_rollback
222
- @memo[:raw_stack].value['DisableRollback']
223
- end
224
-
225
- def notification_arns
226
- @memo[:raw_stack].value['NotificationARNs']
227
- end
228
-
229
- def timeout_in_minutes
230
- @memo[:raw_stack].value['TimeoutInMinutes']
231
- end
232
- alias_method :timeout, :timeout_in_minutes
233
-
234
- def stack_id
235
- @memo[:raw_stack].value['StackId']
236
- end
237
- alias_method :id, :stack_id
238
-
239
- def creation_time
240
- @memo[:raw_stack].value['CreationTime']
241
- end
242
- alias_method :created_at, :creation_time
243
-
244
- def status(force_refresh=nil)
245
- load_stack if refresh?(force_refresh)
246
- @memo[:raw_stack].value['StackStatus']
247
- end
248
-
249
- def resources(force_refresh=nil)
250
- load_resources if @memo[:raw_resources].nil? || refresh?(force_refresh)
251
- @memo[:raw_resources].value
252
- end
253
-
254
- def events(all=false)
255
- @memo.init(:events, :stamped)
256
- res = []
257
- if(@memo[:events].value.nil? || refresh?)
258
- begin
259
- @memo.init(:events_lock, :lock)
260
- @memo[:events_lock].lock do
261
- if(@memo[:events].update_allowed?)
262
- res = common.aws(:cloud_formation).describe_stack_events(name).body['StackEvents']
263
- current = @memo[:events].value || []
264
- current_events = current.map{|e| e['EventId']}
265
- res.delete_if{|e| current_events.include?(e['EventId'])}
266
- current += res
267
- current.uniq!
268
- current.sort!{|x,y| x['Timestamp'] <=> y['Timestamp']}
269
- @memo[:events].value = current
270
- end
271
- end
272
- rescue => e
273
- if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
274
- debug 'Got lock timeout on events'
275
- else
276
- raise
277
- end
278
- end
279
- end
280
- all ? @memo[:events].value : res
281
- end
282
-
283
- def outputs(style=:unformatted)
284
- case style
285
- when :formatted
286
- Hash[*(
287
- @memo[:raw_stack].value['Outputs'].map do |item|
288
- [item['OutputKey'].gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym, item['OutputValue']]
289
- end.flatten
290
- )]
291
- when :unformatted
292
- Hash[*(
293
- @memo[:raw_stack].value['Outputs'].map do |item|
294
- [item['OutputKey'], item['OutputValue']]
295
- end.flatten
296
- )]
297
- else
298
- @memo[:raw_stack].value['Outputs']
299
- end
300
- end
301
-
302
- def event_start_index(given_events, status)
303
- Array(given_events).flatten.compact.rindex do |e|
304
- e['ResourceType'] == 'AWS::CloudFormation::Stack' &&
305
- e['ResourceStatus'] == status.to_s.upcase
306
- end.to_i
307
- end
308
-
309
- # min:: do not return value lower than this (defaults to 5)
310
- # Returns Numeric < 100 to represent completed resources
311
- # percentage (never returns less than 5)
312
- def percent_complete(min=5)
313
- if(complete?)
314
- 100
315
- else
316
- all_events = events(:all)
317
- if(all_events)
318
- total_expected = template['Resources'].size
319
- action = performing
320
- start = event_start_index(all_events, "#{action}_in_progress".to_sym)
321
- finished = all_events.find_all do |e|
322
- e['ResourceStatus'] == "#{action}_complete".upcase ||
323
- e['ResourceStatus'] == "#{action}_failed".upcase
324
- end.size
325
- calculated = ((finished / total_expected.to_f) * 100).to_i
326
- calculated < min ? min : calculated
327
- else
328
- 100 # Assume deletion and no events == complete
329
- end
330
- end
331
- end
332
-
333
- ## State ##
334
-
335
- def in_progress?
336
- status.to_s.downcase.end_with?('in_progress')
337
- end
338
-
339
- def complete?
340
- stat = status.to_s.downcase
341
- stat.end_with?('complete') || stat.end_with?('failed')
342
- end
343
-
344
- def failed?
345
- stat = status.to_s.downcase
346
- stat.end_with?('failed') || (stat.include?('rollback') && stat.end_with?('complete'))
347
- end
348
-
349
- def success?
350
- !failed? && complete?
351
- end
352
-
353
- def creating?
354
- in_progress? && status.to_s.downcase.start_with?('create')
355
- end
356
-
357
- def deleting?
358
- in_progress? && status.to_s.downcase.start_with?('delete')
359
- end
360
-
361
- def updating?
362
- in_progress? && status.to_s.downcase.start_with?('update')
363
- end
364
-
365
- def rollbacking?
366
- in_progress? && status.to_s.downcase.start_with?('rollback')
367
- end
368
-
369
- def performing
370
- if(in_progress?)
371
- status.to_s.downcase.split('_').first.to_sym
372
- end
373
- end
374
-
375
- # Lets build in some color coding!
376
- def red?
377
- failed? || deleting?
378
- end
379
-
380
- def yellow?
381
- !red? && !green?
382
- end
383
-
384
- def green?
385
- success? || creating? || updating?
386
- end
387
-
388
- ## Fog instance helpers ##
389
-
390
- RESOURCE_FILTER_KEYS = {
391
- :auto_scaling_group => 'AutoScalingGroupNames'
392
- }
393
-
394
- def expand_resource(resource)
395
- kind = resource['ResourceType'].split('::')[1]
396
- kind_snake = snake(kind)
397
- aws = common.aws(kind_snake)
398
- aws.send("#{snake(resource['ResourceType'].split('::').last).to_s.split('_').last}s").get(resource['PhysicalResourceId'])
399
- end
400
-
401
- def nodes
402
- if(@local[:nodes].empty?)
403
- as_resources = resources.find_all do |r|
404
- r['ResourceType'] == 'AWS::AutoScaling::AutoScalingGroup'
405
- end
406
- value = as_resources.map do |as_resource|
407
- as_group = expand_resource(as_resource)
408
- as_group.instances.map do |inst|
409
- common.aws(:ec2).servers.get(inst.id)
410
- end
411
- end.flatten
412
- @local[:nodes] = value unless in_progress?
413
- end
414
- value || @local[:nodes]
415
- end
416
-
417
- def nodes_data(*args)
418
- cache_key = ['nd', name, Digest::SHA256.hexdigest(args.map(&:to_s).join)].join('_')
419
- @memo.init(cache_key, :value)
420
- unless(@memo[cache_key].value)
421
- data = nodes.map do |n|
422
- [:id, args].flatten.compact.map do |k|
423
- n.send(k)
424
- end
425
- end
426
- end
427
- unless(data.empty?)
428
- @memo[cache_key].value = data unless in_progress?
429
- end
430
- data || @memo[cache_key].value
431
- end
432
-
433
- end
434
- end
435
- end