vagabond 0.1.0

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 (63) hide show
  1. data/CHANGELOG.md +2 -0
  2. data/README.md +94 -0
  3. data/bin/vagabond +6 -0
  4. data/lib/vagabond.rb +1 -0
  5. data/lib/vagabond/actions/create.rb +29 -0
  6. data/lib/vagabond/actions/destroy.rb +31 -0
  7. data/lib/vagabond/actions/freeze.rb +14 -0
  8. data/lib/vagabond/actions/provision.rb +31 -0
  9. data/lib/vagabond/actions/ssh.rb +13 -0
  10. data/lib/vagabond/actions/status.rb +29 -0
  11. data/lib/vagabond/actions/thaw.rb +14 -0
  12. data/lib/vagabond/actions/up.rb +27 -0
  13. data/lib/vagabond/bootstraps/server.erb +62 -0
  14. data/lib/vagabond/commands.rb +58 -0
  15. data/lib/vagabond/config.rb +7 -0
  16. data/lib/vagabond/cookbooks/lxc/CHANGELOG.md +21 -0
  17. data/lib/vagabond/cookbooks/lxc/Gemfile +3 -0
  18. data/lib/vagabond/cookbooks/lxc/Gemfile.lock +132 -0
  19. data/lib/vagabond/cookbooks/lxc/README.md +83 -0
  20. data/lib/vagabond/cookbooks/lxc/attributes/default.rb +26 -0
  21. data/lib/vagabond/cookbooks/lxc/files/default/knife_lxc +228 -0
  22. data/lib/vagabond/cookbooks/lxc/libraries/lxc.rb +279 -0
  23. data/lib/vagabond/cookbooks/lxc/libraries/lxc_expanded_resources.rb +40 -0
  24. data/lib/vagabond/cookbooks/lxc/libraries/lxc_file_config.rb +81 -0
  25. data/lib/vagabond/cookbooks/lxc/metadata.rb +11 -0
  26. data/lib/vagabond/cookbooks/lxc/providers/config.rb +82 -0
  27. data/lib/vagabond/cookbooks/lxc/providers/container.rb +342 -0
  28. data/lib/vagabond/cookbooks/lxc/providers/fstab.rb +71 -0
  29. data/lib/vagabond/cookbooks/lxc/providers/interface.rb +99 -0
  30. data/lib/vagabond/cookbooks/lxc/providers/service.rb +53 -0
  31. data/lib/vagabond/cookbooks/lxc/recipes/containers.rb +13 -0
  32. data/lib/vagabond/cookbooks/lxc/recipes/default.rb +45 -0
  33. data/lib/vagabond/cookbooks/lxc/recipes/install_dependencies.rb +15 -0
  34. data/lib/vagabond/cookbooks/lxc/recipes/knife.rb +37 -0
  35. data/lib/vagabond/cookbooks/lxc/resources/#container.rb# +28 -0
  36. data/lib/vagabond/cookbooks/lxc/resources/config.rb +19 -0
  37. data/lib/vagabond/cookbooks/lxc/resources/container.rb +28 -0
  38. data/lib/vagabond/cookbooks/lxc/resources/fstab.rb +11 -0
  39. data/lib/vagabond/cookbooks/lxc/resources/interface.rb +10 -0
  40. data/lib/vagabond/cookbooks/lxc/resources/service.rb +5 -0
  41. data/lib/vagabond/cookbooks/lxc/templates/default/client.rb.erb +13 -0
  42. data/lib/vagabond/cookbooks/lxc/templates/default/default-lxc.erb +3 -0
  43. data/lib/vagabond/cookbooks/lxc/templates/default/fstab.erb +5 -0
  44. data/lib/vagabond/cookbooks/lxc/templates/default/interface.erb +21 -0
  45. data/lib/vagabond/cookbooks/lxc/test/kitchen/Kitchenfile +7 -0
  46. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/metadata.rb +2 -0
  47. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/centos_lxc.rb +0 -0
  48. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/chef-bootstrap.rb +0 -0
  49. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_files.rb +0 -0
  50. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_templates.rb +0 -0
  51. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/ubuntu_lxc.rb +0 -0
  52. data/lib/vagabond/cookbooks/vagabond/attributes/default.rb +2 -0
  53. data/lib/vagabond/cookbooks/vagabond/libraries/vagabond.rb +10 -0
  54. data/lib/vagabond/cookbooks/vagabond/metadata.rb +6 -0
  55. data/lib/vagabond/cookbooks/vagabond/recipes/create.rb +3 -0
  56. data/lib/vagabond/cookbooks/vagabond/recipes/default.rb +30 -0
  57. data/lib/vagabond/internal_configuration.rb +147 -0
  58. data/lib/vagabond/server.rb +158 -0
  59. data/lib/vagabond/vagabond.rb +109 -0
  60. data/lib/vagabond/vagabondfile.rb +34 -0
  61. data/lib/vagabond/version.rb +10 -0
  62. data/vagabond.gemspec +18 -0
  63. metadata +125 -0
