knife-cloudformation 0.1.12 → 0.1.14
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.
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/knife-cloudformation.gemspec +4 -2
- data/lib/chef/knife/cloudformation_create.rb +109 -3
- data/lib/chef/knife/cloudformation_destroy.rb +6 -2
- data/lib/chef/knife/cloudformation_list.rb +26 -1
- data/lib/knife-cloudformation/aws_commons/stack.rb +195 -55
- data/lib/knife-cloudformation/aws_commons.rb +64 -58
- data/lib/knife-cloudformation/cache.rb +204 -0
- data/lib/knife-cloudformation/utils.rb +18 -0
- data/lib/knife-cloudformation/version.rb +1 -1
- metadata +40 -8
- data/lib/knife-cloudformation/sparkle_attribute.rb +0 -67
- data/lib/knife-cloudformation/sparkle_formation.rb +0 -132
data/CHANGELOG.md
CHANGED
data/Gemfile
ADDED
@@ -10,8 +10,10 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = 'Knife tooling for Cloud Formation'
|
11
11
|
s.require_path = 'lib'
|
12
12
|
s.add_dependency 'chef'
|
13
|
-
s.add_dependency 'fog', '~> 1.
|
13
|
+
s.add_dependency 'fog', '~> 1.17'
|
14
14
|
s.add_dependency 'net-sftp'
|
15
|
-
s.add_dependency '
|
15
|
+
s.add_dependency 'sparkle_formation', '~> 0.1.2'
|
16
|
+
s.add_dependency 'redis-objects'
|
17
|
+
s.add_dependency 'attribute_struct', '~> 0.1.8'
|
16
18
|
s.files = Dir['**/*']
|
17
19
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
require '
|
1
|
+
require 'sparkle_formation'
|
2
|
+
require 'pathname'
|
2
3
|
require 'chef/knife/cloudformation_base'
|
3
4
|
|
4
5
|
class Chef
|
@@ -94,6 +95,16 @@ class Chef
|
|
94
95
|
:long => '--print-only',
|
95
96
|
:description => 'Print template and exit'
|
96
97
|
)
|
98
|
+
option(:base_directory,
|
99
|
+
:long => '--cloudformation-directory PATH',
|
100
|
+
:description => 'Path to cloudformation directory',
|
101
|
+
:proc => lambda {|val| Chef::Config[:knife][:cloudformation][:base_directory] = val}
|
102
|
+
)
|
103
|
+
option(:no_base_directory,
|
104
|
+
:long => '--no-cloudformation-directory',
|
105
|
+
:description => 'Unset any value used for cloudformation path',
|
106
|
+
:proc => lambda {|*val| Chef::Config[:knife][:cloudformation][:base_directory] = nil}
|
107
|
+
)
|
97
108
|
|
98
109
|
%w(rollback polling interactive_parameters).each do |key|
|
99
110
|
if(Chef::Config[:knife][:cloudformation][key].nil?)
|
@@ -109,13 +120,20 @@ class Chef
|
|
109
120
|
|
110
121
|
def run
|
111
122
|
@action_type = self.class.name.split('::').last.sub('Cloudformation', '').upcase
|
123
|
+
name = name_args.first
|
124
|
+
unless(name)
|
125
|
+
ui.fatal "Formation name must be specified!"
|
126
|
+
exit 1
|
127
|
+
end
|
128
|
+
|
129
|
+
set_paths_and_discover_file!
|
112
130
|
unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
|
113
131
|
ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
|
114
132
|
exit 1
|
115
133
|
end
|
116
|
-
|
134
|
+
|
117
135
|
if(Chef::Config[:knife][:cloudformation][:processing])
|
118
|
-
file =
|
136
|
+
file = SparkleFormation.compile(Chef::Config[:knife][:cloudformation][:file])
|
119
137
|
else
|
120
138
|
file = _from_json(File.read(Chef::Config[:knife][:cloudformation][:file]))
|
121
139
|
end
|
@@ -176,6 +194,94 @@ class Chef
|
|
176
194
|
end
|
177
195
|
end
|
178
196
|
|
197
|
+
private
|
198
|
+
|
199
|
+
def set_paths_and_discover_file!
|
200
|
+
if(Chef::Config[:knife][:cloudformation][:base_directory])
|
201
|
+
SparkleFormation.components_path = File.join(
|
202
|
+
Chef::Config[:knife][:cloudformation][:base_directory], 'components'
|
203
|
+
)
|
204
|
+
SparkleFormation.dynamics_path = File.join(
|
205
|
+
Chef::Config[:knife][:cloudformation][:base_directory], 'dynamics'
|
206
|
+
)
|
207
|
+
end
|
208
|
+
unless(Chef::Config[:knife][:cloudformation][:file])
|
209
|
+
Chef::Config[:knife][:cloudformation][:file] = prompt_for_file(
|
210
|
+
Chef::Config[:knife][:cloudformation][:base_directory] || File.join(Dir.pwd, 'cloudformation')
|
211
|
+
)
|
212
|
+
else
|
213
|
+
unless(Pathname(Chef::Config[:knife][:cloudformation][:file]).absolute?)
|
214
|
+
Chef::Config[:knife][:cloudformation][:file] = File.join(
|
215
|
+
Chef::Config[:knife][:cloudformation][:base_directory] || File.join(Dir.pwd, 'cloudformation'),
|
216
|
+
Chef::Config[:knife][:cloudformation][:file]
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
IGNORE_CF_DIRS = %w(dynamics components)
|
223
|
+
|
224
|
+
def prompt_for_file(dir)
|
225
|
+
directory = Dir.new(dir)
|
226
|
+
directories = directory.map do |d|
|
227
|
+
if(!d.start_with?('.') && !IGNORE_CF_DIRS.include?(d) && File.directory?(path = File.join(dir, d)))
|
228
|
+
path
|
229
|
+
end
|
230
|
+
end.compact.sort
|
231
|
+
files = directory.map do |f|
|
232
|
+
if(!f.start_with?('.') && File.file?(path = File.join(dir, f)))
|
233
|
+
path
|
234
|
+
end
|
235
|
+
end.compact.sort
|
236
|
+
if(directories.empty? && files.empty?)
|
237
|
+
ui.fatal 'No formation paths discoverable!'
|
238
|
+
else
|
239
|
+
output = ['Please select the formation to create']
|
240
|
+
output << '(or directory to list):' unless directories.empty?
|
241
|
+
ui.info output.join(' ')
|
242
|
+
output.clear
|
243
|
+
idx = 1
|
244
|
+
valid = {}
|
245
|
+
unless(directories.empty?)
|
246
|
+
output << ui.color('Directories:', :bold)
|
247
|
+
directories.each do |path|
|
248
|
+
valid[idx] = {:path => path, :type => :directory}
|
249
|
+
output << [idx, "#{File.basename(path).sub('.rb', '').split(/[-_]/).map(&:capitalize).join(' ')}"]
|
250
|
+
idx += 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
unless(files.empty?)
|
254
|
+
output << ui.color('Templates:', :bold)
|
255
|
+
files.each do |path|
|
256
|
+
valid[idx] = {:path => path, :type => :file}
|
257
|
+
output << [idx, "#{File.basename(path).sub('.rb', '').split(/[-_]/).map(&:capitalize).join(' ')}"]
|
258
|
+
idx += 1
|
259
|
+
end
|
260
|
+
end
|
261
|
+
max = idx.to_s.length
|
262
|
+
output.map! do |o|
|
263
|
+
if(o.is_a?(Array))
|
264
|
+
" #{o.first}.#{' ' * (max - o.first.to_s.length)} #{o.last}"
|
265
|
+
else
|
266
|
+
o
|
267
|
+
end
|
268
|
+
end
|
269
|
+
ui.info "#{output.join("\n")}\n"
|
270
|
+
response = ask_question('Enter selection: ').to_i
|
271
|
+
unless(valid[response])
|
272
|
+
ui.fatal 'How about using a real value'
|
273
|
+
exit 1
|
274
|
+
else
|
275
|
+
entry = valid[response.to_i]
|
276
|
+
if(entry[:type] == :directory)
|
277
|
+
prompt_for_file(entry[:path])
|
278
|
+
else
|
279
|
+
Chef::Config[:knife][:cloudformation][:file] = entry[:path]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
179
285
|
end
|
180
286
|
end
|
181
287
|
end
|
@@ -26,8 +26,12 @@ class Chef
|
|
26
26
|
ui.info "Destroy request sent for stack: #{ui.color(stack_name, :bold)}"
|
27
27
|
end
|
28
28
|
if(config[:polling])
|
29
|
-
|
30
|
-
|
29
|
+
begin
|
30
|
+
stacks.each do |stack_name|
|
31
|
+
poll_stack(stack_name)
|
32
|
+
end
|
33
|
+
rescue Fog::AWS::CloudFormation::NotFound
|
34
|
+
# ignore this error since this is the end result we want!
|
31
35
|
end
|
32
36
|
ui.info " -> Destroyed Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
|
33
37
|
end
|
@@ -38,10 +38,35 @@ class Chef
|
|
38
38
|
|
39
39
|
def get_list
|
40
40
|
get_things do
|
41
|
-
aws.
|
41
|
+
aws.aws(:cloud_formation).list_stacks(list_options).body['StackSummaries'].sort do |x,y|
|
42
|
+
if(y['CreationTime'].to_s.empty?)
|
43
|
+
-1
|
44
|
+
elsif(x['CreationTime'].to_s.empty?)
|
45
|
+
1
|
46
|
+
else
|
47
|
+
Time.parse(y['CreationTime'].to_s) <=> Time.parse(x['CreationTime'].to_s)
|
48
|
+
end
|
49
|
+
end
|
42
50
|
end
|
43
51
|
end
|
44
52
|
|
53
|
+
def list_options
|
54
|
+
status = Chef::Config[:knife][:cloudformation][:status] ||
|
55
|
+
KnifeCloudformation::AwsCommons::DEFAULT_STACK_STATUS
|
56
|
+
if(status.map(&:downcase).include?('none'))
|
57
|
+
filter = {}
|
58
|
+
else
|
59
|
+
count = 0
|
60
|
+
filter = Hash[*(
|
61
|
+
status.map do |n|
|
62
|
+
count += 1
|
63
|
+
["StackStatusFilter.member.#{count}", n]
|
64
|
+
end.flatten
|
65
|
+
)]
|
66
|
+
end
|
67
|
+
filter
|
68
|
+
end
|
69
|
+
|
45
70
|
def default_attributes
|
46
71
|
%w(StackName CreationTime StackStatus TemplateDescription)
|
47
72
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
-
require 'knife-cloudformation/
|
1
|
+
require 'knife-cloudformation/cache'
|
2
|
+
require 'knife-cloudformation/aws_commons'
|
3
|
+
require 'digest/sha2'
|
2
4
|
|
3
5
|
module KnifeCloudformation
|
4
6
|
class AwsCommons
|
5
7
|
class Stack
|
6
8
|
|
9
|
+
include KnifeCloudformation::Utils::Debug
|
7
10
|
include KnifeCloudformation::Utils::JSON
|
8
11
|
include KnifeCloudformation::Utils::AnimalStrings
|
9
12
|
|
@@ -27,7 +30,7 @@ module KnifeCloudformation
|
|
27
30
|
def build_stack_definition(template, options={})
|
28
31
|
stack = Mash.new
|
29
32
|
options.each do |key, value|
|
30
|
-
format_key = key.split('_').map do |k|
|
33
|
+
format_key = key.to_s.split('_').map do |k|
|
31
34
|
"#{k.slice(0,1).upcase}#{k.slice(1,k.length)}"
|
32
35
|
end.join
|
33
36
|
stack[format_key] = value
|
@@ -61,12 +64,19 @@ module KnifeCloudformation
|
|
61
64
|
def initialize(name, common, raw_stack=nil)
|
62
65
|
@name = name
|
63
66
|
@common = common
|
64
|
-
@memo =
|
67
|
+
@memo = Cache.new(common.credentials.merge(:stack => name))
|
68
|
+
reset_local
|
69
|
+
@memo.init(:raw_stack, :stamped)
|
65
70
|
if(raw_stack)
|
66
|
-
@
|
67
|
-
|
68
|
-
|
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
|
69
78
|
end
|
79
|
+
load_stack
|
70
80
|
@force_refresh = false
|
71
81
|
@force_refresh = in_progress?
|
72
82
|
end
|
@@ -99,27 +109,63 @@ module KnifeCloudformation
|
|
99
109
|
res
|
100
110
|
end
|
101
111
|
|
102
|
-
def load_stack
|
103
|
-
@
|
104
|
-
|
105
|
-
.
|
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
|
106
131
|
end
|
107
132
|
|
108
133
|
def load_resources
|
109
|
-
@
|
110
|
-
|
111
|
-
.
|
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
|
112
151
|
end
|
113
152
|
|
114
153
|
def refresh?(bool=nil)
|
115
154
|
bool || (bool.nil? && @force_refresh)
|
116
155
|
end
|
117
156
|
|
157
|
+
def reset_local
|
158
|
+
@local = {
|
159
|
+
:nodes => []
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
118
163
|
def reload!
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
164
|
+
@memo.clear! do
|
165
|
+
load_stack(:force)
|
166
|
+
load_resources
|
167
|
+
@force_refresh = in_progress?
|
168
|
+
end
|
123
169
|
true
|
124
170
|
end
|
125
171
|
|
@@ -141,99 +187,142 @@ module KnifeCloudformation
|
|
141
187
|
end
|
142
188
|
|
143
189
|
def template
|
144
|
-
|
145
|
-
|
190
|
+
@memo.init(:template, :value)
|
191
|
+
unless(@memo[:template].value)
|
192
|
+
@memo[:template].value = _from_json(
|
146
193
|
common.aws(:cloud_formation)
|
147
194
|
.get_template(name).body['TemplateBody']
|
148
195
|
)
|
149
196
|
end
|
150
|
-
@memo[:template]
|
197
|
+
@memo[:template].value
|
151
198
|
end
|
152
199
|
|
153
200
|
## Stack metadata ##
|
154
201
|
def parameters(raw=false)
|
155
202
|
if(raw)
|
156
|
-
@raw_stack['Parameters']
|
203
|
+
@memo[:raw_stack].value['Parameters']
|
157
204
|
else
|
158
|
-
|
159
|
-
|
160
|
-
|
205
|
+
@memo.init(:parameters, :value)
|
206
|
+
unless(@memo[:parameters].value)
|
207
|
+
@memo[:parameters].value = Hash[*(
|
208
|
+
@memo[:raw_stack].value['Parameters'].map do |ary|
|
161
209
|
[ary['ParameterKey'], ary['ParameterValue']]
|
162
210
|
end.flatten
|
163
211
|
)]
|
164
212
|
end
|
165
|
-
@memo[:parameters]
|
213
|
+
@memo[:parameters].value
|
166
214
|
end
|
167
215
|
end
|
168
216
|
|
169
217
|
def capabilities
|
170
|
-
@raw_stack['Capabilities']
|
218
|
+
@memo[:raw_stack].value['Capabilities']
|
171
219
|
end
|
172
220
|
|
173
221
|
def disable_rollback
|
174
|
-
@raw_stack['DisableRollback']
|
222
|
+
@memo[:raw_stack].value['DisableRollback']
|
175
223
|
end
|
176
224
|
|
177
225
|
def notification_arns
|
178
|
-
@raw_stack['NotificationARNs']
|
226
|
+
@memo[:raw_stack].value['NotificationARNs']
|
179
227
|
end
|
180
228
|
|
181
229
|
def timeout_in_minutes
|
182
|
-
@raw_stack['TimeoutInMinutes']
|
230
|
+
@memo[:raw_stack].value['TimeoutInMinutes']
|
183
231
|
end
|
184
232
|
alias_method :timeout_in_minutes, :timeout
|
185
233
|
|
186
234
|
def stack_id
|
187
|
-
@raw_stack['StackId']
|
235
|
+
@memo[:raw_stack].value['StackId']
|
188
236
|
end
|
189
237
|
alias_method :id, :stack_id
|
190
238
|
|
191
239
|
def creation_time
|
192
|
-
@raw_stack['CreationTime']
|
240
|
+
@memo[:raw_stack].value['CreationTime']
|
193
241
|
end
|
194
242
|
alias_method :created_at, :creation_time
|
195
243
|
|
196
244
|
def status(force_refresh=nil)
|
197
245
|
load_stack if refresh?(force_refresh)
|
198
|
-
@raw_stack['StackStatus']
|
246
|
+
@memo[:raw_stack].value['StackStatus']
|
199
247
|
end
|
200
248
|
|
201
249
|
def resources(force_refresh=nil)
|
202
|
-
load_resources if @raw_resources.nil? || refresh?(force_refresh)
|
203
|
-
@raw_resources
|
250
|
+
load_resources if @memo[:raw_resources].nil? || refresh?(force_refresh)
|
251
|
+
@memo[:raw_resources].value
|
204
252
|
end
|
205
253
|
|
206
254
|
def events(all=false)
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
217
279
|
end
|
218
|
-
all ? @memo[:events] : res
|
280
|
+
all ? @memo[:events].value : res
|
219
281
|
end
|
220
282
|
|
221
283
|
def outputs(style=:unformatted)
|
222
284
|
case style
|
223
285
|
when :formatted
|
224
286
|
Hash[*(
|
225
|
-
@raw_stack['Outputs'].map do |item|
|
287
|
+
@memo[:raw_stack].value['Outputs'].map do |item|
|
226
288
|
[item['OutputKey'].gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym, item['OutputValue']]
|
227
289
|
end.flatten
|
228
290
|
)]
|
229
291
|
when :unformatted
|
230
292
|
Hash[*(
|
231
|
-
@raw_stack['Outputs'].map do |item|
|
293
|
+
@memo[:raw_stack].value['Outputs'].map do |item|
|
232
294
|
[item['OutputKey'], item['OutputValue']]
|
233
295
|
end.flatten
|
234
296
|
)]
|
235
297
|
else
|
236
|
-
@raw_stack['Outputs']
|
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
|
+
total_expected = template['Resources'].size
|
318
|
+
action = performing
|
319
|
+
start = event_start_index(all_events, "#{action}_in_progress".to_sym)
|
320
|
+
finished = all_events.find_all do |e|
|
321
|
+
e['ResourceStatus'] == "#{action}_complete".upcase ||
|
322
|
+
e['ResourceStatus'] == "#{action}_failed".upcase
|
323
|
+
end.size
|
324
|
+
calculated = ((finished / total_expected.to_f) * 100).to_i
|
325
|
+
calculated < min ? min : calculated
|
237
326
|
end
|
238
327
|
end
|
239
328
|
|
@@ -254,7 +343,42 @@ module KnifeCloudformation
|
|
254
343
|
end
|
255
344
|
|
256
345
|
def success?
|
257
|
-
!failed?
|
346
|
+
!failed? && complete?
|
347
|
+
end
|
348
|
+
|
349
|
+
def creating?
|
350
|
+
in_progress? && status.to_s.downcase.start_with?('create')
|
351
|
+
end
|
352
|
+
|
353
|
+
def deleting?
|
354
|
+
in_progress? && status.to_s.downcase.start_with?('delete')
|
355
|
+
end
|
356
|
+
|
357
|
+
def updating?
|
358
|
+
in_progress? && status.to_s.downcase.start_with?('update')
|
359
|
+
end
|
360
|
+
|
361
|
+
def rollbacking?
|
362
|
+
in_progress? && status.to_s.downcase.start_with?('rollback')
|
363
|
+
end
|
364
|
+
|
365
|
+
def performing
|
366
|
+
if(in_progress?)
|
367
|
+
status.to_s.downcase.split('_').first.to_sym
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Lets build in some color coding!
|
372
|
+
def red?
|
373
|
+
failed? || deleting?
|
374
|
+
end
|
375
|
+
|
376
|
+
def yellow?
|
377
|
+
!red? && !green?
|
378
|
+
end
|
379
|
+
|
380
|
+
def green?
|
381
|
+
success? || creating? || updating?
|
258
382
|
end
|
259
383
|
|
260
384
|
## Fog instance helpers ##
|
@@ -271,18 +395,34 @@ module KnifeCloudformation
|
|
271
395
|
end
|
272
396
|
|
273
397
|
def nodes
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
398
|
+
if(@local[:nodes].empty?)
|
399
|
+
as_resources = resources.find_all do |r|
|
400
|
+
r['ResourceType'] == 'AWS::AutoScaling::AutoScalingGroup'
|
401
|
+
end
|
402
|
+
@local[:nodes] = as_resources.map do |as_resource|
|
279
403
|
as_group = expand_resource(as_resource)
|
280
404
|
as_group.instances.map do |inst|
|
281
405
|
common.aws(:ec2).servers.get(inst.id)
|
282
406
|
end
|
283
407
|
end.flatten
|
284
408
|
end
|
285
|
-
@
|
409
|
+
@local[:nodes]
|
410
|
+
end
|
411
|
+
|
412
|
+
def nodes_data(*args)
|
413
|
+
cache_key = ['nd', name, Digest::SHA256.hexdigest(args.map(&:to_s).join)].join('_')
|
414
|
+
@memo.init(cache_key, :value)
|
415
|
+
unless(@memo[cache_key].value)
|
416
|
+
data = nodes.map do |n|
|
417
|
+
[:id, args].flatten.compact.map do |k|
|
418
|
+
n.send(k)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
unless(data.empty?)
|
423
|
+
@memo[cache_key].value = data
|
424
|
+
end
|
425
|
+
@memo[cache_key].value || data
|
286
426
|
end
|
287
427
|
|
288
428
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'fog'
|
2
2
|
require 'knife-cloudformation/utils'
|
3
|
+
require 'knife-cloudformation/cache'
|
3
4
|
|
4
5
|
Dir.glob(File.join(File.dirname(__FILE__), 'aws_commons/*.rb')).each do |item|
|
5
6
|
require "knife-cloudformation/aws_commons/#{File.basename(item).sub('.rb', '')}"
|
@@ -8,26 +9,29 @@ end
|
|
8
9
|
module KnifeCloudformation
|
9
10
|
class AwsCommons
|
10
11
|
|
12
|
+
include KnifeCloudformation::Utils::AnimalStrings
|
13
|
+
include KnifeCloudformation::Utils::Debug
|
14
|
+
|
11
15
|
FOG_MAP = {
|
12
16
|
:ec2 => :compute
|
13
17
|
}
|
14
18
|
|
19
|
+
attr_reader :credentials
|
20
|
+
|
15
21
|
def initialize(args={})
|
16
22
|
@ui = args[:ui]
|
17
|
-
@creds = args[:fog]
|
23
|
+
@credentials = @creds = args[:fog]
|
18
24
|
@connections = {}
|
19
|
-
@memo =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
@memo = Cache.new(credentials)
|
26
|
+
@local = {:stacks => {}}
|
27
|
+
end
|
28
|
+
|
29
|
+
def cache
|
30
|
+
@memo
|
24
31
|
end
|
25
32
|
|
26
33
|
def clear_cache(*types)
|
27
|
-
|
28
|
-
keys.each do |key|
|
29
|
-
@memo[key].clear if @memo[key]
|
30
|
-
end
|
34
|
+
@memo.clear!(*types)
|
31
35
|
true
|
32
36
|
end
|
33
37
|
|
@@ -43,9 +47,20 @@ module KnifeCloudformation
|
|
43
47
|
dns_creds.delete(:region) || dns_creds.delete('region')
|
44
48
|
@connections[:dns] = Fog::DNS::AWS.new(dns_creds)
|
45
49
|
else
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
begin
|
51
|
+
Fog.credentials = Fog.symbolize_credentials(@creds)
|
52
|
+
@connections[type] = Fog::AWS[type]
|
53
|
+
Fog.credentials = {}
|
54
|
+
rescue NameError
|
55
|
+
klass = [camel(type.to_s), 'AWS'].inject(Fog) do |memo, item|
|
56
|
+
memo.const_defined?(item) ? memo.const_get(item) : break
|
57
|
+
end
|
58
|
+
if(klass)
|
59
|
+
@connections[type] = klass.new(Fog.symbolize_credentials(@creds))
|
60
|
+
else
|
61
|
+
raise
|
62
|
+
end
|
63
|
+
end
|
49
64
|
end
|
50
65
|
end
|
51
66
|
@connections[type]
|
@@ -62,64 +77,55 @@ module KnifeCloudformation
|
|
62
77
|
)
|
63
78
|
|
64
79
|
def stacks(args={})
|
65
|
-
status = args[:status] || DEFAULT_STACK_STATUS
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
80
|
+
status = Array(args[:status] || DEFAULT_STACK_STATUS).flatten.compact.map do |stat|
|
81
|
+
stat.to_s.upcase
|
82
|
+
end
|
83
|
+
@memo.init(:stacks_lock, :lock)
|
84
|
+
@memo.init(:stacks, :stamped)
|
85
|
+
if(args[:cache_time])
|
86
|
+
@memo[:stacks].stamp
|
71
87
|
else
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
88
|
+
if(args[:refresh_every])
|
89
|
+
cache.apply_limit(:stacks, args[:refresh_every].to_i)
|
90
|
+
end
|
91
|
+
if(@memo[:stacks].update_allowed? || args[:force_refresh])
|
92
|
+
@memo[:stacks_lock].lock do
|
93
|
+
@memo[:stacks].value = aws(:cloud_formation).describe_stacks.body['Stacks']
|
94
|
+
end
|
95
|
+
end
|
78
96
|
end
|
79
|
-
|
80
|
-
|
97
|
+
@memo[:stacks].value.find_all do |s|
|
98
|
+
status.include?(s['StackStatus'])
|
81
99
|
end
|
82
|
-
@memo[:stack_list][key]
|
83
100
|
end
|
84
101
|
|
85
|
-
def name_from_stack_id(
|
102
|
+
def name_from_stack_id(s_id)
|
86
103
|
found = stacks.detect do |s|
|
87
|
-
s['StackId'] ==
|
104
|
+
s['StackId'] == s_id
|
88
105
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
106
|
+
found ? found['StackName'] : raise(IndexError.new("Failed to locate stack with ID: #{s_id}"))
|
107
|
+
end
|
108
|
+
|
109
|
+
def id_from_stack_name(name)
|
110
|
+
found = stacks.detect do |s|
|
111
|
+
s['StackName'] == name
|
93
112
|
end
|
113
|
+
found ? found['StackId'] : raise(IndexError.new("Failed to locate stack with name: #{name}"))
|
94
114
|
end
|
95
115
|
|
96
116
|
def stack(*names)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
104
|
-
if(names.size == 1)
|
105
|
-
name = names.first
|
106
|
-
unless(@memo[:stacks][name])
|
107
|
-
@memo[:stacks][name] = Stack.new(name, self)
|
108
|
-
end
|
109
|
-
@memo[:stacks][name]
|
110
|
-
else
|
111
|
-
to_fetch = names - @memo[:stacks].keys
|
112
|
-
slim_stacks = {}
|
113
|
-
unless(to_fetch.empty?)
|
114
|
-
to_fetch.each do |name|
|
115
|
-
slim_stacks[name] = Stack.new(name, self, stacks.detect{|s| s['StackName'] == name})
|
117
|
+
result = names.map do |name|
|
118
|
+
[name, name.start_with?('arn:') ? name : id_from_stack_name(name)]
|
119
|
+
end.map do |name, s_id|
|
120
|
+
unless(@local[:stacks][s_id])
|
121
|
+
seed = stacks.detect do |stk|
|
122
|
+
stk['StackId'] == s_id
|
116
123
|
end
|
124
|
+
@local[:stacks][s_id] = Stack.new(name, self, seed)
|
117
125
|
end
|
118
|
-
|
119
|
-
@memo[:stacks][n] || slim_stacks[n]
|
120
|
-
end
|
121
|
-
result
|
126
|
+
@local[:stacks][s_id]
|
122
127
|
end
|
128
|
+
result.size == 1 ? result.first : result
|
123
129
|
end
|
124
130
|
|
125
131
|
def create_stack(name, definition)
|
@@ -131,7 +137,7 @@ module KnifeCloudformation
|
|
131
137
|
def process(things, args={})
|
132
138
|
@event_ids ||= []
|
133
139
|
processed = things.reverse.map do |thing|
|
134
|
-
next if @
|
140
|
+
next if @event_ids.include?(thing['EventId'])
|
135
141
|
@event_ids.push(thing['EventId']).compact!
|
136
142
|
if(args[:attributes])
|
137
143
|
args[:attributes].map do |key|
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
class Cache
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def configure(type, args={})
|
9
|
+
type = type.to_sym
|
10
|
+
case type
|
11
|
+
when :redis
|
12
|
+
require 'redis-objects'
|
13
|
+
Redis::Objects.redis = Redis.new(args)
|
14
|
+
when :local
|
15
|
+
else
|
16
|
+
raise TypeError.new("Unsupported caching type: #{type}")
|
17
|
+
end
|
18
|
+
enable(type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def enable(type)
|
22
|
+
@type = type.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
@type || :local
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply_limit(kind, seconds=nil)
|
30
|
+
@apply_limit ||= {}
|
31
|
+
if(seconds)
|
32
|
+
@apply_limit[kind.to_sym] = seconds.to_i
|
33
|
+
end
|
34
|
+
@apply_limit[kind.to_sym].to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_limits
|
38
|
+
(@apply_limit || {}).dup
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :key
|
44
|
+
attr_reader :direct_store
|
45
|
+
|
46
|
+
def initialize(key)
|
47
|
+
if(key.respond_to?(:sort))
|
48
|
+
key = key.flatten if key.respond_to?(:flatten)
|
49
|
+
key = key.map(&:to_s).sort
|
50
|
+
end
|
51
|
+
@key = Digest::SHA256.hexdigest(key.to_s)
|
52
|
+
@direct_store = {}
|
53
|
+
@apply_limit = self.class.default_limits
|
54
|
+
end
|
55
|
+
|
56
|
+
def init(name, kind, args={})
|
57
|
+
name = name.to_sym
|
58
|
+
unless(@direct_store[name])
|
59
|
+
full_name = [key, name.to_s].join('_')
|
60
|
+
@direct_store[name] = get_storage(self.class.type, kind, full_name, args)
|
61
|
+
end
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear!(*args)
|
66
|
+
internal_lock do
|
67
|
+
args = @direct_store.keys if args.empty?
|
68
|
+
args.each do |key|
|
69
|
+
value = @direct_store[key]
|
70
|
+
if(value.respond_to?(:clear))
|
71
|
+
value.clear
|
72
|
+
elsif(value.respond_to?(:value))
|
73
|
+
value.value = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
yield if block_given?
|
77
|
+
end
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_storage(store_type, data_type, full_name, args={})
|
82
|
+
case store_type.to_sym
|
83
|
+
when :redis
|
84
|
+
get_redis_storage(data_type, full_name, args)
|
85
|
+
when :local
|
86
|
+
get_local_storage(data_type, full_name, args)
|
87
|
+
else
|
88
|
+
raise TypeError.new("Unsupported caching storage type encountered: #{store_type}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_redis_storage(data_type, full_name, args={})
|
93
|
+
case data_type.to_sym
|
94
|
+
when :array
|
95
|
+
Redis::List.new(full_name, {:marshal => true}.merge(args))
|
96
|
+
when :hash
|
97
|
+
Redis::HashKey.new(full_name)
|
98
|
+
when :value
|
99
|
+
Redis::Value.new(full_name, {:marshal => true}.merge(args))
|
100
|
+
when :lock
|
101
|
+
Redis::Lock.new(full_name, {:expiration => 3, :timeout => 0.1}.merge(args))
|
102
|
+
when :stamped
|
103
|
+
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_redis_storage(:value, full_name), self)
|
104
|
+
else
|
105
|
+
raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_local_storage(data_type, full_name, args={})
|
110
|
+
case data_type.to_sym
|
111
|
+
when :array
|
112
|
+
[]
|
113
|
+
when :hash
|
114
|
+
{}
|
115
|
+
when :value
|
116
|
+
LocalValue.new
|
117
|
+
when :lock
|
118
|
+
LocalLock.new
|
119
|
+
when :stamped
|
120
|
+
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_local_storage(:value, full_name), self)
|
121
|
+
else
|
122
|
+
raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def internal_lock
|
127
|
+
get_storage(self.class.type, :lock, :internal_access, :timeout => 20).lock do
|
128
|
+
yield
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def [](name)
|
133
|
+
internal_lock do
|
134
|
+
@direct_store[name.to_sym]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def []=(key, val)
|
139
|
+
raise 'Setting backend data is not allowed'
|
140
|
+
end
|
141
|
+
|
142
|
+
def time_check_allow?(key, stamp)
|
143
|
+
Time.now.to_i - stamp.to_i > apply_limit(key)
|
144
|
+
end
|
145
|
+
|
146
|
+
def apply_limit(kind, seconds=nil)
|
147
|
+
@apply_limit ||= {}
|
148
|
+
if(seconds)
|
149
|
+
@apply_limit[kind.to_sym] = seconds.to_i
|
150
|
+
end
|
151
|
+
@apply_limit[kind.to_sym].to_i
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
class LocalValue
|
156
|
+
attr_accessor :value
|
157
|
+
def initialize(*args)
|
158
|
+
@value = nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class LocalLock
|
163
|
+
def initialize(*args)
|
164
|
+
end
|
165
|
+
|
166
|
+
def lock
|
167
|
+
yield
|
168
|
+
end
|
169
|
+
|
170
|
+
def clear
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Stamped
|
175
|
+
|
176
|
+
def initialize(name, base, cache)
|
177
|
+
@name = name.to_sym
|
178
|
+
@base = base
|
179
|
+
@cache = cache
|
180
|
+
end
|
181
|
+
|
182
|
+
def value
|
183
|
+
@base.value[:value] if set?
|
184
|
+
end
|
185
|
+
|
186
|
+
def value=(v)
|
187
|
+
@base.value = {:stamp => Time.now.to_i, :value => v}
|
188
|
+
end
|
189
|
+
|
190
|
+
def set?
|
191
|
+
@base.value.is_a?(Hash)
|
192
|
+
end
|
193
|
+
|
194
|
+
def stamp
|
195
|
+
@base.value[:stamp] if set?
|
196
|
+
end
|
197
|
+
|
198
|
+
def update_allowed?
|
199
|
+
!set? || @cache.time_check_allow?(@name, @base.value[:stamp])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
@@ -1,5 +1,23 @@
|
|
1
1
|
module KnifeCloudformation
|
2
2
|
module Utils
|
3
|
+
|
4
|
+
module Debug
|
5
|
+
module Output
|
6
|
+
def debug(msg)
|
7
|
+
puts "<KnifeCloudformation>: #{msg}" if ENV['DEBUG']
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def included(klass)
|
13
|
+
klass.class_eval do
|
14
|
+
include Output
|
15
|
+
extend Output
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
3
21
|
module JSON
|
4
22
|
|
5
23
|
def try_json_compat
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-cloudformation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.14
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-10-
|
12
|
+
date: 2013-10-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: '1.
|
37
|
+
version: '1.17'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: '1.
|
45
|
+
version: '1.17'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: net-sftp
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +59,38 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sparkle_formation
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.1.2
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.1.2
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: redis-objects
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
62
94
|
- !ruby/object:Gem::Dependency
|
63
95
|
name: attribute_struct
|
64
96
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,7 +98,7 @@ dependencies:
|
|
66
98
|
requirements:
|
67
99
|
- - ~>
|
68
100
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.1.
|
101
|
+
version: 0.1.8
|
70
102
|
type: :runtime
|
71
103
|
prerelease: false
|
72
104
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -74,7 +106,7 @@ dependencies:
|
|
74
106
|
requirements:
|
75
107
|
- - ~>
|
76
108
|
- !ruby/object:Gem::Version
|
77
|
-
version: 0.1.
|
109
|
+
version: 0.1.8
|
78
110
|
description: Knife tooling for Cloud Formation
|
79
111
|
email: chrisroberts.code@gmail.com
|
80
112
|
executables: []
|
@@ -90,13 +122,13 @@ files:
|
|
90
122
|
- lib/chef/knife/cloudformation_update.rb
|
91
123
|
- lib/chef/knife/cloudformation_inspect.rb
|
92
124
|
- lib/knife-cloudformation.rb
|
93
|
-
- lib/knife-cloudformation/sparkle_attribute.rb
|
94
125
|
- lib/knife-cloudformation/version.rb
|
95
126
|
- lib/knife-cloudformation/aws_commons.rb
|
96
|
-
- lib/knife-cloudformation/
|
127
|
+
- lib/knife-cloudformation/cache.rb
|
97
128
|
- lib/knife-cloudformation/utils.rb
|
98
129
|
- lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb
|
99
130
|
- lib/knife-cloudformation/aws_commons/stack.rb
|
131
|
+
- Gemfile
|
100
132
|
- README.md
|
101
133
|
- knife-cloudformation.gemspec
|
102
134
|
- CHANGELOG.md
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'attribute_struct'
|
2
|
-
|
3
|
-
module SparkleAttribute
|
4
|
-
|
5
|
-
# TODO: look at the docs for Fn stuff. We can probably just map
|
6
|
-
# simple ones with a bit of string manipulations
|
7
|
-
|
8
|
-
def _cf_join(*args)
|
9
|
-
options = args.detect{|i| i.is_a?(Hash) && i[:options]} || {:options => {}}
|
10
|
-
args.delete(options)
|
11
|
-
unless(args.size == 1)
|
12
|
-
args = [args]
|
13
|
-
end
|
14
|
-
{'Fn::Join' => [options[:options][:delimiter] || '', *args]}
|
15
|
-
end
|
16
|
-
|
17
|
-
def _cf_ref(thing)
|
18
|
-
thing = _process_key(thing, :force) if thing.is_a?(Symbol)
|
19
|
-
{'Ref' => thing}
|
20
|
-
end
|
21
|
-
|
22
|
-
def _cf_map(thing, key, *suffix)
|
23
|
-
suffix = suffix.map do |item|
|
24
|
-
if(item.is_a?(Symbol))
|
25
|
-
_process_key(item, :force)
|
26
|
-
else
|
27
|
-
item
|
28
|
-
end
|
29
|
-
end
|
30
|
-
thing = _process_key(thing, :force) if thing.is_a?(Symbol)
|
31
|
-
key = _process_key(key, :force) if key.is_a?(Symbol)
|
32
|
-
{'Fn::FindInMap' => [_process_key(thing), {'Ref' => _process_key(key)}, *suffix]}
|
33
|
-
end
|
34
|
-
|
35
|
-
def _cf_attr(*args)
|
36
|
-
args = args.map do |thing|
|
37
|
-
if(thing.is_a?(Symbol))
|
38
|
-
_process_key(thing, :force)
|
39
|
-
else
|
40
|
-
thing
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
{'Fn::GetAtt' => args}
|
45
|
-
end
|
46
|
-
|
47
|
-
def _cf_base64(arg)
|
48
|
-
{'Fn::Base64' => arg}
|
49
|
-
end
|
50
|
-
|
51
|
-
def rhel?
|
52
|
-
!!@platform[:rhel]
|
53
|
-
end
|
54
|
-
|
55
|
-
def debian?
|
56
|
-
!!@platform[:debian]
|
57
|
-
end
|
58
|
-
|
59
|
-
def _platform=(plat)
|
60
|
-
@platform || __hashish
|
61
|
-
@platform.clear
|
62
|
-
@platform[plat.to_sym] = true
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
AttributeStruct.send(:include, SparkleAttribute)
|
@@ -1,132 +0,0 @@
|
|
1
|
-
require 'chef/mash'
|
2
|
-
require 'attribute_struct'
|
3
|
-
require 'knife-cloudformation/sparkle_attribute'
|
4
|
-
require 'knife-cloudformation/utils'
|
5
|
-
|
6
|
-
AttributeStruct.camel_keys = true
|
7
|
-
|
8
|
-
module KnifeCloudformation
|
9
|
-
class SparkleFormation
|
10
|
-
|
11
|
-
include KnifeCloudformation::Utils::AnimalStrings
|
12
|
-
|
13
|
-
class << self
|
14
|
-
|
15
|
-
attr_reader :dynamics
|
16
|
-
attr_reader :components_path
|
17
|
-
attr_reader :dynamics_path
|
18
|
-
|
19
|
-
def custom_paths
|
20
|
-
@_paths ||= {}
|
21
|
-
@_paths
|
22
|
-
end
|
23
|
-
|
24
|
-
def components_path=(path)
|
25
|
-
custom_paths[:sparkle_path] = path
|
26
|
-
end
|
27
|
-
|
28
|
-
def dynamics_path=(path)
|
29
|
-
custom_paths[:dynamics_directory] = path
|
30
|
-
end
|
31
|
-
|
32
|
-
def compile(path)
|
33
|
-
formation = self.instance_eval(IO.read(path), path, 1)
|
34
|
-
formation.compile._dump
|
35
|
-
end
|
36
|
-
|
37
|
-
def build(&block)
|
38
|
-
struct = AttributeStruct.new
|
39
|
-
struct.instance_exec(&block)
|
40
|
-
struct
|
41
|
-
end
|
42
|
-
|
43
|
-
def load_component(path)
|
44
|
-
self.instance_eval(IO.read(path), path, 1)
|
45
|
-
end
|
46
|
-
|
47
|
-
def load_dynamics!(directory)
|
48
|
-
@loaded_dynamics ||= []
|
49
|
-
Dir.glob(File.join(directory, '*.rb')).each do |dyn|
|
50
|
-
dyn = File.expand_path(dyn)
|
51
|
-
next if @loaded_dynamics.include?(dyn)
|
52
|
-
self.instance_eval(IO.read(dyn), dyn, 1)
|
53
|
-
@loaded_dynamics << dyn
|
54
|
-
end
|
55
|
-
@loaded_dynamics.uniq!
|
56
|
-
true
|
57
|
-
end
|
58
|
-
|
59
|
-
def dynamic(name, &block)
|
60
|
-
@dynamics ||= Mash.new
|
61
|
-
@dynamics[name] = block
|
62
|
-
end
|
63
|
-
|
64
|
-
def insert(dynamic_name, struct, *args)
|
65
|
-
if(@dynamics && @dynamics[dynamic_name])
|
66
|
-
struct.instance_exec(*args, &@dynamics[dynamic_name])
|
67
|
-
struct
|
68
|
-
else
|
69
|
-
raise "Failed to locate requested dynamic block for insertion: #{dynamic_name} (valid: #{@dynamics.keys.sort.join(', ')})"
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def from_hash(hash)
|
74
|
-
struct = AttributeStruct.new
|
75
|
-
struct._camel_keys_set(:auto_discovery)
|
76
|
-
struct._load(hash)
|
77
|
-
struct._camel_keys_set(nil)
|
78
|
-
struct
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
attr_reader :name
|
83
|
-
attr_reader :sparkle_path
|
84
|
-
attr_reader :components
|
85
|
-
attr_reader :load_order
|
86
|
-
|
87
|
-
def initialize(name, options={})
|
88
|
-
@name = name
|
89
|
-
@sparkle_path = options[:sparkle_path] ||
|
90
|
-
self.class.custom_paths[:sparkle_path] ||
|
91
|
-
File.join(Dir.pwd, 'cloudformation/components')
|
92
|
-
@dynamics_directory = options[:dynamics_directory] ||
|
93
|
-
self.class.custom_paths[:dynamics_directory] ||
|
94
|
-
File.join(File.dirname(@sparkle_path), 'dynamics')
|
95
|
-
self.class.load_dynamics!(@dynamics_directory)
|
96
|
-
@components = Mash.new
|
97
|
-
@load_order = []
|
98
|
-
end
|
99
|
-
|
100
|
-
def load(*args)
|
101
|
-
args.each do |thing|
|
102
|
-
if(thing.is_a?(Symbol))
|
103
|
-
path = File.join(sparkle_path, "#{thing}.rb")
|
104
|
-
else
|
105
|
-
path = thing
|
106
|
-
end
|
107
|
-
key = File.basename(path).sub('.rb', '')
|
108
|
-
components[key] = self.class.load_component(path)
|
109
|
-
@load_order << key
|
110
|
-
end
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
|
-
def overrides(&block)
|
115
|
-
@overrides = self.class.build(&block)
|
116
|
-
self
|
117
|
-
end
|
118
|
-
|
119
|
-
# Returns compiled Mash instance
|
120
|
-
def compile
|
121
|
-
compiled = AttributeStruct.new
|
122
|
-
@load_order.each do |key|
|
123
|
-
compiled._merge!(components[key])
|
124
|
-
end
|
125
|
-
if(@overrides)
|
126
|
-
compiled._merge!(@overrides)
|
127
|
-
end
|
128
|
-
compiled
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
end
|