vagabond 0.2.0 → 0.2.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.
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