elecksee 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.md +4 -0
  2. data/bin/lxc-awesome-ephemeral +57 -2
  3. data/lib/elecksee/clone.rb +15 -0
  4. data/lib/elecksee/ephemeral.rb +310 -0
  5. data/lib/elecksee/helpers.rb +70 -0
  6. data/lib/elecksee/lxc.rb +409 -5
  7. data/lib/elecksee/lxc_file_config.rb +86 -0
  8. data/lib/elecksee/storage/overlay_directory.rb +31 -0
  9. data/lib/elecksee/storage/overlay_mount.rb +60 -0
  10. data/lib/elecksee/storage/virtual_device.rb +81 -0
  11. data/lib/elecksee/version.rb +1 -1
  12. metadata +9 -39
  13. data/Gemfile.lock +0 -18
  14. data/lib/elecksee/awesome.rb +0 -14
  15. data/lib/elecksee/vendor/lxc/CHANGELOG.md +0 -37
  16. data/lib/elecksee/vendor/lxc/Gemfile +0 -4
  17. data/lib/elecksee/vendor/lxc/Gemfile.lock +0 -41
  18. data/lib/elecksee/vendor/lxc/README.md +0 -112
  19. data/lib/elecksee/vendor/lxc/attributes/default.rb +0 -28
  20. data/lib/elecksee/vendor/lxc/files/default/knife_lxc +0 -228
  21. data/lib/elecksee/vendor/lxc/files/default/lxc-awesome-ephemeral +0 -495
  22. data/lib/elecksee/vendor/lxc/libraries/lxc.rb +0 -366
  23. data/lib/elecksee/vendor/lxc/libraries/lxc_expanded_resources.rb +0 -40
  24. data/lib/elecksee/vendor/lxc/libraries/lxc_file_config.rb +0 -84
  25. data/lib/elecksee/vendor/lxc/libraries/monkey.rb +0 -51
  26. data/lib/elecksee/vendor/lxc/metadata.rb +0 -12
  27. data/lib/elecksee/vendor/lxc/providers/config.rb +0 -75
  28. data/lib/elecksee/vendor/lxc/providers/container.rb +0 -318
  29. data/lib/elecksee/vendor/lxc/providers/default.rb +0 -57
  30. data/lib/elecksee/vendor/lxc/providers/ephemeral.rb +0 -40
  31. data/lib/elecksee/vendor/lxc/providers/fstab.rb +0 -30
  32. data/lib/elecksee/vendor/lxc/providers/interface.rb +0 -45
  33. data/lib/elecksee/vendor/lxc/providers/service.rb +0 -53
  34. data/lib/elecksee/vendor/lxc/recipes/containers.rb +0 -13
  35. data/lib/elecksee/vendor/lxc/recipes/default.rb +0 -58
  36. data/lib/elecksee/vendor/lxc/recipes/install_dependencies.rb +0 -15
  37. data/lib/elecksee/vendor/lxc/recipes/knife.rb +0 -37
  38. data/lib/elecksee/vendor/lxc/resources/config.rb +0 -19
  39. data/lib/elecksee/vendor/lxc/resources/container.rb +0 -54
  40. data/lib/elecksee/vendor/lxc/resources/default.rb +0 -12
  41. data/lib/elecksee/vendor/lxc/resources/ephemeral.rb +0 -13
  42. data/lib/elecksee/vendor/lxc/resources/fstab.rb +0 -12
  43. data/lib/elecksee/vendor/lxc/resources/interface.rb +0 -13
  44. data/lib/elecksee/vendor/lxc/resources/service.rb +0 -5
  45. data/lib/elecksee/vendor/lxc/templates/default/client.rb.erb +0 -13
  46. data/lib/elecksee/vendor/lxc/templates/default/default-lxc.erb +0 -3
  47. data/lib/elecksee/vendor/lxc/templates/default/file_content.erb +0 -2
  48. data/lib/elecksee/vendor/lxc/templates/default/fstab.erb +0 -5
  49. data/lib/elecksee/vendor/lxc/templates/default/interface.erb +0 -27
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## v0.1.4
2
+ * Removes vendored lxc cookbook
3
+ * Adds proper implementations for lxc and ephemerals
4
+
1
5
  ## v0.1.2
2
6
  * Update Chef::Log usage to only apply when Chef is loaded
3
7
 
