125 lines
3.8 KiB
Plaintext
125 lines
3.8 KiB
Plaintext
|
#!/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
|