require "erb" require "netaddr" require "pathname" require "fileutils" module Lxc class Container def initialize(data, name:, ipv4: nil, ipv6: nil, ula: nil, **options) @data = data @data["network"] ||= {} @data["network"][name] = {} zone = @data["zone"] || {} @ipv4_subnet = NetAddr::CIDR.create(zone["ipv4-subnet"] || "192.168.10.0/24") @ipv6_subnet = NetAddr::CIDR.create(zone["ipv6-subnet"] || "fd7d:aed0:18aa::/48") @ula_subnet = NetAddr::CIDR.create(zone["ula-subnet"] || "fdc5:bdb8:b81::/48") @container_root = Pathname.new(zone["lxc-root"]).join(name) network = data["network"] @name = name @ipv4 = ipv4 || find_address(@ipv4_subnet, collect_subnets(network, "ipv4")) @ipv6 = ipv6 || find_address(@ipv6_subnet, collect_subnets(network, "ipv6")) @ula = ula || find_address(@ula_subnet, collect_subnets(network, "ula")) @options = options end def write_config(config_path) c = @data["network"][@name] || {} c["ipv4"] = NetAddr::CIDR.create(@ipv4).to_s(Short: true) c["ipv6"] = NetAddr::CIDR.create(@ipv6).to_s(Short: true) c["ula"] = NetAddr::CIDR.create(@ula).to_s(Short: true) c["group"] = @options[:group] if @options[:group] c["vars"] = @options[:vars] if @options[:vars] opts = @options.merge(name: @name, ipv4: format_address(@ipv4, @ipv4_subnet.to_i(:netmask)), ipv6: format_address(@ipv6, @ipv6_subnet.to_i(:netmask)), ula: format_address(@ula, @ula_subnet.to_i(:netmask))) config_dir = File.dirname(config_path) local_conf = File.join(config_dir, "local.conf") unless File.exists?(local_conf) FileUtils.touch(local_conf) end opts[:local_conf] = local_conf opts[:global_conf] = @data["zone"]["lxc-config"] fstab = @container_root.join("fstab") opts[:fstab] = fstab if File.exists?(fstab) opts[:rootfs] = @data["zone"]["shared_rootfs"] || opts[:rootfs] || @container_root.join("rootfs") templ = Template.new(CONFIG_ROOT.join("hooks", "templates", "config.erb")) templ.write(config_path, opts) end private def format_address(address, netmask) NetAddr::CIDR.create(address, Mask: netmask).desc(IP: true, Short: true) end def collect_subnets(network, type) addrs = [] network.each do |k,v| if v[type] addrs << NetAddr::CIDR.create(v[type]) end end addrs end def find_address(subnet, assigned_subnets) subnet.enumerate(Limit: 1E4, Short: true)[1..1E4].each do |cidr| assigned = assigned_subnets.find { |s| s.contains?(cidr) || s == cidr } return cidr unless assigned end end end end