#!/opt/chef/embedded/bin/ruby require 'json' require 'getoptlong' LXC_HOME = '/var/lib/lxc' opts = GetoptLong.new( ['--help', '-h', GetoptLong::NO_ARGUMENT], ['--version', '-v', GetoptLong::NO_ARGUMENT], ['--template', '-t', GetoptLong::REQUIRED_ARGUMENT] ) # Default template = 'ubuntu' opts.each do |opt, arg| case opt when '--help' show_usage exit 0 when '--version' show_version exit 0 when '--template' template = arg raise 'Unsupported template provided' unless %w(debian fedora ubuntu).include?(template) end end def conf @c ||= JSON.load(File.read('/etc/knife-lxc/config.json')) end def lxc_exists?(lxc_name) current_names = Dir.glob(File.join(LXC_HOME, '*')).map{|c| File.basename(c)} current_names.include?(lxc_name) end def ensure_name_availability!(name) raise 'Name already in use' if lxc_exists?(name) end def available_ips # TODO: Add range calculation range = conf['address']['range'] if(range.to_s.empty?) range = (range.split('-').first.split('.').last..range.split('-').last).map{|oct| "#{range.split('-').first.split('.').slice(0,3).join('.')}.#{oct}" } else range = [] end (conf['addresses']['static'] + range).compact end def used_ips Dir.glob(File.join(LXC_HOME, '*')).map{ |ctn| File.readlines(File.join(ctn, 'rootfs', 'etc', 'network', 'interfaces')).detect{ |line| line.include?('address') }.to_s.split(' ').last.to_s.strip }.reject{ |addr| addr.empty? } end def update_container_ip(name) new_ip = (available_ips - used_ips).pop raise 'no ips available' unless new_ip update_network_interfaces(name, new_ip) new_ip end def update_network_interfaces(name, address) default_nm = '255.255.255.0' (parts = address.split('.')).last.replace('1') default_gw = parts.join('.') File.open(File.join(LXC_HOME, name, 'rootfs', 'etc', 'network', 'interfaces'), 'w') do |file| file.puts "auto lo eth0" file.puts "iface lo inet loopback" file.puts "iface eth0 inet static" file.puts " address #{address}" file.puts " gateway #{conf['gateway'] || default_gw}" file.puts " netmask #{conf['netmask'] || default_nm}" end end def sudoable_ubuntu(name) path = File.join(LXC_HOME, name, 'rootfs', 'etc', 'sudoers') content = File.readlines(path).map do |line| if(line.start_with?('%sudo')) '%sudo ALL=(ALL) NOPASSWD:ALL' else line end end File.open(path, 'w') do |file| file.write(content.join("\n")) end end def clone_container(name, template) %x{lxc-clone -o #{template}_base -n #{name}} end def start_container(name) %x{lxc-start -n #{name} -d} %x{lxc-wait -n #{name} -s RUNNING} end def stop_container(name) %x{lxc-shutdown -n #{name} -d} %x{lxc-wait -n #{name} -s STOPPED} end def create_lxc(lxc_name, template) ensure_name_availability!(lxc_name) clone_container(lxc_name, template) address = update_container_ip(lxc_name) # TODO: Update debian and fedora to use sudo user and remove root login if(lxc_name == 'ubuntu') sudoable_ubuntu(lxc_name) end start_container(lxc_name) puts "LXC Node #{lxc_name} available at: #{address}" end def lxc_address(name) if(File.exists?(int = File.join(LXC_HOME, name, 'rootfs', 'etc', 'network', 'interfaces'))) addr = File.readlines(int).detect{|l|l.include?('address')}.to_s.split(' ').last.to_s.strip addr.empty? ? 'dynamic' : addr else 'dynamic' end end def lxc_type(name) base = File.join(LXC_HOME, name, 'rootfs', 'etc') if(File.exists?(lsb = File.join(base, 'lsb-release'))) File.readlines(lsb).last.split('=').last.strip.gsub('"', '') elsif(File.exists?(sys_rel = File.join(base, 'system-release'))) File.readlines(sys_rel).first.strip elsif(File.exists?(deb_ver = File.join(base, 'debian_version'))) "Debain #{File.read(deb_ver).strip}" else 'UNKNOWN' end end def list_lxcs info = Hash[ *Dir.glob(File.join(LXC_HOME, '*')).map{|dir| key = File.basename(dir) [key, {:address => lxc_address(key), :type => lxc_type(key)}] }.sort{|a,b| a.first <=> b.first }.flatten ] info.each do |name, info| puts "#{name}" puts " Type: #{info[:type]}" puts " Address: #{info[:address]}" end end def info_lxc(lxc_name) puts "#{lxc_name}" puts " Type: #{lxc_type(lxc_name)}" puts " Address: #{lxc_address(lxc_name)}" end def delete_lxc(lxc_name) %x{lxc-stop -n #{lxc_name}} %x{lxc-wait -n #{lxc_name} -s STOPPED} %x{lxc-destroy -n #{lxc_name}} end action = ARGV.first.to_s case action when 'create' lxc_name = ARGV[1] create_lxc(lxc_name, template) when 'list' list_lxcs when 'info' lxc_name = ARGV[1] if(lxc_exists?(lxc_name)) info_lxc(lxc_name) else $stderr.puts "Requested container does not exist: #{lxc_name}" exit 2 end when 'start' lxc_name = ARGV[1] if(lxc_exists?(lxc_name)) print "Starting container #{lxc_name}... " start_lxc(lxc_name) puts 'started' else $stderr.puts "Requested container does not exist: #{lxc_name}" exit 2 end when 'stop' lxc_name = ARGV[1] if(lxc_exists?(lxc_name)) print "Stopping container #{lxc_name}... " stop_lxc(lxc_name) puts 'stopped' else $stderr.puts "Requested container does not exist: #{lxc_name}" exit 2 end when 'delete' lxc_name = ARGV[1] if(lxc_exists?(lxc_name)) print "Deleting LXC #{lxc_name}... " destroy_lxc(lxc_name) puts 'done' else $stderr.puts "Requested container does not exist: #{lxc_name}" exit 2 end else $stderr.puts "ERROR: Unknown action: #{action}" exit 1 end