vagabond 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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