diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit index df637bbc5064..f268f9b0f52c 100755 --- a/libexec/nuageinit/nuageinit +++ b/libexec/nuageinit/nuageinit @@ -1,305 +1,314 @@ #!/usr/libexec/flua -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- Copyright(c) 2022 Baptiste Daroussin local nuage = require("nuage") local yaml = require("yaml") if #arg ~= 2 then nuage.err("Usage ".. arg[0] .." [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("") 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") +local ud = nil +local f = nil +userdatas = { "user-data", "user_data" } +for _,v in pairs(userdatas) do + f = io.open(path..'/' .. v, "r") + if f then + ud = v + break + end +end 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") + f = io.open(path.."/" .. ud) local obj = yaml.eval(f:read("*a")) f:close() if not obj then - nuage.err("nuageinit: error parsing cloud-config file: user-data") + nuage.err("nuageinit: error parsing cloud-config file: " .. ud) 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') + local res,err = os.execute(path..'/' .. ud) if not res then nuage.err("nuageinit: error executing user-data script: ".. err) end end diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 42c95e1b2abd..54c88ceb539a 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -1,338 +1,382 @@ atf_test_case args atf_test_case nocloud atf_test_case nocloud_userdata_script +atf_test_case nocloud_user_data_script atf_test_case nocloud_userdata_cloudconfig atf_test_case nocloud_userdata_cloudconfig_users atf_test_case nocloud_network atf_test_case config2 atf_test_case config2_pubkeys +atf_test_case config2_pubkeys_user_data atf_test_case config2_network atf_test_case config2_network_static_v4 args_body() { atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit [config-2|nocloud]\n" /usr/libexec/nuageinit atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit [config-2|nocloud]\n" /usr/libexec/nuageinit bla atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit [config-2|nocloud]\n" /usr/libexec/nuageinit bla meh plop atf_check -s exit:1 -e inline:"Unknown cloud init type: meh\n" /usr/libexec/nuageinit bla meh } nocloud_body() { here=$(pwd) mkdir -p media/nuageinit atf_check -s exit:1 -e match:"nuageinit: error parsing nocloud.*" /usr/libexec/nuageinit ${here}/media/nuageinit/ nocloud export NUAGE_FAKE_ROOTDIR=$(pwd) printf "instance-id: iid-local01\nlocal-hostname: cloudimg\n" > ${here}/media/nuageinit/meta-data atf_check -s exit:0 /usr/libexec/nuageinit ${here}/media/nuageinit nocloud atf_check -o inline:"hostname=\"cloudimg\"\n" cat etc/rc.conf.d/hostname cat > media/nuageinit/meta-data << EOF instance-id: iid-local01 hostname: myhost EOF atf_check -s exit:0 /usr/libexec/nuageinit ${here}/media/nuageinit nocloud atf_check -o inline:"hostname=\"myhost\"\n" cat etc/rc.conf.d/hostname } nocloud_userdata_script_body() { here=$(pwd) mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data printf "#!/bin/sh\necho "yeah"\n" > ${here}/media/nuageinit/user-data chmod 755 ${here}/media/nuageinit/user-data atf_check -s exit:0 -o inline:"yeah\n" /usr/libexec/nuageinit ${here}/media/nuageinit nocloud } +nocloud_user_data_script_body() +{ + here=$(pwd) + mkdir -p media/nuageinit + printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data + printf "#!/bin/sh\necho "yeah"\n" > ${here}/media/nuageinit/user_data + chmod 755 ${here}/media/nuageinit/user_data + atf_check -s exit:0 -o inline:"yeah\n" /usr/libexec/nuageinit ${here}/media/nuageinit nocloud +} + nocloud_userdata_cloudconfig_users_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data mkdir -p etc cat > etc/master.passwd < etc/group < media/nuageinit/user-data < expectedgroup << EOF wheel:*:0:root,freebsd users:*:1:foobar admingroup:*:1001:root,sys cloud-users:*:1002: freebsd:*:1003: foobar:*:1004: EOF cat > expectedpasswd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh freebsd:freebsd:1001:1003::0:0:FreeBSD User:/home/freebsd:/bin/sh foobar:H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1002:1004::0:0:Foo B. Bar:/home/foobar:/bin/sh EOF atf_check -o file:expectedpasswd cat ${here}/etc/master.passwd atf_check -o file:expectedgroup cat ${here}/etc/group } nocloud_network_body() { here=$(pwd) mkdir -p media/nuageinit mkdir -p etc cat > etc/master.passwd < etc/group < ${here}/media/nuageinit/meta-data cat > media/nuageinit/user-data < network < routing < media/nuageinit/meta_data.json atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 cat > media/nuageinit/meta_data.json << EOF { "hostname": "cloudimg", } EOF export NUAGE_FAKE_ROOTDIR=$(pwd) atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 atf_check -o inline:"hostname=\"cloudimg\"\n" cat etc/rc.conf.d/hostname } config2_pubkeys_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit touch media/nuageinit/meta_data.json cat > media/nuageinit/user-data << EOF #cloud-config ssh_authorized_keys: - "ssh-rsa AAAAB3NzaC1y...== Generated by Nova" EOF mkdir -p etc cat > etc/master.passwd < etc/group < media/nuageinit/user_data << EOF +#cloud-config + +ssh_authorized_keys: + - "ssh-rsa AAAAB3NzaC1y...== Generated by Nova" +EOF + mkdir -p etc + cat > etc/master.passwd < etc/group < media/nuageinit/meta_data.json mynetworks=$(ifconfig -l ether) if [ -z "$mynetworks" ]; then atf_skip "a network interface is needed" fi set -- $mynetworks myiface=$1 myaddr=$(ifconfig $myiface ether | awk '/ether/ { print $2 }') cat > media/nuageinit/network_data.json < network < routing < media/nuageinit/meta_data.json mynetworks=$(ifconfig -l ether) if [ -z "$mynetworks" ]; then atf_skip "a network interface is needed" fi set -- $mynetworks myiface=$1 myaddr=$(ifconfig $myiface ether | awk '/ether/ { print $2 }') cat > media/nuageinit/network_data.json < network < routing <