freebsd-src/libexec/nuageinit/nuageinit
2024-05-21 16:52:42 +02:00

306 lines
8.4 KiB
Plaintext
Executable file

#!/usr/libexec/flua
-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
--
-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
local nuage = require("nuage")
local yaml = require("yaml")
if #arg ~= 2 then
nuage.err("Usage ".. arg[0] .." <cloud-init directory> [config-2|nocloud]")
end
local path = arg[1]
local citype = arg[2]
local ucl = require("ucl")
local default_user = {
name = "freebsd",
homedir = "/home/freebsd",
groups = "wheel",
gecos = "FreeBSD User",
shell = "/bin/sh",
plain_text_passwd = "freebsd"
}
local root = os.getenv("NUAGE_FAKE_ROOTDIR")
if not root then
root = ""
end
local function open_config(name)
nuage.mkdir_p(root .. "/etc/rc.conf.d")
local f,err = io.open(root .. "/etc/rc.conf.d/" .. name, "w")
if not f then
nuage.err("nuageinit: unable to open "..name.." config: " .. err)
end
return f
end
local function get_ifaces()
local parser = ucl.parser()
-- grab ifaces
local ns = io.popen('netstat -i --libxo json')
local netres = ns:read("*a")
ns:close()
local res,err = parser:parse_string(netres)
if not res then
nuage.warn("Error parsing netstat -i --libxo json outout: " .. err)
return nil
end
local ifaces = parser:get_object()
local myifaces = {}
for _,iface in pairs(ifaces["statistics"]["interface"]) do
if iface["network"]:match("<Link#%d>") then
local s = iface["address"]
myifaces[s:lower()] = iface["name"]
end
end
return myifaces
end
local function config2_network(p)
local parser = ucl.parser()
local f = io.open(p .. "/network_data.json")
if not f then
-- silently return no network configuration is provided
return
end
f:close()
local res,err = parser:parse_file(p .. "/network_data.json")
if not res then
nuage.warn("nuageinit: error parsing network_data.json: " .. err)
return
end
local obj = parser:get_object()
local ifaces = get_ifaces()
if not ifaces then
nuage.warn("nuageinit: no network interfaces found")
return
end
local mylinks = {}
for _,v in pairs(obj["links"]) do
local s = v["ethernet_mac_address"]:lower()
mylinks[v["id"]] = ifaces[s]
end
nuage.mkdir_p(root .. "/etc/rc.conf.d")
local network = open_config("network")
local routing = open_config("routing")
local ipv6 = {}
local ipv6_routes = {}
local ipv4 = {}
for _,v in pairs(obj["networks"]) do
local interface = mylinks[v["link"]]
if v["type"] == "ipv4_dhcp" then
network:write("ifconfig_"..interface.."=\"DHCP\"\n")
end
if v["type"] == "ipv4" then
network:write("ifconfig_"..interface.."=\"inet "..v["ip_address"].." netmask " .. v["netmask"] .. "\"\n")
if v["gateway"] then
routing:write("defaultrouter=\""..v["gateway"].."\"\n")
end
if v["routes"] then
for i,r in ipairs(v["routes"]) do
local rname = "cloudinit" .. i .. "_" .. interface
if v["gateway"] and v["gateway"] == r["gateway"] then goto next end
if r["network"] == "0.0.0.0" then
routing:write("defaultrouter=\""..r["gateway"].."\"\n")
goto next
end
routing:write("route_".. rname .. "=\"-net ".. r["network"] .. " ")
routing:write(r["gateway"] .. " " .. r["netmask"] .. "\"\n")
ipv4[#ipv4 + 1] = rname
::next::
end
end
end
if v["type"] == "ipv6" then
ipv6[#ipv6+1] = interface
ipv6_routes[#ipv6_routes+1] = interface
network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..v["ip_address"].."\"\n")
if v["gateway"] then
routing:write("ipv6_defaultrouter=\""..v["gateway"].."\"\n")
routing:write("ipv6_route_"..interface.."=\""..v["gateway"])
routing:write(" -prefixlen 128 -interface "..interface.."\"\n")
end
-- TODO compute the prefixlen for the routes
--if v["routes"] then
-- for i,r in ipairs(v["routes"]) do
-- local rname = "cloudinit" .. i .. "_" .. mylinks[v["link"]]
-- -- skip all the routes which are already covered by the default gateway, some provider
-- -- still list plenty of them.
-- if v["gateway"] == r["gateway"] then goto next end
-- routing:write("ipv6_route_" .. rname .. "\"\n")
-- ipv6_routes[#ipv6_routes+1] = rname
-- ::next::
-- end
--end
end
end
if #ipv4 > 0 then
routing:write("static_routes=\"")
routing:write(table.concat(ipv4, " ") .. "\"\n")
end
if #ipv6 > 0 then
network:write("ipv6_network_interfaces=\"")
network:write(table.concat(ipv6, " ") .. "\"\n")
network:write("ipv6_default_interface=\""..ipv6[1].."\"\n")
end
if #ipv6_routes > 0 then
routing:write("ipv6_static_routes=\"")
routing:write(table.concat(ipv6, " ") .. "\"\n")
end
network:close()
routing:close()
end
if citype == "config-2" then
local parser = ucl.parser()
local res,err = parser:parse_file(path..'/meta_data.json')
if not res then
nuage.err("nuageinit: error parsing config-2: meta_data.json: " .. err)
end
local obj = parser:get_object()
nuage.sethostname(obj["hostname"])
-- network
config2_network(path)
elseif citype == "nocloud" then
local f,err = io.open(path.."/meta-data")
if err then
nuage.err("nuageinit: error parsing nocloud meta-data: ".. err)
end
local obj = yaml.eval(f:read("*a"))
f:close()
if not obj then
nuage.err("nuageinit: error parsing nocloud meta-data")
end
local hostname = obj['local-hostname']
if not hostname then
hostname = obj['hostname']
end
if hostname then
nuage.sethostname(hostname)
end
else
nuage.err("Unknown cloud init type: ".. citype)
end
-- deal with user-data
local f = io.open(path..'/user-data', "r")
if not f then
os.exit(0)
end
local line = f:read('*l')
f:close()
if line == "#cloud-config" then
f = io.open(path.."/user-data")
local obj = yaml.eval(f:read("*a"))
f:close()
if not obj then
nuage.err("nuageinit: error parsing cloud-config file: user-data")
end
if obj.groups then
for n,g in pairs(obj.groups) do
if (type(g) == "string") then
local r = nuage.addgroup({name = g})
if not r then
nuage.warn("nuageinit: failed to add group: ".. g)
end
elseif type(g) == "table" then
for k,v in pairs(g) do
nuage.addgroup({name = k, members = v})
end
else
nuage.warn("nuageinit: invalid type : "..type(g).." for users entry number "..n);
end
end
end
if obj.users then
for n,u in pairs(obj.users) do
if type(u) == "string" then
if u == "default" then
nuage.adduser(default_user)
else
nuage.adduser({name = u})
end
elseif type(u) == "table" then
-- ignore users without a username
if u.name == nil then
goto unext
end
local homedir = nuage.adduser(u)
if u.ssh_authorized_keys then
for _,v in ipairs(u.ssh_authorized_keys) do
nuage.addsshkey(homedir, v)
end
end
else
nuage.warn("nuageinit: invalid type : "..type(u).." for users entry number "..n);
end
::unext::
end
else
-- default user if none are defined
nuage.adduser(default_user)
end
if obj.ssh_authorized_keys then
local homedir = nuage.adduser(default_user)
for _,k in ipairs(obj.ssh_authorized_keys) do
nuage.addsshkey(homedir, k)
end
end
if obj.network then
local ifaces = get_ifaces()
nuage.mkdir_p(root .. "/etc/rc.conf.d")
local network = open_config("network")
local routing = open_config("routing")
local ipv6={}
for _,v in pairs(obj.network.ethernets) do
if not v.match then goto next end
if not v.match.macaddress then goto next end
if not ifaces[v.match.macaddress] then
nuage.warn("nuageinit: not interface matching: "..v.match.macaddress)
goto next
end
local interface = ifaces[v.match.macaddress]
if v.dhcp4 then
network:write("ifconfig_"..interface.."=\"DHCP\"\n")
elseif v.addresses then
for _,a in pairs(v.addresses) do
if a:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)") then
network:write("ifconfig_"..interface.."=\"inet "..a.."\"\n")
else
network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..a.."\"\n")
ipv6[#ipv6 +1] = interface
end
end
end
if v.gateway4 then
routing:write("defaultrouter=\""..v.gateway4.."\"\n")
end
if v.gateway6 then
routing:write("ipv6_defaultrouter=\""..v.gateway6.."\"\n")
routing:write("ipv6_route_"..interface.."=\""..v.gateway6)
routing:write(" -prefixlen 128 -interface "..interface.."\"\n")
end
::next::
end
if #ipv6 > 0 then
network:write("ipv6_network_interfaces=\"")
network:write(table.concat(ipv6, " ") .. "\"\n")
network:write("ipv6_default_interface=\""..ipv6[1].."\"\n")
end
network:close()
routing:close()
end
else
local res,err = os.execute(path..'/user-data')
if not res then
nuage.err("nuageinit: error executing user-data script: ".. err)
end
end