elecksee 1.0.2 → 1.0.4

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 (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