@@ -1,5 +1,60 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'elecksee/awesome'
3
+ require 'elecksee/ephemeral'
4
+ require 'getoptlong'
4
5
 
5
- Elecksee::Awesome.run!
6
+ require 'pp'
7
+
8
+ opts = Lxc::Ephemeral.options.map do |k,v|
9
+ res = [
10
+ ["--#{k}", v[:short], v[:type] == :boolean ? GetoptLong::NO_ARGUMENT : GetoptLong::REQUIRED_ARGUMENT]
11
+ ]
12
+ if(v[:aliases])
13
+ Array(v[:aliases]).each do |al|
14
+ res << ["--#{al}", v[:type] == :boolean ? GetoptLong::NO_ARGUMENT : GetoptLong::REQUIRED_ARGUMENT]
15
+ end
16
+ end
17
+ res
18
+ end.flatten(1) << ['--help', '-h', GetoptLong::NO_ARGUMENT]
19
+
20
+ opts = GetoptLong.new(*opts)
21
+
22
+ config = {}
23
+ opts.each do |opt,arg|
24
+ case opt
25
+ when '--help'
26
+ puts 'Usage: lxc-awesome-ephemeral [OPTS] -o NAME'
27
+ output = []
28
+ Lxc::Ephemeral.options.map do |k,v|
29
+ option = "--#{k} #{v[:short]}"
30
+ unless(v[:type] == :boolean)
31
+ option << ' VAL'
32
+ end
33
+ output << {:opt => option, :desc => v[:desc], :alias => v[:aliases]}
34
+ end
35
+ opt_len = output.map{|o| o[:opt].length}.max + 2
36
+ output.each do |option|
37
+ puts " #{option[:opt]}:#{' ' * (opt_len - option[:opt].length)}#{option[:desc]}"
38
+ Array(option[:alias]).each do |a|
39
+ puts " --#{a}"
40
+ end
41
+ end
42
+ exit -1
43
+ when '--version'
44
+ else
45
+ key = opt.sub('--', '').to_sym
46
+ opt_conf = Lxc::Ephemeral.options[key]
47
+ case opt_conf[:type]
48
+ when :boolean
49
+ val = true
50
+ when :integer
51
+ val = arg.to_i
52
+ else
53
+ val = arg
54
+ end
55
+ end
56
+ config[key] = val
57
+ end
58
+
59
+ ephemeral = Lxc::Ephemeral.new(config.merge(:cli => true))
60
+ ephemeral.start!
@@ -0,0 +1,15 @@
1
+ require 'elecksee/helpers'
2
+ require 'elecksee/lxc'
3
+
4
+ class Lxc
5
+ class Clone
6
+
7
+
8
+ def initialize(args={})
9
+ args = Mash.new(args)
10
+ %w(original new_name).each do |key|
11
+ raise ArgumentError.new "Missing required parameter: #{key}" unless args[key]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,310 @@
1
+ require 'securerandom'
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'etc'
5
+
6
+ %w(
7
+ helpers lxc storage/overlay_directory
8
+ storage/overlay_mount storage/virtual_device
9
+ ).each do |path|
10
+ require "elecksee/#{path}"
11
+ end
12
+
13
+ class Lxc
14
+
15
+ class Ephemeral
16
+
17
+ include Helpers
18
+
19
+ NAME_FILES = %w(fstab config)
20
+ HOSTNAME_FILES = %w(
21
+ rootfs/etc/hostname
22
+ rootfs/etc/hosts
23
+ rootfs/etc/sysconfig/network
24
+ rootfs/etc/sysconfig/network-scripts/ifcfg-eth0
25
+ )
26
+
27
+ class << self
28
+ attr_reader :options
29
+
30
+ def option(name, short, type, args={})
31
+ @options ||= {}
32
+ @options[name] = args.merge(:short => short, :type => type)
33
+ instance_eval do
34
+ attr_accessor name.to_sym
35
+ end
36
+ end
37
+ end
38
+
39
+ option :original, '-o', :string, :required => true, :desc => 'Original container name'
40
+ option :ipaddress, '-I', :string, :desc => 'Custom IP address'
41
+ option :gateway, '-G', :string, :desc => 'Custom gateway'
42
+ option :netmask, '-N', :string, :default => '255.255.255.0', :desc => 'Custom netmask'
43
+ option :device, '-D', :integer, :desc => 'Create VBD for overlay of size {SIZE}M'
44
+ option :directory, '-z', :boolean, :desc => 'Use host based directory for overlay'
45
+ option :union, '-U', :string, :desc => 'Overlay FS to use (overlayfs or aufs)'
46
+ option :daemon, '-d', :boolean, :desc => 'Run as a daemon'
47
+ option :bind, '-b', :string, :desc => 'Bind provided directory (non-ephemeral)'
48
+ option :user, '-u', :string, :desc => 'Deprecated: Provided for compatibility'
49
+ option :ssh_key, '-S', :string, :default => '/opt/hw-lxc-config/id_rsa', :aliases => 'ssh-key', :desc => 'Deprecated: Provided for compatibility'
50
+ option :lxc_dir, '-L', :string, :default => '/var/lib/lxc', :aliases => 'lxc-dir', :desc => 'Directory of LXC store'
51
+ option :tmp_dir, '-T', :string, :default => '/tmp/lxc/ephemerals', :aliases => 'tmp-dir', :desc => 'Directory of ephemeral temp files'
52
+
53
+ attr_reader :name
54
+ attr_reader :cli
55
+ attr_reader :hostname
56
+ attr_reader :path
57
+ attr_reader :lxc
58
+ attr_reader :ephemeral_device
59
+ attr_reader :ephemeral_overlay
60
+ attr_reader :ephemeral_binds
61
+
62
+ def initialize(args={})
63
+ configure!(args)
64
+ @cli = args[:cli]
65
+ @path = Dir.mktmpdir(File.join(lxc_dir, original))
66
+ @name = File.basename(@path)
67
+ @hostname = @name.gsub(%r{[^A-Za-z0-9\-]}, '')
68
+ @ephemeral_binds = []
69
+ @lxc = nil
70
+ end
71
+
72
+ def register_traps
73
+ %w(TERM INT QUIT).each do |sig|
74
+ Signal.trap(sig){ cleanup }
75
+ end
76
+ end
77
+
78
+ def cli_output
79
+ if(cli)
80
+ puts "New ephemeral container started. (#{name})"
81
+ puts " - Connect using: sudo ssh -i #{ssh_key} root@#{lxc.container_ip(10)}"
82
+ end
83
+ end
84
+
85
+ def start!(*args)
86
+ register_traps
87
+ setup
88
+ if(daemon)
89
+ if(args.include?(:fork))
90
+ fork do
91
+ lxc.start
92
+ cli_output
93
+ lxc.wait_for_state(:stopped)
94
+ cleanup
95
+ end
96
+ else
97
+ Process.daemon
98
+ lxc.start
99
+ cli_output
100
+ lxc.wait_for_state(:stopped)
101
+ cleanup
102
+ end
103
+ else
104
+ lxc.start
105
+ cli_output
106
+ lxc.wait_for_state(:stopped)
107
+ cleanup
108
+ end
109
+ end
110
+
111
+ def cleanup
112
+ lxc.stop
113
+ @ephemeral_overlay.unmount
114
+ @ephemeral_binds.map(&:destroy)
115
+ @ephemeral_device.destroy
116
+ if(lxc.path.to_path.split('/').size > 1)
117
+ command("rm -rf #{lxc.path.to_path}", :sudo => true)
118
+ true
119
+ else
120
+ $stderr.puts "This path seems bad and I won't remove it: #{lxc.path.to_path}"
121
+ false
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def configure!(args)
128
+ self.class.options.each do |name, opts|
129
+ argv = args.detect{|k,v| (Array(opts[:aliases]) + Array(opts[:short]) + [name]).include?(k.to_sym)}
130
+ argv = argv.last if argv
131
+ argv ||= opts[:default]
132
+ if(argv)
133
+ check_type!(name, argv, opts[:type])
134
+ self.send("#{name}=", argv)
135
+ else
136
+ if(opts[:required])
137
+ raise ArgumentError.new "Missing required argument: #{name}"
138
+ end
139
+ end
140
+ end
141
+ if(ipaddress && gateway.nil?)
142
+ self.gateway = ipaddress.sub(%r{\d+$}, '1')
143
+ end
144
+ end
145
+
146
+ def check_type!(arg_name, val, type)
147
+ valid = false
148
+ case type
149
+ when :string
150
+ valid = val.is_a?(String)
151
+ when :boolean
152
+ valid = val.is_a?(TrueClass) || val.is_a?(FalseClass)
153
+ when :integer
154
+ valid = val.is_a?(Numeric)
155
+ end
156
+ raise ArgumentError.new "Invalid type provided for #{arg_name}. Expecting value type of: #{type.inspect} Got: #{val.class} - #{val}" unless valid
157
+ end
158
+
159
+ def setup
160
+ create
161
+ build_overlay
162
+ update_naming
163
+ discover_binds
164
+ apply_custom_networking if ipaddress
165
+ end
166
+
167
+ def build_overlay
168
+ if(directory)
169
+ @ephemeral_device = OverlayDirectory.new(name, :tmp_dir => directory.is_a?(String) ? directory : tmp_dir)
170
+ else
171
+ @ephemeral_device = VirtualDevice.new(name, :size => device, :tmp_fs => !device, :tmp_dir => tmp_dir)
172
+ @ephemeral_device.mount
173
+ end
174
+ @ephemeral_overlay = OverlayMount.new(
175
+ :base => Lxc.new(original).rootfs.to_path,
176
+ :overlay => ephemeral_device.target_path,
177
+ :target => lxc.rootfs.to_path,
178
+ :overlay_type => union
179
+ )
180
+ @ephemeral_overlay.mount
181
+ end
182
+
183
+ def writable_path!(path)
184
+ unless(File.directory?(File.dirname(path)))
185
+ command("mkdir -p #{File.dirname(path)}", :sudo => true)
186
+ end
187
+ unless(File.exists?(path))
188
+ command("touch #{path}", :sudo => true)
189
+ end
190
+ command("chown #{Etc.getlogin} #{path}", :sudo => true)
191
+ end
192
+
193
+ def create
194
+ Dir.glob(File.join(lxc_dir, original, '*')).each do |o_path|
195
+ next unless File.file?(o_path)
196
+ command("cp #{o_path} #{File.join(path, File.basename(o_path))}", :sudo => true)
197
+ end
198
+ command("chown -R #{Etc.getlogin} #{path}", :sudo => true)
199
+ @lxc = Lxc.new(name)
200
+ Dir.mkdir(lxc.rootfs.to_path)
201
+ contents = File.readlines(lxc.config)
202
+ File.open(lxc.config, 'w') do |file|
203
+ contents.each do |line|
204
+ if(line.strip.start_with?('lxc.network.hwaddr'))
205
+ file.write "00:16:3e#{SecureRandom.hex(3).gsub(/(..)/, ':\1')}"
206
+ else
207
+ file.write line
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # TODO: Discovered binds for ephemeral are all tmpfs for now.
214
+ def discover_binds
215
+ contents = File.readlines(lxc.path.join('fstab'))
216
+ File.open(lxc.path.join('fstab'), 'w') do |file|
217
+ contents.each do |line|
218
+ parts = line.split(' ')
219
+ if(parts[3] == 'bind')
220
+ source = parts.first
221
+ target = parts[1].sub(%r{^.+rootfs/}, '')
222
+ container_target = lxc.rootfs.join(target).to_path
223
+ device = VirtualDevice.new(target.gsub('/', '_'), :tmp_fs => true)
224
+ device.mount
225
+ FileUtils.mkdir_p(container_target)
226
+ ephemeral_binds << device
227
+ if(union == 'overlayfs')
228
+ file.write "none #{container_target} overlayfs upperdir=#{device.mount_path},lowerdir=#{source} 0 0"
229
+ else
230
+ file.write "none #{container_target} aufs br=#{device.mount_path}=rw:#{source}=ro,noplink 0 0"
231
+ end
232
+ else
233
+ file.write line
234
+ end
235
+ end
236
+ # If bind option used, bind in for rw
237
+ if(bind)
238
+ command("mkdir -p #{lxc.rootfs.join(bind).to_path}", :sudo => true)
239
+ file.puts "#{bind} #{lxc.rootfs.join(bind)} none bind 0 0"
240
+ end
241
+ end
242
+ end
243
+
244
+ def update_naming
245
+ NAME_FILES.each do |file|
246
+ next unless File.exists?(lxc.path.join(file))
247
+ writable_path!(lxc.path.join(file).to_path)
248
+ contents = File.read(lxc.path.join(file))
249
+ File.open(lxc.path.join(file), 'w') do |new_file|
250
+ new_file.write contents.gsub(original, name)
251
+ end
252
+ end
253
+ HOSTNAME_FILES.each do |file|
254
+ next unless File.exists?(lxc.path.join(file))
255
+ writable_path!(lxc.path.join(file).to_path)
256
+ contents = File.read(lxc.path.join(file))
257
+ File.open(lxc.path.join(file), 'w') do |new_file|
258
+ new_file.write contents.gsub(original, hostname)
259
+ end
260
+ end
261
+ end
262
+
263
+ def el_platform?
264
+ lxc.rootfs.join('etc/redhat-release').exist?
265
+ end
266
+
267
+ def apply_custom_networking
268
+ if(el_platform?)
269
+ writable_path!(path = lxc.rootfs.join('etc/sysconfig/network-scripts/ifcfg-eth0'))
270
+ File.open(path, 'w') do |file|
271
+ file.write <<-EOF
272
+ DEVICE=eth0
273
+ BOOTPROTO=static
274
+ NETMASK=#{netmask}
275
+ IPADDR=#{ipaddress}
276
+ ONBOOT=yes
277
+ TYPE=Ethernet
278
+ USERCTL=yes
279
+ PEERDNS=yes
280
+ IPV6INIT=no
281
+ GATEWAY=#{gateway}
282
+ EOF
283
+ end
284
+ writable_path!(path = lxc.rootfs.join('etc/sysconfig/network'))
285
+ File.open(path, 'w') do |file|
286
+ file.write <<-EOF
287
+ NETWORKING=yes
288
+ HOSTNAME=#{hostname}
289
+ EOF
290
+ end
291
+ File.open(@lxc.rootfs.join('etc/rc.local'), 'w') do |file|
292
+ file.puts "hostname #{hostname}"
293
+ end
294
+ else
295
+ writable_path!(path = lxc.rootfs.join('etc/network/interfaces'))
296
+ File.open(path, 'w') do |file|
297
+ file.write <<-EOF
298
+ auto lo
299
+ iface lo inet loopback
300
+ auto eth0
301
+ iface eth0 inet static
302
+ address #{ipaddress}
303
+ netmask #{netmask}
304
+ gateway #{gateway}
305
+ EOF
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,70 @@
1
+ class Lxc
2
+ class CommandFailed < StandardError
3
+ end
4
+
5
+ module Helpers
6
+
7
+ def sudo
8
+ Lxc.sudo
9
+ end
10
+
11
+ # Simple helper to shell out
12
+ def run_command(cmd, args={})
13
+ retries = args[:allow_failure_retry].to_i
14
+ cmd = [sudo, cmd].join(' ') if args[:sudo]
15
+ begin
16
+ shlout = Mixlib::ShellOut.new(cmd,
17
+ :logger => defined?(Chef) ? Chef::Log.logger : log,
18
+ :live_stream => args[:livestream] ? STDOUT : nil,
19
+ :timeout => args[:timeout] || 1200,
20
+ :environment => {'HOME' => detect_home}
21
+ )
22
+ shlout.run_command
23
+ shlout.error!
24
+ shlout
25
+ rescue Mixlib::ShellOut::ShellCommandFailed, CommandFailed, Mixlib::ShellOut::CommandTimeout
26
+ if(retries > 0)
27
+ log.warn "LXC run command failed: #{cmd}"
28
+ log.warn "Retrying command. #{args[:allow_failure_retry].to_i - retries} of #{args[:allow_failure_retry].to_i} retries remain"
29
+ sleep(0.3)
30
+ retries -= 1
31
+ retry
32
+ elsif(args[:allow_failure])
33
+ true
34
+ else
35
+ raise
36
+ end
37
+ end
38
+ end
39
+
40
+ def command(*args)
41
+ run_command(*args)
42
+ end
43
+
44
+ def log
45
+ if(defined?(Chef))
46
+ Chef::Log
47
+ else
48
+ unless(@logger)
49
+ require 'logger'
50
+ @logger = Logger.new('/dev/null')
51
+ end
52
+ @logger
53
+ end
54
+ end
55
+
56
+ # Detect HOME environment variable. If not an acceptable
57
+ # value, set to /root or /tmp
58
+ def detect_home(set_if_missing=false)
59
+ if(ENV['HOME'] && Pathname.new(ENV['HOME']).absolute?)
60
+ ENV['HOME']
61
+ else
62
+ home = File.directory?('/root') && File.writable?('/root') ? '/root' : '/tmp'
63
+ if(set_if_missing)
64
+ ENV['HOME'] = home
65
+ end
66
+ home
67
+ end
68
+ end
69
+ end
70
+ end