vagabond 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG.md +18 -0
  2. data/README.md +125 -4
  3. data/bin/vagabond +43 -16
  4. data/lib/vagabond/actions/cluster.rb +66 -0
  5. data/lib/vagabond/actions/create.rb +12 -7
  6. data/lib/vagabond/actions/destroy.rb +69 -15
  7. data/lib/vagabond/actions/init.rb +75 -0
  8. data/lib/vagabond/actions/provision.rb +4 -2
  9. data/lib/vagabond/actions/status.rb +33 -22
  10. data/lib/vagabond/actions/up.rb +8 -1
  11. data/lib/vagabond/bootstraps/server-zero.erb +20 -0
  12. data/lib/vagabond/bootstraps/server.erb +3 -2
  13. data/lib/vagabond/constants.rb +0 -15
  14. data/lib/vagabond/cookbooks/lxc/CHANGELOG.md +16 -0
  15. data/lib/vagabond/cookbooks/lxc/Gemfile +3 -2
  16. data/lib/vagabond/cookbooks/lxc/Gemfile.lock +30 -121
  17. data/lib/vagabond/cookbooks/lxc/README.md +43 -14
  18. data/lib/vagabond/cookbooks/lxc/attributes/default.rb +3 -3
  19. data/lib/vagabond/cookbooks/lxc/files/default/lxc-awesome-ephemeral +499 -0
  20. data/lib/vagabond/cookbooks/lxc/libraries/lxc.rb +223 -58
  21. data/lib/vagabond/cookbooks/lxc/libraries/lxc_file_config.rb +3 -0
  22. data/lib/vagabond/cookbooks/lxc/libraries/monkey.rb +51 -0
  23. data/lib/vagabond/cookbooks/lxc/metadata.rb +6 -5
  24. data/lib/vagabond/cookbooks/lxc/providers/config.rb +9 -16
  25. data/lib/vagabond/cookbooks/lxc/providers/container.rb +241 -229
  26. data/lib/vagabond/cookbooks/lxc/providers/default.rb +57 -0
  27. data/lib/vagabond/cookbooks/lxc/providers/ephemeral.rb +40 -0
  28. data/lib/vagabond/cookbooks/lxc/providers/fstab.rb +13 -54
  29. data/lib/vagabond/cookbooks/lxc/providers/interface.rb +13 -67
  30. data/lib/vagabond/cookbooks/lxc/providers/service.rb +14 -14
  31. data/lib/vagabond/cookbooks/lxc/recipes/default.rb +17 -4
  32. data/lib/vagabond/cookbooks/lxc/recipes/install_dependencies.rb +1 -1
  33. data/lib/vagabond/cookbooks/lxc/resources/config.rb +2 -2
  34. data/lib/vagabond/cookbooks/lxc/resources/container.rb +31 -6
  35. data/lib/vagabond/cookbooks/lxc/resources/default.rb +12 -0
  36. data/lib/vagabond/cookbooks/lxc/resources/ephemeral.rb +13 -0
  37. data/lib/vagabond/cookbooks/lxc/resources/fstab.rb +2 -1
  38. data/lib/vagabond/cookbooks/lxc/resources/interface.rb +6 -3
  39. data/lib/vagabond/cookbooks/lxc/resources/service.rb +1 -1
  40. data/lib/vagabond/cookbooks/lxc/templates/default/file_content.erb +2 -0
  41. data/lib/vagabond/cookbooks/lxc/templates/default/interface.erb +9 -3
  42. data/lib/vagabond/cookbooks/vagabond/README.md +10 -0
  43. data/lib/vagabond/cookbooks/vagabond/attributes/default.rb +1 -0
  44. data/lib/vagabond/cookbooks/vagabond/files/default/lxc-centos +13 -6
  45. data/lib/vagabond/cookbooks/vagabond/metadata.rb +1 -0
  46. data/lib/vagabond/cookbooks/vagabond/recipes/default.rb +46 -4
  47. data/lib/vagabond/cookbooks/vagabond/recipes/zero.rb +9 -0
  48. data/lib/vagabond/errors.rb +23 -0
  49. data/lib/vagabond/helpers.rb +41 -14
  50. data/lib/vagabond/internal_configuration.rb +120 -27
  51. data/lib/vagabond/kitchen.rb +143 -63
  52. data/lib/vagabond/knife.rb +8 -5
  53. data/lib/vagabond/layout.rb +16 -0
  54. data/lib/vagabond/monkey/kitchen_config.rb +23 -0
  55. data/lib/vagabond/server.rb +79 -63
  56. data/lib/vagabond/spec.rb +345 -0
  57. data/lib/vagabond/uploader.rb +30 -0
  58. data/lib/vagabond/uploader/berkshelf.rb +53 -0
  59. data/lib/vagabond/uploader/knife.rb +24 -0
  60. data/lib/vagabond/uploader/librarian.rb +31 -0
  61. data/lib/vagabond/vagabond.rb +30 -11
  62. data/lib/vagabond/vagabondfile.rb +40 -5
  63. data/lib/vagabond/version.rb +1 -1
  64. data/vagabond.gemspec +5 -2
  65. metadata +75 -15
  66. data/lib/vagabond/cookbooks/lxc/resources/#container.rb# +0 -28
  67. data/lib/vagabond/cookbooks/lxc/test/kitchen/Kitchenfile +0 -7
  68. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/metadata.rb +0 -2
  69. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/centos_lxc.rb +0 -0
  70. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/chef-bootstrap.rb +0 -0
  71. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_files.rb +0 -0
  72. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_templates.rb +0 -0
  73. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/ubuntu_lxc.rb +0 -0
