3 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
5 -- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
7 local nuage = require("nuage")
8 local yaml = require("yaml")
11 nuage.err("Usage ".. arg[0] .." <cloud-init directory> [config-2|nocloud]")
15 local ucl = require("ucl")
17 local default_user = {
19 homedir = "/home/freebsd",
21 gecos = "FreeBSD User",
23 plain_text_passwd = "freebsd"
26 local root = os.getenv("NUAGE_FAKE_ROOTDIR")
31 local function open_config(name)
32 nuage.mkdir_p(root .. "/etc/rc.conf.d")
33 local f,err = io.open(root .. "/etc/rc.conf.d/" .. name, "w")
35 nuage.err("nuageinit: unable to open "..name.." config: " .. err)
40 local function get_ifaces()
41 local parser = ucl.parser()
43 local ns = io.popen('netstat -i --libxo json')
44 local netres = ns:read("*a")
46 local res,err = parser:parse_string(netres)
48 nuage.warn("Error parsing netstat -i --libxo json outout: " .. err)
51 local ifaces = parser:get_object()
53 for _,iface in pairs(ifaces["statistics"]["interface"]) do
54 if iface["network"]:match("<Link#%d>") then
55 local s = iface["address"]
56 myifaces[s:lower()] = iface["name"]
62 local function config2_network(p)
63 local parser = ucl.parser()
64 local f = io.open(p .. "/network_data.json")
66 -- silently return no network configuration is provided
70 local res,err = parser:parse_file(p .. "/network_data.json")
72 nuage.warn("nuageinit: error parsing network_data.json: " .. err)
75 local obj = parser:get_object()
77 local ifaces = get_ifaces()
79 nuage.warn("nuageinit: no network interfaces found")
83 for _,v in pairs(obj["links"]) do
84 local s = v["ethernet_mac_address"]:lower()
85 mylinks[v["id"]] = ifaces[s]
88 nuage.mkdir_p(root .. "/etc/rc.conf.d")
89 local network = open_config("network")
90 local routing = open_config("routing")
92 local ipv6_routes = {}
94 for _,v in pairs(obj["networks"]) do
95 local interface = mylinks[v["link"]]
96 if v["type"] == "ipv4_dhcp" then
97 network:write("ifconfig_"..interface.."=\"DHCP\"\n")
99 if v["type"] == "ipv4" then
100 network:write("ifconfig_"..interface.."=\"inet "..v["ip_address"].." netmask " .. v["netmask"] .. "\"\n")
102 routing:write("defaultrouter=\""..v["gateway"].."\"\n")
105 for i,r in ipairs(v["routes"]) do
106 local rname = "cloudinit" .. i .. "_" .. interface
107 if v["gateway"] and v["gateway"] == r["gateway"] then goto next end
108 if r["network"] == "0.0.0.0" then
109 routing:write("defaultrouter=\""..r["gateway"].."\"\n")
112 routing:write("route_".. rname .. "=\"-net ".. r["network"] .. " ")
113 routing:write(r["gateway"] .. " " .. r["netmask"] .. "\"\n")
114 ipv4[#ipv4 + 1] = rname
119 if v["type"] == "ipv6" then
120 ipv6[#ipv6+1] = interface
121 ipv6_routes[#ipv6_routes+1] = interface
122 network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..v["ip_address"].."\"\n")
124 routing:write("ipv6_defaultrouter=\""..v["gateway"].."\"\n")
125 routing:write("ipv6_route_"..interface.."=\""..v["gateway"])
126 routing:write(" -prefixlen 128 -interface "..interface.."\"\n")
128 -- TODO compute the prefixlen for the routes
129 --if v["routes"] then
130 -- for i,r in ipairs(v["routes"]) do
131 -- local rname = "cloudinit" .. i .. "_" .. mylinks[v["link"]]
132 -- -- skip all the routes which are already covered by the default gateway, some provider
133 -- -- still list plenty of them.
134 -- if v["gateway"] == r["gateway"] then goto next end
135 -- routing:write("ipv6_route_" .. rname .. "\"\n")
136 -- ipv6_routes[#ipv6_routes+1] = rname
143 routing:write("static_routes=\"")
144 routing:write(table.concat(ipv4, " ") .. "\"\n")
147 network:write("ipv6_network_interfaces=\"")
148 network:write(table.concat(ipv6, " ") .. "\"\n")
149 network:write("ipv6_default_interface=\""..ipv6[1].."\"\n")
151 if #ipv6_routes > 0 then
152 routing:write("ipv6_static_routes=\"")
153 routing:write(table.concat(ipv6, " ") .. "\"\n")
159 if citype == "config-2" then
160 local parser = ucl.parser()
161 local res,err = parser:parse_file(path..'/meta_data.json')
164 nuage.err("nuageinit: error parsing config-2: meta_data.json: " .. err)
166 local obj = parser:get_object()
167 local sshkeys = obj["public_keys"]
169 local homedir = nuage.adduser(default_user)
170 for _,v in pairs(sshkeys) do
171 nuage.addsshkey(root .. homedir, v)
174 nuage.sethostname(obj["hostname"])
177 config2_network(path)
178 elseif citype == "nocloud" then
179 local f,err = io.open(path.."/meta-data")
181 nuage.err("nuageinit: error parsing nocloud meta-data: ".. err)
183 local obj = yaml.eval(f:read("*a"))
186 nuage.err("nuageinit: error parsing nocloud meta-data")
188 local hostname = obj['local-hostname']
190 hostname = obj['hostname']
193 nuage.sethostname(hostname)
196 nuage.err("Unknown cloud init type: ".. citype)
199 -- deal with user-data
200 local f = io.open(path..'/user-data', "r")
204 local line = f:read('*l')
206 if line == "#cloud-config" then
207 f = io.open(path.."/user-data")
208 local obj = yaml.eval(f:read("*a"))
211 nuage.err("nuageinit: error parsing cloud-config file: user-data")
214 for n,g in pairs(obj.groups) do
215 if (type(g) == "string") then
216 local r = nuage.addgroup({name = g})
218 nuage.warn("nuageinit: failed to add group: ".. g)
220 elseif type(g) == "table" then
221 for k,v in pairs(g) do
222 nuage.addgroup({name = k, members = v})
225 nuage.warn("nuageinit: invalid type : "..type(g).." for users entry number "..n);
230 for n,u in pairs(obj.users) do
231 if type(u) == "string" then
232 if u == "default" then
233 nuage.adduser(default_user)
235 nuage.adduser({name = u})
237 elseif type(u) == "table" then
238 -- ignore users without a username
239 if u.name == nil then
242 local homedir = nuage.adduser(u)
243 if u.ssh_authorized_keys then
244 for _,v in ipairs(u.ssh_authorized_keys) do
245 nuage.addsshkey(homedir, v)
249 nuage.warn("nuageinit: invalid type : "..type(u).." for users entry number "..n);
254 -- default user if none are defined
255 nuage.adduser(default_user)
257 if obj.ssh_authorized_keys then
258 local homedir = nuage.adduser(default_user)
259 for _,k in ipairs(obj.ssh_authorized_keys) do
260 nuage.addsshkey(homedir, k)
264 local ifaces = get_ifaces()
265 nuage.mkdir_p(root .. "/etc/rc.conf.d")
266 local network = open_config("network")
267 local routing = open_config("routing")
269 for _,v in pairs(obj.network.ethernets) do
270 if not v.match then goto next end
271 if not v.match.macaddress then goto next end
272 if not ifaces[v.match.macaddress] then
273 nuage.warn("nuageinit: not interface matching: "..v.match.macaddress)
276 local interface = ifaces[v.match.macaddress]
278 network:write("ifconfig_"..interface.."=\"DHCP\"\n")
279 elseif v.addresses then
280 for _,a in pairs(v.addresses) do
281 if a:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)") then
282 network:write("ifconfig_"..interface.."=\"inet "..a.."\"\n")
284 network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..a.."\"\n")
285 ipv6[#ipv6 +1] = interface
290 routing:write("defaultrouter=\""..v.gateway4.."\"\n")
293 routing:write("ipv6_defaultrouter=\""..v.gateway6.."\"\n")
294 routing:write("ipv6_route_"..interface.."=\""..v.gateway6)
295 routing:write(" -prefixlen 128 -interface "..interface.."\"\n")
300 network:write("ipv6_network_interfaces=\"")
301 network:write(table.concat(ipv6, " ") .. "\"\n")
302 network:write("ipv6_default_interface=\""..ipv6[1].."\"\n")
308 local res,err = os.execute(path..'/user-data')
310 nuage.err("nuageinit: error executing user-data script: ".. err)