@@ -0,0 +1,279 @@
1
+ class Lxc
2
+ class CommandFailed < StandardError
3
+ end
4
+
5
+ attr_reader :name
6
+
7
+ class << self
8
+
9
+ attr_accessor :use_sudo
10
+
11
+ def sudo
12
+ case use_sudo
13
+ when TrueClass
14
+ 'sudo '
15
+ when String
16
+ "#{use_sudo} "
17
+ end
18
+ end
19
+
20
+ # List running containers
21
+ def running
22
+ full_list[:running]
23
+ end
24
+
25
+ # List stopped containers
26
+ def stopped
27
+ full_list[:stopped]
28
+ end
29
+
30
+ # List frozen containers
31
+ def frozen
32
+ full_list[:frozen]
33
+ end
34
+
35
+ # name:: name of container
36
+ # Returns if container exists
37
+ def exists?(name)
38
+ list.include?(name)
39
+ end
40
+
41
+ # List of containers
42
+ def list
43
+ %x{#{sudo}lxc-ls}.split("\n").uniq
44
+ end
45
+
46
+ # name:: Name of container
47
+ # Returns information about given container
48
+ def info(name)
49
+ res = {:state => nil, :pid => nil}
50
+ info = %x{#{sudo}lxc-info -n #{name}}.split("\n")
51
+ parts = info.first.split(' ')
52
+ res[:state] = parts.last.downcase.to_sym
53
+ parts = info.last.split(' ')
54
+ res[:pid] = parts.last.to_i
55
+ res
56
+ end
57
+
58
+ # Return full container information list
59
+ def full_list
60
+ res = {}
61
+ list.each do |item|
62
+ item_info = info(item)
63
+ res[item_info[:state]] ||= []
64
+ res[item_info[:state]] << item
65
+ end
66
+ res
67
+ end
68
+
69
+ # ip:: IP address
70
+ # Returns if IP address is alive
71
+ def connection_alive?(ip)
72
+ %x{ping -c 1 -W 1 #{ip}}
73
+ $?.exitstatus == 0
74
+ end
75
+ end
76
+
77
+ # name:: name of container
78
+ # args:: Argument hash
79
+ # - :base_path -> path to container directory
80
+ # - :dnsmasq_lease_file -> path to lease file
81
+ def initialize(name, args={})
82
+ @name = name
83
+ @base_path = args[:base_path] || '/var/lib/lxc'
84
+ @lease_file = args[:dnsmasq_lease_file] || '/var/lib/misc/dnsmasq.leases'
85
+ end
86
+
87
+ # Returns if container exists
88
+ def exists?
89
+ self.class.exists?(name)
90
+ end
91
+
92
+ # Returns if container is running
93
+ def running?
94
+ self.class.info(name)[:state] == :running
95
+ end
96
+
97
+ # Returns if container is stopped
98
+ def stopped?
99
+ self.class.info(name)[:state] == :stopped
100
+ end
101
+
102
+ # Returns if container is frozen
103
+ def frozen?
104
+ self.class.info(name)[:state] == :frozen
105
+ end
106
+
107
+ # retries:: Number of discovery attempt (3 second sleep intervals)
108
+ # Returns container IP
109
+ def container_ip(retries=0, raise_on_fail=false)
110
+ retries.to_i.times do
111
+ ip = hw_detected_address || leased_address || lxc_stored_address
112
+ return ip if ip && self.class.connection_alive?(ip)
113
+ Chef::Log.warn "LXC IP discovery: Failed to detect live IP"
114
+ sleep(3)
115
+ end
116
+ raise "Failed to detect live IP address for container: #{name}" if raise_on_fail
117
+ end
118
+
119
+ # Container address via lxc config file
120
+ def lxc_stored_address
121
+ ip = File.readlines(container_config).detect{|line|
122
+ line.include?('ipv4')
123
+ }.to_s.split('=').last.to_s.strip
124
+ if(ip.to_s.empty?)
125
+ nil
126
+ else
127
+ Chef::Log.info "LXC Discovery: Found container address via storage: #{ip}"
128
+ ip
129
+ end
130
+ end
131
+
132
+ # Container address via dnsmasq lease
133
+ def leased_address
134
+ ip = nil
135
+ if(File.exists?(@lease_file))
136
+ leases = File.readlines(@lease_file).map{|line| line.split(' ')}
137
+ leases.each do |lease|
138
+ if(lease.include?(name))
139
+ ip = lease[2]
140
+ end
141
+ end
142
+ end
143
+ if(ip.to_s.empty?)
144
+ nil
145
+ else
146
+ Chef::Log.info "LXC Discovery: Found container address via DHCP lease: #{ip}"
147
+ ip
148
+ end
149
+ end
150
+
151
+ def hw_detected_address
152
+ hw = File.readlines(container_config).detect{|line|
153
+ line.include?('hwaddr')
154
+ }.to_s.split('=').last.to_s.strip
155
+ running? # need to do a list!
156
+ ip = File.readlines('/proc/net/arp').detect{|line|
157
+ line.include?(hw)
158
+ }.to_s.split(' ').first.to_s.strip
159
+ if(ip.to_s.empty?)
160
+ nil
161
+ else
162
+ Chef::Log.info "LXC Discovery: Found container address via HW addr: #{ip}"
163
+ ip
164
+ end
165
+ end
166
+
167
+ def sudo
168
+ self.class.sudo
169
+ end
170
+
171
+ # Full path to container
172
+ def container_path
173
+ File.join(@base_path, name)
174
+ end
175
+ alias_method :path, :container_path
176
+
177
+ # Full path to container configuration file
178
+ def container_config
179
+ File.join(container_path, 'config')
180
+ end
181
+ alias_method :config, :container_config
182
+
183
+ def container_rootfs
184
+ File.join(container_path, 'rootfs')
185
+ end
186
+ alias_method :rootfs, :container_rootfs
187
+
188
+ def expand_path(path)
189
+ File.join(container_rootfs, path)
190
+ end
191
+
192
+ # Start the container
193
+ def start
194
+ run_command("#{sudo}lxc-start -n #{name} -d")
195
+ run_command("#{sudo}lxc-wait -n #{name} -s RUNNING", :allow_failure_retry => 2)
196
+ end
197
+
198
+ # Stop the container
199
+ def stop
200
+ run_command("#{sudo}lxc-stop -n #{name}")
201
+ run_command("#{sudo}lxc-wait -n #{name} -s STOPPED", :allow_failure_retry => 2)
202
+ end
203
+
204
+ # Freeze the container
205
+ def freeze
206
+ run_command("#{sudo}lxc-freeze -n #{name}")
207
+ run_command("#{sudo}lxc-wait -n #{name} -s FROZEN", :allow_failure_retry => 2)
208
+ end
209
+
210
+ # Unfreeze the container
211
+ def unfreeze
212
+ run_command("#{sudo}lxc-unfreeze -n #{name}")
213
+ run_command("#{sudo}lxc-wait -n #{name} -s RUNNING", :allow_failure_retry => 2)
214
+ end
215
+
216
+ # Shutdown the container
217
+ def shutdown
218
+ run_command("#{sudo}lxc-shutdown -n #{name}")
219
+ run_command("#{sudo}lxc-wait -n #{name} -s STOPPED", :allow_failure_retry => 2)
220
+ end
221
+
222
+ def knife_container(cmd, ip)
223
+ require 'chef/knife/ssh'
224
+ Chef::Knife::Ssh.load_deps
225
+ k = Chef::Knife::Ssh.new([
226
+ ip, '-m', '-i', '/opt/hw-lxc-config/id_rsa', '--no-host-key-verify', cmd
227
+ ])
228
+ e = nil
229
+ begin
230
+ e = k.run
231
+ rescue SystemExit => e
232
+ end
233
+ raise CommandFailed.new(cmd) if e.nil? || e != 0
234
+ end
235
+
236
+ # Simple helper to shell out
237
+ def run_command(cmd, args={})
238
+ retries = args[:allow_failure_retry].to_i
239
+ begin
240
+ shlout = Mixlib::ShellOut.new(cmd,
241
+ :logger => Chef::Log.logger,
242
+ :live_stream => STDOUT
243
+ )
244
+ shlout.run_command
245
+ shlout.error!
246
+ rescue Mixlib::ShellOut::ShellCommandFailed, CommandFailed
247
+ if(args[:allow_failure])
248
+ true
249
+ elsif(retries > 0)
250
+ Chef::Log.warn "LXC run command failed: #{cmd}"
251
+ Chef::Log.warn "Retrying command. #{args[:allow_failure_retry].to_i - retries} of #{args[:allow_failure_retry].to_i} retries remain"
252
+ retries -= 1
253
+ retry
254
+ else
255
+ raise
256
+ end
257
+ end
258
+ end
259
+
260
+ # cmd:: Shell command string
261
+ # retries:: Number of retry attempts (1 second sleep interval)
262
+ # Runs command in container via ssh
263
+ def container_command(cmd, retries=1)
264
+ begin
265
+ knife_container(cmd, container_ip(5))
266
+ rescue => e
267
+ if(retries.to_i > 0)
268
+ Chef::Log.info "Encountered error running container command (#{cmd}): #{e}"
269
+ Chef::Log.info "Retrying command..."
270
+ retries = retries.to_i - 1
271
+ sleep(1)
272
+ retry
273
+ else
274
+ raise e
275
+ end
276
+ end
277
+ end
278
+
279
+ end
@@ -0,0 +1,40 @@
1
+ module ChefLxc
2
+ module Resource
3
+
4
+ def container(arg=nil)
5
+ set_or_return(:container, arg, :kind_of => [String], :required => true)
6
+ end
7
+
8
+ def lxc
9
+ @lxc ||= Lxc.new(
10
+ @container,
11
+ :base_dir => node[:lxc][:container_directory]
12
+ )
13
+ end
14
+
15
+ def path(arg=nil)
16
+ arg ? super(arg) : lxc.expand_path(super(arg))
17
+ end
18
+
19
+ def self.included(base)
20
+ base.class_eval do
21
+ def initialize(*args)
22
+ super
23
+ @container = nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class Chef
31
+ class Resource
32
+ class LxcTemplate < Template
33
+ include ChefLxc::Resource
34
+ end
35
+
36
+ class LxcFile < File
37
+ include ChefLxc::Resource
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,81 @@
1
+ class LxcFileConfig
2
+
3
+ attr_reader :network
4
+ attr_reader :base
5
+
6
+ class << self
7
+ def generate_config(resource)
8
+ config = []
9
+ config << "lxc.utsname = #{resource.utsname}"
10
+ [resource.network].flatten.each do |net_hash|
11
+ nhsh = Mash.new(net_hash)
12
+ flags = nhsh.delete(:flags)
13
+ %w(type link).each do |k|
14
+ config << "lxc.network.#{k} = #{nhsh.delete(k)}" if nhsh[k]
15
+ end
16
+ nhsh.each_pair do |k,v|
17
+ config << "lxc.network.#{k} = #{v}"
18
+ end
19
+ if(flags)
20
+ config << "lxc.network.flags = #{flags}"
21
+ end
22
+ end
23
+ if(resource.cap_drop)
24
+ config << "lxc.cap.drop = #{Array(resource.cap_drop).join(' ')}"
25
+ end
26
+ %w(pts tty arch devttydir mount mount_entry rootfs rootfs_mount pivotdir).each do |k|
27
+ config << "lxc.#{k.sub('_', '.')} = #{resource.send(k)}" if resource.send(k)
28
+ end
29
+ prefix = 'lxc.cgroup'
30
+ resource.cgroup.each_pair do |key, value|
31
+ if(value.is_a?(Array))
32
+ value.each do |val|
33
+ config << "#{prefix}.#{key} = #{val}"
34
+ end
35
+ else
36
+ config << "#{prefix}.#{key} = #{value}"
37
+ end
38
+ end
39
+ config.join("\n") + "\n"
40
+ end
41
+
42
+ end
43
+
44
+ def initialize(path)
45
+ raise 'LXC config file not found' unless File.exists?(path)
46
+ @path = path
47
+ @network = []
48
+ @base = Mash.new
49
+ parse!
50
+ end
51
+
52
+ private
53
+
54
+ def parse!
55
+ cur_net = nil
56
+ File.readlines(@path).each do |line|
57
+ if(line.start_with?('lxc.network'))
58
+ parts = line.split('=')
59
+ name = parts.first.split('.').last.strip
60
+ if(name.to_sym == :type)
61
+ @network << cur_net if cur_net
62
+ cur_net = Mash.new
63
+ end
64
+ if(cur_net)
65
+ cur_net[name] = parts.last.strip
66
+ else
67
+ raise "Expecting 'lxc.network.type' to start network config block. Found: 'lxc.network.#{name}'"
68
+ end
69
+ else
70
+ parts = line.split('=')
71
+ name = parts.first.sub('lxc.', '').strip
72
+ if(@base[name])
73
+ @base[name] = [@base[name], parts.last.strip].flatten
74
+ else
75
+ @base[name] = parts.last
76
+ end
77
+ end
78
+ end
79
+ @network << cur_net if cur_net
80
+ end
81
+ end
@@ -0,0 +1,11 @@
1
+ maintainer "Chris Roberts"
2
+ maintainer_email "chrisroberts.code@gmail.com"
3
+ license "Apache 2.0"
4
+ description "Chef driven Linux Containers"
5
+ long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
6
+ version "0.1.1"
7
+
8
+ supports 'ubuntu', '12.04'
9
+
10
+ depends 'omnibus_updater'
11
+ suggests 'bridger'
@@ -0,0 +1,82 @@
1
+ require 'securerandom'
2
+
3
+ def load_current_resource
4
+ new_resource._lxc Lxc.new(
5
+ new_resource.name,
6
+ :base_dir => node[:lxc][:container_directory],
7
+ :dnsmasq_lease_file => node[:lxc][:dnsmasq_lease_file]
8
+ )
9
+ new_resource.utsname new_resource.name unless new_resource.utsname
10
+ new_resource.rootfs new_resource._lxc.rootfs unless new_resource.rootfs
11
+ new_resource.default_bridge node[:lxc][:bridge] unless new_resource.default_bridge
12
+ new_resource.mount ::File.join(new_resource._lxc.path, 'fstab') unless new_resource.mount
13
+ config = LxcFileConfig.new(new_resource._lxc.container_config)
14
+ if((new_resource.network.nil? || new_resource.network.empty?))
15
+ if(config.network.empty?)
16
+ default_net = {
17
+ :type => :veth,
18
+ :link => new_resource.default_bridge,
19
+ :flags => :up,
20
+ :hwaddr => "00:16:3e#{SecureRandom.hex(3).gsub(/(..)/, ':\1')}"
21
+ }
22
+ default_net.merge!(:ipv4 => new_resource.static_ip) if new_resource.static_ip
23
+ else
24
+ default_net = config.network.first
25
+ default_net.merge!(:link => new_resource.default_bridge)
26
+ default_net.merge!(:ipv4 => new_resource.static_ip) if new_resource.static_ip
27
+ end
28
+ new_resource.network(default_net)
29
+ else
30
+ [new_resource.network].flatten.each_with_index do |net_hash, idx|
31
+ if(config.network[idx].nil? || config.network[idx][:hwaddr].nil?)
32
+ net_hash[:hwaddr] ||= "00:16:3e#{SecureRandom.hex(3).gsub(/(..)/, ':\1')}"
33
+ end
34
+ end
35
+ end
36
+ new_resource.cgroup(
37
+ Chef::Mixin::DeepMerge.merge(
38
+ Mash.new(
39
+ 'devices.deny' => 'a',
40
+ 'devices.allow' => [
41
+ 'c *:* m',
42
+ 'b *:* m',
43
+ 'c 1:3 rwm',
44
+ 'c 1:5 rwm',
45
+ 'c 5:1 rwm',
46
+ 'c 5:0 rwm',
47
+ 'c 1:9 rwm',
48
+ 'c 1:8 rwm',
49
+ 'c 136:* rwm',
50
+ 'c 5:2 rwm',
51
+ 'c 254:0 rwm',
52
+ 'c 10:229 rwm',
53
+ 'c 10:200 rwm',
54
+ 'c 1:7 rwm',
55
+ 'c 10:228 rwm',
56
+ 'c 10:232 rwm'
57
+ ]
58
+ ),
59
+ new_resource.cgroup
60
+ )
61
+ )
62
+ end
63
+
64
+ action :create do
65
+ ruby_block "lxc config_updater[#{new_resource.utsname}]" do
66
+ block do
67
+ new_resource.updated_by_last_action(true)
68
+ end
69
+ action :nothing
70
+ end
71
+
72
+ directory new_resource._lxc.container_path do
73
+ action :create
74
+ end
75
+
76
+ file "lxc update_config[#{new_resource.utsname}]" do
77
+ path new_resource._lxc.container_config
78
+ content LxcFileConfig.generate_config(new_resource)
79
+ mode 0644
80
+ notifies :create, resources(:ruby_block => "lxc config_updater[#{new_resource.utsname}]"), :immediately
81
+ end
82
+ end