@@ -0,0 +1,345 @@
1
+ require 'thor'
2
+ require File.join(File.dirname(__FILE__), 'cookbooks/lxc/libraries/lxc.rb')
3
+
4
+ %w(layout vagabond server helpers vagabondfile internal_configuration actions/status).each do |dep|
5
+ require "vagabond/#{dep}"
6
+ end
7
+
8
+ module Vagabond
9
+ class Spec < Thor
10
+
11
+ include Thor::Actions
12
+ include Helpers
13
+ include Actions::Status
14
+
15
+ attr_accessor :layout
16
+
17
+ self.class_exec(&Vagabond::CLI_OPTIONS)
18
+
19
+ def self.basename
20
+ 'vagabond spec'
21
+ end
22
+
23
+ def initialize(*args)
24
+ @name = nil
25
+ super
26
+ end
27
+
28
+ method_option(:irl,
29
+ :type => :boolean,
30
+ :default => false,
31
+ :desc => 'Test In Real Life'
32
+ )
33
+ method_option(:environment,
34
+ :type => :string,
35
+ :desc => 'Specify environment to restrict node detection'
36
+ )
37
+ method_option(:auto_destroy,
38
+ :type => :boolean,
39
+ :desc => 'Automatically destroy created nodes after spec tests (not valid with --irl)',
40
+ :default => true
41
+ )
42
+ desc 'start [CLUSTER]', 'Run specs for cluster'
43
+ def start(cluster=nil)
44
+ @options = options.dup
45
+ setup_ui(nil, :no_class_set)
46
+ error = nil
47
+ begin
48
+ if(options[:irl])
49
+ irl_spec(cluster)
50
+ else
51
+ cluster_spec(cluster)
52
+ end
53
+ rescue => error
54
+ ensure
55
+ cluster_destroy(cluster) if options[:auto_destroy] && !options[:irl]
56
+ result = error ? ui.color('FAILED', :red, :bold) : ui.color('PASSED', :green, :bold)
57
+ ui.info "--> Specs for cluster #{cluster}: #{result}"
58
+ raise VagabondError::SpecFailed.new(error) if error
59
+ end
60
+ end
61
+
62
+ method_option(:node,
63
+ :type => :string,
64
+ :desc => 'Destroy named node within cluster cluster'
65
+ )
66
+ desc 'destroy NAME', 'Destroy the given cluster/node'
67
+ def destroy(cluster)
68
+ base_setup
69
+ options[:node] ? node_destroy(cluster, options[:node]) : cluster_destroy(cluster)
70
+ end
71
+
72
+ desc 'status [NAME]', 'Show status of existing nodes'
73
+ def status(name=nil)
74
+ base_setup
75
+ _status
76
+ end
77
+
78
+ desc 'init', 'Initalize spec configuration'
79
+ def init
80
+ setup_ui(nil, :no_class_set)
81
+ ui.info "Initializing spec configuration..."
82
+ make_spec_directory
83
+ populate_spec_directory
84
+ # - dump empty layout
85
+ ui.info " -> #{ui.color('COMPLETE!', :green)}"
86
+ end
87
+
88
+ protected
89
+
90
+ def cluster_destroy(cluster)
91
+ ui.info "#{ui.color('Destroying cluster:', :bold)} #{ui.color(cluster, :red)}"
92
+ Array(internal_config[:spec_clusters][cluster]).each do |n|
93
+ node_destroy(cluster, n)
94
+ end
95
+ ui.info ui.color(" --> Cluster #{cluster} DESTROYED", :red)
96
+ end
97
+
98
+ def node_destroy(cluster, node_name)
99
+ v_n = vagabond_instance(:destroy, :cluster => cluster, :name => node_name)
100
+ v_n.send(:execute)
101
+ remove_node_from_cluster(cluster, node_name)
102
+ end
103
+
104
+ def make_spec_directory
105
+ %w(role recipe).each do |leaf|
106
+ FileUtils.mkdir_p(File.join(spec_directory, leaf))
107
+ end
108
+ end
109
+
110
+ def spec_directory
111
+ File.join(vagabondfile.directory, 'spec')
112
+ end
113
+
114
+ def populate_spec_directory
115
+ write_default_file('Layout')
116
+ write_default_file('spec_helper.rb')
117
+ end
118
+
119
+ def write_default_file(file)
120
+ write = true
121
+ if(File.exists?(path = File.join(spec_directory, file)))
122
+ answer = ''
123
+ until(%w(y n).include?(answer))
124
+ answer = ui.ask_question("Overwrite existing #{file} ", :default => 'y').downcase
125
+ end
126
+ write = answer == 'y'
127
+ end
128
+ if(write)
129
+ File.open(path, 'w') do |file|
130
+ file.write self.class.const_get("CONTENT_DEFAULT_#{File.basename(path).upcase.sub(%r{\..*$}, '')}")
131
+ end
132
+ ui.info "New file has been written: #{file}"
133
+ else
134
+ ui.warn "Skipping file: #{file}"
135
+ end
136
+ end
137
+
138
+ def mappings_key
139
+ :spec_mappings
140
+ end
141
+
142
+ def irl_spec(cluster)
143
+ if(cluster && load_layout[cluster])
144
+ valid_runlists = layout[:clusters][cluster][:nodes]
145
+ # Runlists composed of role AND/OR recipe
146
+ valid_runlists.each do |r_l|
147
+ runlist = r_l.map{|r| Chef::RunList::RunListItem.new(r)}
148
+ roles = runlist.find_all do |i|
149
+ i.role?
150
+ end
151
+ recipes = runlist.find_all do |i|
152
+ i.recipe?
153
+ end
154
+ terms = roles.map{|r| "role:#{r.name}"} + recipes.map{|r| "recipes:#{r.name}"}
155
+ query = terms.join(' AND ')
156
+ if(options[:environment])
157
+ query = "chef_environment:#{options[:environment]} AND (#{query})"
158
+ end
159
+ search(:node, query).each do |node|
160
+ n_r = node.run_list.map(&:to_s)
161
+ next unless n_r.size == r_l.size && (n_r - r_l).empty?
162
+ test_node!(node.name, node.ipaddress, node.run_list)
163
+ end
164
+ end
165
+ else
166
+ query = %w(*:*)
167
+ if(options[:environment])
168
+ query << "chef_environment:#{options[:environment]}"
169
+ end
170
+ Chef::Search::Query.new(:node, query.join(' AND ')) do |nodes|
171
+ nodes.each do |node|
172
+ test_node!(node.name, node.ipaddress, node.run_list)
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ def vagabondfile
179
+ unless(@vagabondfile)
180
+ @vagabondfile = Vagabondfile.new(options[:vagabond_file])
181
+ end
182
+ @vagabondfile
183
+ end
184
+
185
+ def cluster_spec(cluster)
186
+ @options[:auto_provision] = true
187
+ options[:sudo] = sudo
188
+ Lxc.use_sudo = vagabondfile[:sudo].nil? ? true : vagabondfile[:sudo]
189
+ @internal_config = InternalConfiguration.new(vagabondfile, nil, options)
190
+ # First, setup server
191
+ if(vagabondfile[:local_chef_server][:enabled])
192
+ require 'vagabond/server'
193
+ srv = ::Vagabond::Server.new
194
+ srv.send(:setup, 'up')
195
+ srv.send(:execute)
196
+ end
197
+
198
+ load_layout
199
+ default_config = Chef::Mixin::DeepMerge.merge(
200
+ Mash.new(:platform => 'ubuntu_1204'), layout[:defaults]
201
+ )
202
+ test_nodes = []
203
+ layout[:clusters][cluster][:nodes].each_with_index do |node, index|
204
+ config = Chef::Mixin::DeepMerge.merge(default_config, layout[:definitions][node])
205
+ config = Chef::Mixin::DeepMerge.merge(config, layout[:clusters][cluster][:overrides] || {})
206
+ v_n = vagabond_instance(:up,
207
+ :platform => config[:platform],
208
+ :cluster => cluster,
209
+ :base_name => "s-#{node}-#{index}"
210
+ )
211
+ v_n.config = Chef::Mixin::DeepMerge.merge(v_n.config, config)
212
+ v_n.send(:execute)
213
+ test_nodes << [v_n.name, v_n.lxc.name, config]
214
+ end
215
+ test_nodes.each do |node|
216
+ name, lxc_name, config = node
217
+ lxc = Lxc.new(lxc_name)
218
+ test_node!(name, lxc.container_ip, config[:run_list])
219
+ end
220
+ end
221
+
222
+ def test_node!(name, ip_address, run_list)
223
+ run_list.each do |item|
224
+ r_item = item.is_a?(Chef::RunList::RunListItem) ? item : Chef::RunList::RunListItem.new(item)
225
+ dir = File.join(File.dirname(vagabondfile.path), "spec/#{r_item.type}/#{r_item.name.sub('::', '/')}")
226
+ dir << '/default' if r_item.type.to_sym == :recipe && !r_item.name.include?('::')
227
+ Dir.glob(File.join(dir, '*.rb')).each do |path|
228
+ com = "#{sudo}VAGABOND_TEST_HOST='#{ip_address}' rspec #{path}"
229
+ debug(com)
230
+ cmd = Mixlib::ShellOut.new(com, :live_stream => STDOUT, :env => {'VAGABOND_TEST_HOST' => ip_address})
231
+ cmd.run_command
232
+ cmd.error!
233
+ end
234
+ end
235
+ end
236
+
237
+ def load_layout
238
+ # Load up layouts and set defaults
239
+ @layout = Layout.new(File.dirname(vagabondfile.path))
240
+ end
241
+
242
+ def vagabond_instance(action, args={})
243
+ @options[:disable_name_validate] = true
244
+ v = Vagabond.new
245
+ v.options = @options
246
+ v.send(:setup, action, args[:name] || generated_name(args[:base_name]),
247
+ :ui => ui,
248
+ :template => args[:platform],
249
+ :disable_name_validate => true,
250
+ :ui => ui
251
+ )
252
+ if(args[:platform])
253
+ v.internal_config.force_bases = args[:platform]
254
+ v.internal_config.ensure_state
255
+ end
256
+ v.mappings_key = :spec_mappings
257
+ v.config = Mash.new(
258
+ :template => args[:platform],
259
+ :run_list => args[:run_list]
260
+ )
261
+ v.lxc = Lxc.new(
262
+ v.internal_config[v.mappings_key][v.name]
263
+ ) if v.internal_config[v.mappings_key][v.name]
264
+ add_node_to_cluster(v.name, args[:cluster])
265
+ v
266
+ end
267
+
268
+ def _status
269
+ status = []
270
+ if(name)
271
+ clusters = [name]
272
+ else
273
+ load_layout
274
+ clusters = layout[:clusters].keys.sort
275
+ end
276
+ clusters.each do |cluster|
277
+ ui.info "#{ui.color('Status of spec cluster:', :bold)} #{ui.color(cluster, :yellow)}"
278
+ status = [
279
+ ui.color('Name', :bold),
280
+ ui.color('State', :bold),
281
+ ui.color('PID', :bold),
282
+ ui.color('IP', :bold)
283
+ ]
284
+ Array(internal_config[:spec_clusters][cluster]).sort.each do |n|
285
+ status += status_for(n)
286
+ end
287
+ puts ui.list(status, :uneven_columns_across, 4)
288
+ end
289
+ end
290
+
291
+ def add_node_to_cluster(node_name, cluster)
292
+ internal_config[:spec_clusters][cluster] ||= []
293
+ internal_config[:spec_clusters][cluster] |= [node_name]
294
+ internal_config.save
295
+ end
296
+
297
+ def remove_node_from_cluster(cluster, node_name)
298
+ internal_config[:spec_clusters][cluster] ||= []
299
+ internal_config[:spec_clusters][cluster] -= [node_name]
300
+ internal_config[mappings_key].delete(node_name)
301
+ internal_config.save
302
+ end
303
+
304
+ CONTENT_DEFAULT_LAYOUT = <<-EOF
305
+ {
306
+ :defaults => {
307
+ :platform => 'ubuntu_1204',
308
+ :environment => nil
309
+ },
310
+ :definitions => {
311
+ :example_node => {
312
+ :run_list => %w(role[example])
313
+ }
314
+ },
315
+ :clusters => {
316
+ :example_cluster => {
317
+ :nodes => ['example_node']
318
+ }
319
+ }
320
+ }
321
+ EOF
322
+ CONTENT_DEFAULT_SPEC_HELPER = <<-EOF
323
+ require 'serverspec'
324
+ require 'pathname'
325
+ require 'net/ssh'
326
+
327
+ include Serverspec::Helper::Ssh
328
+
329
+ RSpec.configure do |c|
330
+ c.before do
331
+ host = ENV['VAGABOND_TEST_HOST']
332
+ if(c.host != host)
333
+ c.ssh.close if c.ssh
334
+ c.host = host
335
+ options = Net::SSH::Config.for(c.host)
336
+ c.ssh = Net::SSH.start(c.host, 'root', options.update(:keys => ['/opt/hw-lxc-config/id_rsa']))
337
+ end
338
+ end
339
+ end
340
+ EOF
341
+
342
+ end
343
+ end
344
+
345
+
@@ -0,0 +1,30 @@
1
+ Dir.new(File.join(File.dirname(__FILE__), 'uploader')).each do |file|
2
+ next if file.start_with?('.') || !file.end_with?('.rb')
3
+ require "vagabond/uploader/#{file}"
4
+ end
5
+
6
+ require 'vagabond/helpers'
7
+
8
+ module Vagabond
9
+ class Uploader
10
+
11
+ attr_reader :store
12
+ attr_reader :options
13
+ attr_reader :ui
14
+
15
+ include Helpers
16
+
17
+ def initialize(base_directory, options={})
18
+ @store = base_directory
19
+ @options = Mash.new(options)
20
+ @ui = options[:ui]
21
+ end
22
+
23
+ def prepare
24
+ end
25
+
26
+ def upload
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ require 'vagabond/uploader/berkshelf'
2
+
3
+ module Vagabond
4
+ class Uploader
5
+ class Berkshelf < Uploader
6
+
7
+ def initialize(*args)
8
+ super
9
+ %w(berksfile chef_server_url).each do |k|
10
+ unless(options[k])
11
+ raise ArgumentError.new "Option '#{k}' must be provided!"
12
+ end
13
+ end
14
+ end
15
+
16
+ def prepare
17
+ path = File.join(store, 'berks.json')
18
+ if(File.exists?(path))
19
+ cur = Mash.new(JSON.load(File.read(path)))
20
+ else
21
+ cur = Mash.new
22
+ end
23
+ url = options[:chef_server_url]
24
+ if(cur[:chef].nil? || cur[:chef][:chef_server_url] != url)
25
+ cur[:chef] = Mash.new(:chef_server_url => url)
26
+ cur[:ssl] = Mash.new(:verify => false)
27
+ File.open(path, 'w') do |file|
28
+ file.write(JSON.dump(cur))
29
+ end
30
+ end
31
+ end
32
+
33
+ def upload(*args)
34
+ prepare unless args.include?(:no_prepare)
35
+ com = "berks upload -b #{options[:berksfile]} -c #{File.join(store, 'berks.json')}#{" #{Array(options[:berks_opts]).join(' ')}"}"
36
+ debug(com)
37
+ cmd = Mixlib::ShellOut.new(com, :live_stream => options[:debug], :env => {'HOME' => ENV['HOME']})
38
+ cmd.run_command
39
+ cmd.error!
40
+ end
41
+
42
+ def vendor(*args)
43
+ prepare unless args.include?(:no_prepare)
44
+ FileUtils.mkdir_p(ckbk_store = File.join(store, 'cookbooks'))
45
+ com = "berks install -b #{options[:berksfile]} -p #{ckbk_store}"
46
+ debug(com)
47
+ cmd = Mixlib::ShellOut.new(com, :live_stream => options[:debug])
48
+ cmd.run_command
49
+ cmd.error!
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ require 'vagabond/uploader'
2
+
3
+ module Vagabond
4
+ class Uploader
5
+ class Knife < Uploader
6
+
7
+ def upload(*args)
8
+ prepare unless args.include?(:no_prepare)
9
+ com = "knife cookbook upload#{options[:knife_opts]} --all"
10
+ if(options[:cookbook_paths])
11
+ com << " --cookbook-path #{Array(options[:cookbook_paths]).join(':')}"
12
+ end
13
+ debug(com)
14
+ cmd = Mixlib::ShellOut.new(com,
15
+ :live_stream => options[:debug],
16
+ :cwd => store
17
+ )
18
+ cmd.run_command
19
+ cmd.error!
20
+ end
21
+
22
+ end
23
+ end
24
+ end