#!/opt/chef/embedded/bin/ruby

require 'json'
require 'getoptlong'
require 'elecksee/lxc'
require 'elecksee/clone'

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)
  Lxc::Clone(:original => "#{template}_base", :new_name => name).clone!
end

def start_container(name)
  Lxc.new(name).start
end

def stop_container(name)
  Lxc.new(name).stop
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)
  Lxc.new(name).container_address
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)
  Lxc.new(lxc_name).stop
  %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

