lxc-config/hooks/create-lxc-config

125 lines
3.8 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require "erb"
require "pathname"
require 'ostruct'
require 'optparse'
require 'json'
require 'netaddr'
def try_env(key)
ENV[key] or abort("enviroment variable '#{key}' not set")
end
def address_free?(assigned_subnets, address)
assigned_subnets.find { |s| s.contains?(address) || s == address }
end
def find_address(subnet, assigned_subsubnets)
subnet.enumerate(Limit: 1E4, Short: true)[1..1E4].each do |cidr|
unless address_free?(assigned_subsubnets, cidr)
return cidr
end
end
end
CONFIG_PATH = Pathname.new("/etc/lxc/")
IPV4_SUBNET = NetAddr::CIDR.create("10.100.0.0/16")
IPV6_SUBNET = NetAddr::CIDR.create("2a01:4f8:210:31fd:1::/80")
options = OpenStruct.new
options.container_name = try_env("LXC_NAME")
options.container_config = try_env("LXC_CONFIG_FILE")
options.rootfs = try_env("LXC_ROOTFS_PATH")
OptionParser.new do |opts|
opts.banner = "Usage: create-lxc-config [options]"
opts.on("-4", "--ipv4", "private Ipv4 subnet") do |v|
options.ipv4 = v
end
opts.on("-6", "--ipv6", "public Ipv6 subnet") do |v|
options.ipv6 = v
end
opts.on("--group GROUP", String, "set ansible group (default NONE)") do |group|
options.group = group
end
opts.on("--vars FILE", String, "set json file for ansible variables") do |vars|
begin
options.vars = JSON.load(File.open(vars))
unless options.vars.is_a? Hash
abort "vars: Should be a json object"
end
rescue SystemCallError => e
abort "Failed to open '#{vars}': #{e.message}"
rescue JSON::ParserError => e
abort "Failed to parse ansible variables: #{e.message}"
end
end
end.parse!
container_data = CONFIG_PATH.join("container.json")
data = if File.exists?(container_data)
JSON.load(File.open(container_data))
else
{}
end
data["network"] ||= {}
#if data["network"][options.container_name]
# abort "container name '#{options.container_name}' in '#{container_data}' already in use!"
#end
data["network"][options.container_name] = {}
ipv4_subnets = data["network"].map {|k,v| NetAddr::CIDR.create(v["ipv4"]) if v["ipv4"]}.compact!
ipv6_subnets = data["network"].map {|k,v| NetAddr::CIDR.create(v["ipv6"]) if v["ipv6"]}.compact!
ipv4_address = if options.ipv4.nil?
find_address(IPV4_SUBNET, ipv4_subnets)
elsif address_free?(ipv4_subnets, options.ipv4)
abort "The address #{ipv4} is already assigned"
else
options.ipv4
end
ipv6_address = if options.ipv6.nil?
find_address(IPV6_SUBNET, ipv6_subnets)
elsif address_free?(ipv6_subnets, options.ipv6)
abort "The address #{ipv4} is already assigned"
else
options.ipv6
end
network_config = data["network"][options.container_name]
network_config["ipv4"] = NetAddr::CIDR.create(ipv4_address).to_s(Short: true)
network_config["ipv6"] = NetAddr::CIDR.create(ipv6_address).to_s(Short: true)
network_config["group"] = options.group
network_config["vars"] = options.vars
open(container_data, File::CREAT|File::TRUNC|File::RDWR) do |f|
f.write(JSON.pretty_generate(data))
end
class TemplateContext
def initialize(hash)
hash.each do |key, value|
singleton_class.send(:define_method, key) { value }
end
end
def get_binding
binding
end
end
context = TemplateContext.new(container_name: options.container_name,
ipv4: NetAddr::CIDR.create(ipv4_address, Mask: IPV4_SUBNET.to_i(:netmask)).desc(IP: true, Short: true),
ipv6: NetAddr::CIDR.create(ipv6_address, Mask: IPV6_SUBNET.to_i(:netmask)).desc(IP: true, Short: true),
rootfs: options.rootfs)
erb = ERB.new(File.read(CONFIG_PATH.join("templates", "config.erb")))
open(options.container_config, "w+") do |f|
f.write(erb.result(context.get_binding))
end