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.
- checksums.yaml +7 -0
- data/README.md +115 -0
- data/Rakefile +42 -0
- data/bin/stackbuilder_debug +0 -0
- data/lib/chef/knife/stack_build.rb +114 -0
- data/lib/chef/knife/stack_delete.rb +50 -0
- data/lib/chef/knife/stack_initialize_repo.rb +62 -0
- data/lib/chef/knife/stack_upload_certificates.rb +38 -0
- data/lib/chef/knife/stack_upload_cookbooks.rb +49 -0
- data/lib/chef/knife/stack_upload_data_bags.rb +36 -0
- data/lib/chef/knife/stack_upload_environments.rb +31 -0
- data/lib/chef/knife/stack_upload_repo.rb +39 -0
- data/lib/chef/knife/stack_upload_roles.rb +33 -0
- data/lib/chef/knife/stackbuilder_base.rb +32 -0
- data/lib/stackbuilder/chef/repo.rb +442 -0
- data/lib/stackbuilder/chef/stack_generic_node.rb +67 -0
- data/lib/stackbuilder/chef/stack_node_manager.rb +251 -0
- data/lib/stackbuilder/chef/stack_provider.rb +77 -0
- data/lib/stackbuilder/chef/stack_vagrant_node.rb +72 -0
- data/lib/stackbuilder/common/config.rb +44 -0
- data/lib/stackbuilder/common/errors.rb +18 -0
- data/lib/stackbuilder/common/helpers.rb +379 -0
- data/lib/stackbuilder/common/semaphore.rb +54 -0
- data/lib/stackbuilder/common/teeio.rb +29 -0
- data/lib/stackbuilder/stack/node_manager.rb +38 -0
- data/lib/stackbuilder/stack/node_provider.rb +21 -0
- data/lib/stackbuilder/stack/node_task.rb +424 -0
- data/lib/stackbuilder/stack/stack.rb +224 -0
- data/lib/stackbuilder/version.rb +8 -0
- data/lib/stackbuilder.rb +53 -0
- metadata +100 -0
@@ -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
|