knife-stackbuilder 0.5.2

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,379 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ module StackBuilder::Common
4
+
5
+ module Helpers
6
+
7
+ #
8
+ # Returns whether platform is a nix OS
9
+ #
10
+ def is_nix_os
11
+ return RbConfig::CONFIG["host_os"] =~ /linux|freebsd|darwin|unix/
12
+ end
13
+
14
+ #
15
+ # Runs the given execution list asynchronously if fork is supported
16
+ #
17
+ def exec_forked(exec_list)
18
+
19
+ if is_nix_os
20
+ p = []
21
+ exec_list.each do |data|
22
+ p << fork {
23
+ yield(data)
24
+ }
25
+ end
26
+ p.each { |pid| Process.waitpid(pid) }
27
+ else
28
+ exec_list.each do |data|
29
+ yield(data)
30
+ printf("\n")
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ #
37
+ # Loads a Yaml file and resolves any includes
38
+ #
39
+ def load_yaml(file, env_vars, my = nil)
40
+
41
+ yaml = YAML.load_file(file)
42
+ eval_map_values(yaml, env_vars, file, my)
43
+ end
44
+
45
+ #
46
+ # Evaluates map values against the
47
+ # given map of environment variables
48
+ #
49
+ def eval_map_values(v, env, file = nil, my = nil)
50
+
51
+ my ||= v
52
+
53
+ if v.is_a?(String)
54
+
55
+ if v=~/#\{.*\}/
56
+ begin
57
+ return eval("\"#{v.gsub(/\"/, "\\\"")}\"")
58
+ rescue
59
+ return v
60
+ end
61
+
62
+ elsif v.start_with?('<<')
63
+
64
+ k1 = v[/<<(\w*)[\*\$\+]/,1]
65
+ env_val = k1.nil? || k1.empty? ? nil : ENV[k1]
66
+
67
+ i = k1.length + 3
68
+ k2 = v[i,v.length-i]
69
+
70
+ case v[i-1]
71
+
72
+ when '*'
73
+ if env_val.nil?
74
+ v = ask(k2) { |q| q.echo = "*" }.to_s
75
+ ENV[k1] = v unless k1.nil? || k1.empty?
76
+ else
77
+ v = env_val
78
+ end
79
+ return v
80
+
81
+ when '$'
82
+ if env_val.nil?
83
+ v = ask(k2).to_s
84
+ ENV[k1] = v unless k1.nil? || k1.empty?
85
+ else
86
+ v = env_val
87
+ end
88
+ return v
89
+
90
+ when '+'
91
+ lookup_keys = (env_val || k2).split(/[\[\]]/).reject { |k| k.empty? }
92
+
93
+ key = lookup_keys.shift
94
+ include_file = key.start_with?('/') || key.nil? ? key : File.expand_path('../' + key, file)
95
+
96
+ begin
97
+ yaml = load_yaml(include_file, env, my)
98
+
99
+ return lookup_keys.empty? ? yaml
100
+ : eval('yaml' + lookup_keys.collect { |v| "['#{v}']" }.join)
101
+
102
+ rescue Exception => msg
103
+ puts "ERROR: Unable to include referenced data '#{v}'."
104
+ raise msg
105
+ end
106
+ else
107
+ return v
108
+ end
109
+ end
110
+
111
+ elsif v.is_a?(Hash)
112
+
113
+ new_keys = { }
114
+ v.each_pair do |k,vv|
115
+
116
+ if k=~/#\{.*\}/
117
+
118
+ new_k = eval("\"#{k}\"")
119
+ if k!=new_k
120
+ v.delete(k)
121
+ new_keys[new_k] = eval_map_values(vv, env, file, my)
122
+ next
123
+ end
124
+ end
125
+
126
+ v[k] = eval_map_values(vv, env, file, my)
127
+ end
128
+ v.merge!(new_keys) unless new_keys.empty?
129
+
130
+ elsif v.is_a?(Array)
131
+ v.map! { |vv| eval_map_values(vv, env, file, my) }
132
+ end
133
+
134
+ v
135
+ end
136
+
137
+ #
138
+ # Merges values of keys of to maps
139
+ #
140
+ def merge_maps(map1, map2)
141
+
142
+ map2.each_pair do |k, v2|
143
+
144
+ v1 = map1[k]
145
+ if v1.nil?
146
+ if v2.is_a?(Hash)
147
+ v1 = { }
148
+ merge_maps(v1, v2)
149
+ elsif v2.is_a?(Array)
150
+ v1 = [ ] + v2
151
+ else
152
+ v1 = v2
153
+ end
154
+ map1[k] = v1
155
+ elsif v1.is_a?(Hash) && v2.is_a?(Hash)
156
+ merge_maps(v1, v2)
157
+ elsif v1.is_a?(Array) && v2.is_a?(Array)
158
+
159
+ if v2.size > 0
160
+
161
+ if v1.size > 0 && v1[0].is_a?(Hash) && v2[0].is_a?(Hash)
162
+
163
+ i = 0
164
+ while i < [ v1.size, v2.size ].max do
165
+
166
+ v1[i] = { } if v1[i].nil?
167
+ v2[i] = { } if v2[i].nil?
168
+ merge_maps(v1[i], v2[i])
169
+
170
+ i += 1
171
+ end
172
+
173
+ else
174
+ v1 += v2
175
+ end
176
+ end
177
+ else
178
+ map1[k] = v2
179
+ end
180
+ end
181
+ end
182
+
183
+ #
184
+ # Prints a 2-d array within a table formatted to terminal size
185
+ #
186
+ def print_table(table, format = true, cols = nil)
187
+
188
+ # Apply column filter
189
+ unless cols.nil?
190
+
191
+ headings = cols.split(",").to_set
192
+
193
+ cols_to_delete = [ ]
194
+ i = 0
195
+ table[0].each do |col|
196
+ cols_to_delete << i unless headings.include?(col)
197
+ i = i + 1
198
+ end
199
+
200
+ table.each do |row|
201
+ cols_to_delete.reverse_each { |j| row.delete_at(j) }
202
+ end
203
+ end
204
+
205
+ if format
206
+
207
+ # Calculate widths
208
+ widths = []
209
+ table.each do |line|
210
+ c = 0
211
+ line.each do |col|
212
+
213
+ len = col.nil? ? 0 : col.length
214
+ widths[c] = (widths[c] && widths[c] > len) ? widths[c] : len
215
+ c += 1
216
+ end
217
+ end
218
+
219
+ max_scr_cols = HighLine::SystemExtensions.terminal_size[0].nil? ? 9999
220
+ : HighLine::SystemExtensions.terminal_size[0] - (widths.length * 3) - 2
221
+
222
+ max_tbl_cols = 0
223
+ width_map = {}
224
+
225
+ c = 0
226
+ widths.each do |n|
227
+ max_tbl_cols += n
228
+ width_map[c] = n
229
+ c += 1
230
+ end
231
+ c = nil
232
+
233
+ # Shrink columns that have too much space to try and fit table into visible console
234
+ if max_tbl_cols > max_scr_cols
235
+
236
+ width_map = width_map.sort_by { |col,width| -width }
237
+
238
+ last_col = widths.length - 1
239
+ c = 0
240
+
241
+ while max_tbl_cols > max_scr_cols && c < last_col
242
+
243
+ while width_map[c][1] > width_map[c + 1][1]
244
+
245
+ i = c
246
+ while i >= 0
247
+ width_map[i][1] -= 1
248
+ widths[width_map[i][0]] -= 1
249
+ max_tbl_cols -= 1
250
+ i -= 1
251
+
252
+ break if max_tbl_cols == max_scr_cols
253
+ end
254
+ break if max_tbl_cols == max_scr_cols
255
+ end
256
+ c += 1
257
+ end
258
+ end
259
+
260
+ border1 = ""
261
+ border2 = ""
262
+ format = ""
263
+ widths.each do |n|
264
+
265
+ border1 += "+#{'-' * (n + 2)}"
266
+ border2 += "+#{'=' * (n + 2)}"
267
+ format += "| %#{n}s "
268
+ end
269
+ border1 += "+\n"
270
+ border2 += "+\n"
271
+ format += "|\n"
272
+
273
+ else
274
+ c = nil
275
+ border1 = nil
276
+ border2 = nil
277
+
278
+ format = Array.new(table[0].size, "%s,").join.chop! + "\n"
279
+
280
+ # Delete column headings for unformatted output
281
+ table.delete_at(0)
282
+ end
283
+
284
+ # Print each line.
285
+ write_header_border = !border2.nil?
286
+ printf border1 if border1
287
+ table.each do |line|
288
+
289
+ if c
290
+ # Check if cell needs to be truncated
291
+ i = 0
292
+ while i < c
293
+
294
+ j = width_map[i][0]
295
+ width = width_map[i][1]
296
+
297
+ cell = line[j]
298
+ len = cell.length
299
+ if len > width
300
+ line[j] = cell[0, width - 2] + ".."
301
+ end
302
+ i += 1
303
+ end
304
+ end
305
+
306
+ printf format, *line
307
+ if write_header_border
308
+ printf border2
309
+ write_header_border = false
310
+ end
311
+ end
312
+ printf border1 if border1
313
+
314
+ end
315
+
316
+ #
317
+ # Helper command to rin Chef knife
318
+ #
319
+ def run_knife(knife_cmd, retries = 0, output = StringIO.new, error = StringIO.new)
320
+
321
+ knife_cmd.ui = Chef::Knife::UI.new(output, error, STDIN, knife_cmd.config) \
322
+ unless output.nil? && error.nil?
323
+
324
+ run = true
325
+ while run
326
+
327
+ begin
328
+ knife_cmd.run
329
+ run = false
330
+
331
+ rescue Exception => msg
332
+
333
+ if retries==0
334
+
335
+ if @logger.level>=::Logger::WARN
336
+ puts "Knife execution failed with an error."
337
+ puts "* StdOut from knife run: #{output.string}"
338
+ puts "* StdErr from knife run: #{error.string}"
339
+ end
340
+
341
+ @logger.debug(msg.backtrace.join("\n\t")) if Config.logger.debug?
342
+ raise msg
343
+ end
344
+
345
+ @logger.debug("Knife command #{knife_cmd} failed. Retrying after 2s.")
346
+
347
+ sleep 2
348
+ retries -= 1
349
+ end
350
+ end
351
+
352
+ output.string
353
+ end
354
+
355
+ #
356
+ # Captures standard out and error to a string
357
+ #
358
+ def capture_stdout
359
+ # The output stream must be an IO-like object. In this case we capture it in
360
+ # an in-memory IO object so we can return the string value. You can assign any
361
+ # IO object here.
362
+ stdout = StringIO.new
363
+ previous_stdout, $stdout = $stdout, stdout
364
+ previous_stderr, $stderr = $stderr, stdout
365
+ yield
366
+ stdout.string
367
+
368
+ rescue Exception => msg
369
+ puts("Error: #{stdout.string}")
370
+ raise msg
371
+
372
+ ensure
373
+ # Restore the previous value of stderr (typically equal to STDERR).
374
+ $stdout = previous_stdout
375
+ $stderr = previous_stderr
376
+ end
377
+
378
+ end
379
+ end
@@ -0,0 +1,54 @@
1
+ # From https://gist.github.com/pettyjamesm/3746457
2
+
3
+ require 'monitor'
4
+
5
+ module StackBuilder::Common
6
+
7
+ class Semaphore
8
+
9
+ def initialize(maxval = nil)
10
+
11
+ maxval = maxval.to_i unless maxval.nil?
12
+ raise ArgumentError, "Semaphores must use a positive maximum " +
13
+ "value or have no maximum!" if maxval and maxval <= 0
14
+
15
+ @max = maxval || -1
16
+ @count = 0
17
+ @mon = Monitor.new
18
+ @dwait = @mon.new_cond
19
+ @uwait = @mon.new_cond
20
+ end
21
+
22
+ def count; @mon.synchronize { @count } end
23
+
24
+ def up!(number = 1)
25
+ if (number > 1)
26
+ number.times { up!(1) }
27
+ count
28
+ else
29
+ @mon.synchronize do
30
+ @uwait.wait while @max > 0 and @count == @max
31
+ @dwait.signal if @count == 0
32
+ @count += 1
33
+ end
34
+ end
35
+ end
36
+
37
+ def down!(number = 1)
38
+ if (number > 1)
39
+ number.times { down!(1) }
40
+ count
41
+ else
42
+ @mon.synchronize do
43
+ @dwait.wait while @count == 0
44
+ @uwait.signal if @count == @max
45
+ @count -= 1
46
+ end
47
+ end
48
+ end
49
+
50
+ alias_method :wait, :down!
51
+ alias_method :signal, :up!
52
+ end
53
+
54
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ module StackBuilder::Common
4
+
5
+ #
6
+ # Sends data written to an IO object to multiple outputs.
7
+ #
8
+ class TeeIO < IO
9
+
10
+ def initialize(output = nil)
11
+ @string_io = StringIO.new
12
+ @output = output
13
+ end
14
+
15
+ def tty?
16
+ return false
17
+ end
18
+
19
+ def write(string)
20
+ @string_io.write(string)
21
+ @output.write(string) unless @output.nil?
22
+ end
23
+
24
+ def string
25
+ @string_io.string
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ include StackBuilder::Common::Helpers
4
+
5
+ module StackBuilder::Stack
6
+
7
+ class NodeManager
8
+
9
+ def get_name
10
+ raise NotImplemented, 'NodeManager.get_name'
11
+ end
12
+
13
+ def get_scale
14
+ @scale.nil? ? 0 : @scale
15
+ end
16
+
17
+ def set_scale(scale)
18
+ @scale = scale
19
+ end
20
+
21
+ def node_attributes
22
+ raise NotImplemented, 'NodeManager.node_attributes'
23
+ end
24
+
25
+ def create(index)
26
+ raise NotImplemented, 'NodeManager.create'
27
+ end
28
+
29
+ def process(index, events, attributes, target = nil)
30
+ raise NotImplemented, 'NodeManager.process'
31
+ end
32
+
33
+ def delete(index)
34
+ raise NotImplemented, 'NodeManager.delete'
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ include StackBuilder::Common::Helpers
4
+
5
+ module StackBuilder::Stack
6
+
7
+ class NodeProvider
8
+
9
+ def set_stack(stack, id)
10
+ raise NotImplemented, 'NodeProvider.set_stack_id'
11
+ end
12
+
13
+ def get_env_vars
14
+ return { }
15
+ end
16
+
17
+ def get_node_manager(node_config)
18
+ raise NotImplemented, 'NodeProvider.get_node_manager'
19
+ end
20
+ end
21
+ end