nuageinit: add basic support for cloudinit.

this is a very early script to support cloudinit, it does not intend to
be a full featured cloudinit client, but will support a good enough
subset to be viable in most case.

It support nocloud and openstack config-2 config drive mode (iso9660 or
msdosfs)

The following features are currently supported:
- adding users (including a default user named 'freebsd' with password
  'freebsd'
- adding groups
- adding ssh keys
- static ipv4, static ipv6, dynamic ipv4

With this one is able to use the 'bring your own image feature" out of
box.

It is expected that the script grows the support of other clouds
supporting cloud-init, contributions are welcomed.

It is designed to be only run once via the firstboot mecanism.

Sponsored by:	OVHCloud
MFC After:	3 weeks
Differential Revision:	https://reviews.freebsd.org/D44141
This commit is contained in:
Baptiste Daroussin 2022-11-23 20:00:39 +01:00
parent 3705d679a6
commit a42d6f7601
21 changed files with 1700 additions and 0 deletions

View file

@ -27,6 +27,7 @@ SUBDIR= ${_atf} \
${_rshd} \
${_rtld-elf} \
save-entropy \
${_nuageinit} \
${_smrsh} \
${_tests} \
${_tftp-proxy} \
@ -119,6 +120,10 @@ _atf= atf
_tests= tests
.endif
.if ${MK_NUAGEINIT} != "no"
_nuageinit= nuageinit
.endif
.include <bsd.arch.inc.mk>
.include <bsd.subdir.mk>

View file

@ -0,0 +1,11 @@
PACKAGE= nuageinit
SCRIPTS= nuageinit
FILES= nuage.lua yaml.lua
FILESDIR= ${SHAREDIR}/flua
.include <src.opts.mk>
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
.include <bsd.prog.mk>

214
libexec/nuageinit/nuage.lua Normal file
View file

@ -0,0 +1,214 @@
-- SPDX-License-Identifier: BSD-2-Clause
--
-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
local pu = require("posix.unistd")
local function warnmsg(str)
io.stderr:write(str.."\n")
end
local function errmsg(str)
io.stderr:write(str.."\n")
os.exit(1)
end
local function dirname(oldpath)
if not oldpath then
return nil
end
local path = oldpath:gsub("[^/]+/*$", "")
if path == "" then
return nil
end
return path
end
local function mkdir_p(path)
if lfs.attributes(path, "mode") ~= nil then
return true
end
local r,err = mkdir_p(dirname(path))
if not r then
return nil,err.." (creating "..path..")"
end
return lfs.mkdir(path)
end
local function sethostname(hostname)
if hostname == nil then return end
local root = os.getenv("NUAGE_FAKE_ROOTDIR")
if not root then
root = ""
end
local hostnamepath = root .. "/etc/rc.conf.d/hostname"
mkdir_p(dirname(hostnamepath))
local f,err = io.open(hostnamepath, "w")
if not f then
warnmsg("Impossible to open "..hostnamepath .. ":" ..err)
return
end
f:write("hostname=\""..hostname.."\"\n")
f:close()
end
local function splitlist(list)
local ret = {}
if type(list) == "string" then
for str in list:gmatch("([^, ]+)") do
ret[#ret + 1] = str
end
elseif type(list) == "table" then
ret = list
else
warnmsg("Invalid type ".. type(list) ..", expecting table or string")
end
return ret
end
local function adduser(pwd)
if (type(pwd) ~= "table") then
warnmsg("Argument should be a table")
return nil
end
local f = io.popen("getent passwd "..pwd.name)
local pwdstr = f:read("*a")
f:close()
if pwdstr:len() ~= 0 then
return pwdstr:match("%a+:.+:%d+:%d+:.*:(.*):.*")
end
if not pwd.gecos then
pwd.gecos = pwd.name .. " User"
end
if not pwd.home then
pwd.home = "/home/" .. pwd.name
end
local extraargs=""
if pwd.groups then
local list = splitlist(pwd.groups)
extraargs = " -G ".. table.concat(list, ',')
end
-- pw will automatically create a group named after the username
-- do not add a -g option in this case
if pwd.primary_group and pwd.primary_group ~= pwd.name then
extraargs = extraargs .. " -g " .. pwd.primary_group
end
if not pwd.no_create_home then
extraargs = extraargs .. " -m "
end
if not pwd.shell then
pwd.shell = "/bin/sh"
end
local precmd = ""
local postcmd = ""
if pwd.passwd then
precmd = "echo "..pwd.passwd .. "| "
postcmd = " -H 0 "
elseif pwd.plain_text_passwd then
precmd = "echo "..pwd.plain_text_passwd .. "| "
postcmd = " -H 0 "
end
local root = os.getenv("NUAGE_FAKE_ROOTDIR")
local cmd = precmd .. "pw "
if root then
cmd = cmd .. "-R " .. root .. " "
end
cmd = cmd .. "useradd -n ".. pwd.name .. " -M 0755 -w none "
cmd = cmd .. extraargs .. " -c '".. pwd.gecos
cmd = cmd .. "' -d '" .. pwd.home .. "' -s "..pwd.shell .. postcmd
local r = os.execute(cmd)
if not r then
warnmsg("nuageinit: fail to add user "..pwd.name);
warnmsg(cmd)
return nil
end
if pwd.locked then
cmd = "pw "
if root then
cmd = cmd .. "-R " .. root .. " "
end
cmd = cmd .. "lock " .. pwd.name
os.execute(cmd)
end
return pwd.home
end
local function addgroup(grp)
if (type(grp) ~= "table") then
warnmsg("Argument should be a table")
return false
end
local f = io.popen("getent group "..grp.name)
local grpstr = f:read("*a")
f:close()
if grpstr:len() ~= 0 then
return true
end
local extraargs = ""
if grp.members then
local list = splitlist(grp.members)
extraargs = " -M " .. table.concat(list, ',')
end
local root = os.getenv("NUAGE_FAKE_ROOTDIR")
local cmd = "pw "
if root then
cmd = cmd .. "-R " .. root .. " "
end
cmd = cmd .. "groupadd -n ".. grp.name .. extraargs
local r = os.execute(cmd)
if not r then
warnmsg("nuageinit: fail to add group ".. grp.name);
warnmsg(cmd)
return false
end
return true
end
local function addsshkey(homedir, key)
local chownak = false
local chowndotssh = false
local ak_path = homedir .. "/.ssh/authorized_keys"
local dotssh_path = homedir .. "/.ssh"
local dirattrs = lfs.attributes(ak_path)
if dirattrs == nil then
chownak = true
dirattrs = lfs.attributes(dotssh_path)
if dirattrs == nil then
if not lfs.mkdir(dotssh_path) then
warnmsg("nuageinit: impossible to create ".. dotssh_path)
return
end
chowndotssh = true
dirattrs = lfs.attributes(homedir)
end
end
local f = io.open(ak_path, "a")
if not f then
warnmsg("nuageinit: impossible to open "..ak_path)
return
end
f:write(key .. "\n")
f:close()
if chownak then
pu.chown(ak_path, dirattrs.uid, dirattrs.gid)
end
if chowndotssh then
pu.chown(dotssh_path, dirattrs.uid, dirattrs.gid)
end
end
local n = {
warn = warnmsg,
err = errmsg,
sethostname = sethostname,
adduser = adduser,
addgroup = addgroup,
addsshkey = addsshkey,
dirname = dirname,
mkdir_p = mkdir_p,
}
return n

312
libexec/nuageinit/nuageinit Executable file
View file

@ -0,0 +1,312 @@
#!/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()
local sshkeys = obj["public_keys"]
if sshkeys then
local homedir = nuage.adduser(default_user)
for _,v in pairs(sshkeys) do
nuage.addsshkey(root .. homedir, v)
end
end
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

View file

@ -0,0 +1,13 @@
PACKAGE= tests
ATF_TESTS_SH= nuage utils nuageinit
${PACKAGE}FILES+= warn.lua
${PACKAGE}FILES+= err.lua
${PACKAGE}FILES+= dirname.lua
${PACKAGE}FILES+= sethostname.lua
${PACKAGE}FILES+= addsshkey.lua
${PACKAGE}FILES+= adduser.lua
${PACKAGE}FILES+= addgroup.lua
.include <bsd.test.mk>

View file

@ -0,0 +1,15 @@
#!/usr/libexec/flua
local n = require("nuage")
if n.addgroup() then
n.err("addgroup should not accept empty value")
end
if n.addgroup("plop") then
n.err("addgroup should not accept empty value")
end
local gr = {}
gr.name = "impossible_groupname"
local res = n.addgroup(gr)
if not res then
n.err("valid addgroup should return a path")
end

View file

@ -0,0 +1,2 @@
local n = require("nuage")
n.addsshkey(".", "mykey")

View file

@ -0,0 +1,15 @@
#!/usr/libexec/flua
local n = require("nuage")
if n.adduser() then
n.err("adduser should not accept empty value")
end
if n.adduser("plop") then
n.err("adduser should not accept empty value")
end
local pw = {}
pw.name = "impossible_username"
local res = n.adduser(pw)
if not res then
n.err("valid adduser should return a path")
end

View file

@ -0,0 +1,8 @@
local n = require("nuage")
print(n.dirname("/my/path/path1"))
if n.dirname("path") then
nuage.err("Expecting nil for n.dirname(\"path\")")
end
if n.dirname() then
nuage.err("Expecting nil for n.dirname")
end

View file

@ -0,0 +1,4 @@
#!/usr/libexec/flua
local n = require("nuage")
n.err("plop")

View file

@ -0,0 +1,52 @@
atf_test_case sethostname
atf_test_case addsshkey
atf_test_case adduser
atf_test_case addgroup
sethostname_body() {
export NUAGE_FAKE_ROOTDIR="$(pwd)"
atf_check /usr/libexec/flua $(atf_get_srcdir)/sethostname.lua
if [ ! -f etc/rc.conf.d/hostname ]; then
atf_fail "hostname not written"
fi
atf_check -o inline:"hostname=\"myhostname\"\n" cat etc/rc.conf.d/hostname
}
addsshkey_body() {
atf_check /usr/libexec/flua $(atf_get_srcdir)/addsshkey.lua
if [ ! -f .ssh/authorized_keys ]; then
atf_fail "ssh key not added"
fi
atf_check -o inline:"mykey\n" cat .ssh/authorized_keys
atf_check /usr/libexec/flua $(atf_get_srcdir)/addsshkey.lua
atf_check -o inline:"mykey\nmykey\n" cat .ssh/authorized_keys
}
adduser_body() {
export NUAGE_FAKE_ROOTDIR="$(pwd)"
if [ $(id -u) -ne 0 ]; then
atf_skip "root required"
fi
mkdir etc
printf "root:*:0:0::0:0:Charlie &:/root:/bin/csh\n" > etc/master.passwd
pwd_mkdb -d etc etc/master.passwd
printf "wheel:*:0:root\n" > etc/group
atf_check -e inline:"Argument should be a table\nArgument should be a table\n" /usr/libexec/flua $(atf_get_srcdir)/adduser.lua
test -d home/impossible_username || atf_fail "home not created"
atf_check -o inline:"impossible_username::1001:1001::0:0:impossible_username User:/home/impossible_username:/bin/sh\n" grep impossible_username etc/master.passwd
}
addgroup_body() {
export NUAGE_FAKE_ROOTDIR="$(pwd)"
mkdir etc
printf "wheel:*:0:root\n" > etc/group
atf_check -e inline:"Argument should be a table\nArgument should be a table\n" /usr/libexec/flua $(atf_get_srcdir)/addgroup.lua
atf_check -o inline:"impossible_groupname:*:1001:\n" grep impossible_groupname etc/group
}
atf_init_test_cases() {
atf_add_test_case sethostname
atf_add_test_case addsshkey
atf_add_test_case adduser
atf_add_test_case addgroup
}

View file

@ -0,0 +1,338 @@
atf_test_case args
atf_test_case nocloud
atf_test_case nocloud_userdata_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_network
atf_test_case config2_network_static_v4
args_body()
{
atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit <cloud-init directory> [config-2|nocloud]\n" /usr/libexec/nuageinit
atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit <cloud-init directory> [config-2|nocloud]\n" /usr/libexec/nuageinit bla
atf_check -s exit:1 -e inline:"Usage /usr/libexec/nuageinit <cloud-init directory> [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_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 <<EOF
root:*:0:0::0:0:Charlie &:/root:/bin/csh
sys:*:1:0::0:0:Sys:/home/sys:/bin/csh
EOF
pwd_mkdb -d etc ${here}/etc/master.passwd
cat > etc/group <<EOF
wheel:*:0:root
users:*:1:
EOF
cat > media/nuageinit/user-data <<EOF
#cloud-config
groups:
- admingroup: [root,sys]
- cloud-users
users:
- default
- name: foobar
gecos: Foo B. Bar
primary_group: foobar
groups: users
passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/
EOF
atf_check /usr/libexec/nuageinit ${here}/media/nuageinit nocloud
cat > 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 <<EOF
root:*:0:0::0:0:Charlie &:/root:/bin/csh
sys:*:1:0::0:0:Sys:/home/sys:/bin/csh
EOF
pwd_mkdb -d etc ${here}/etc/master.passwd
cat > etc/group <<EOF
wheel:*:0:root
users:*:1:
EOF
if [ $(id -u) -ne 0 ]; then
atf_skip "root required"
fi
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 }')
printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data
cat > media/nuageinit/user-data <<EOF
#cloud-config
#
network:
version: 2
ethernets:
# opaque ID for physical interfaces, only referred to by other stanzas
id0:
match:
macaddress: '${myaddr}'
addresses:
- 192.168.14.2/24
- 2001:1::1/64
gateway4: 192.168.14.1
gateway6: 2001:1::2
EOF
export NUAGE_FAKE_ROOTDIR=$(pwd)
atf_check /usr/libexec/nuageinit ${here}/media/nuageinit nocloud
cat > network <<EOF
ifconfig_${myiface}="inet 192.168.14.2/24"
ifconfig_${myiface}_ipv6="inet6 2001:1::1/64"
ipv6_network_interfaces="${myiface}"
ipv6_default_interface="${myiface}"
EOF
cat > routing <<EOF
defaultrouter="192.168.14.1"
ipv6_defaultrouter="2001:1::2"
ipv6_route_${myiface}="2001:1::2 -prefixlen 128 -interface ${myiface}"
EOF
atf_check -o file:network cat ${here}/etc/rc.conf.d/network
atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing
}
config2_body()
{
here=$(pwd)
mkdir -p media/nuageinit
atf_check -s exit:1 -e match:"nuageinit: error parsing config-2: meta_data.json.*" /usr/libexec/nuageinit ${here}/media/nuageinit config-2
printf "{}" > 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)
if [ $(id -u) -ne 0 ]; then
atf_skip "root required"
fi
mkdir -p media/nuageinit
cat > media/nuageinit/meta_data.json << EOF
{
"public_keys": {
"mykey": "ssh-rsa AAAAB3NzaC1y...== Generated by Nova"
},
}
EOF
mkdir -p etc
cat > etc/master.passwd <<EOF
root:*:0:0::0:0:Charlie &:/root:/bin/csh
sys:*:1:0::0:0:Sys:/home/sys:/bin/csh
EOF
pwd_mkdb -d etc ${here}/etc/master.passwd
cat > etc/group <<EOF
wheel:*:0:root
users:*:1:
EOF
export NUAGE_FAKE_ROOTDIR=$(pwd)
atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2
atf_check -o inline:"ssh-rsa AAAAB3NzaC1y...== Generated by Nova\n" cat home/freebsd/.ssh/authorized_keys
}
config2_network_body() {
here=$(pwd)
mkdir -p media/nuageinit
printf "{}" > 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 <<EOF
{
"links": [
{
"ethernet_mac_address": "$myaddr",
"id": "iface0",
"mtu": null,
}
],
"networks": [
{
"id": "network0",
"link": "iface0",
"type": "ipv4_dhcp"
},
{ // IPv6
"id": "private-ipv4",
"type": "ipv6",
"link": "iface0",
// supports condensed IPv6 with CIDR netmask
"ip_address": "2001:cdba::3257:9652/24",
"gateway": "fd00::1"
"routes": [
{
"network": "::",
"netmask": "::",
"gateway": "fd00::1"
},
{
"network": "::",
"netmask": "ffff:ffff:ffff::",
"gateway": "fd00::1:1"
},
],
"network_id": "da5bb487-5193-4a65-a3df-4a0055a8c0d8"
},
],
}
EOF
export NUAGE_FAKE_ROOTDIR=$(pwd)
atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2
cat > network <<EOF
ifconfig_${myiface}="DHCP"
ifconfig_${myiface}_ipv6="inet6 2001:cdba::3257:9652/24"
ipv6_network_interfaces="${myiface}"
ipv6_default_interface="${myiface}"
EOF
cat > routing <<EOF
ipv6_defaultrouter="fd00::1"
ipv6_route_${myiface}="fd00::1 -prefixlen 128 -interface ${myiface}"
ipv6_static_routes="${myiface}"
EOF
atf_check -o file:network cat ${here}/etc/rc.conf.d/network
atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing
}
config2_network_static_v4_body() {
here=$(pwd)
mkdir -p media/nuageinit
printf "{}" > 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 <<EOF
{
"links": [
{
"ethernet_mac_address": "$myaddr",
"id": "iface0",
"mtu": null,
}
],
"networks": [
{
"id": "network0",
"link": "iface0",
"type": "ipv4"
"ip_address": "10.184.0.244",
"netmask": "255.255.240.0",
"routes": [
{
"network": "10.0.0.0",
"netmask": "255.0.0.0",
"gateway": "11.0.0.1"
},
{
"network": "0.0.0.0",
"netmask": "0.0.0.0",
"gateway": "23.253.157.1"
}
]
}
]
}
EOF
export NUAGE_FAKE_ROOTDIR=$(pwd)
atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2
cat > network <<EOF
ifconfig_${myiface}="inet 10.184.0.244 netmask 255.255.240.0"
EOF
cat > routing <<EOF
route_cloudinit1_${myiface}="-net 10.0.0.0 11.0.0.1 255.0.0.0"
defaultrouter="23.253.157.1"
static_routes="cloudinit1_${myiface}"
EOF
atf_check -o file:network cat ${here}/etc/rc.conf.d/network
atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing
}
atf_init_test_cases()
{
atf_add_test_case args
atf_add_test_case nocloud
atf_add_test_case nocloud_userdata_script
atf_add_test_case nocloud_userdata_cloudconfig_users
atf_add_test_case nocloud_network
atf_add_test_case config2
atf_add_test_case config2_pubkeys
atf_add_test_case config2_network
atf_add_test_case config2_network_static_v4
}

View file

@ -0,0 +1,4 @@
#!/usr/libexec/flua
local n = require("nuage")
n.sethostname("myhostname")

View file

@ -0,0 +1,21 @@
atf_test_case warn
atf_test_case err
atf_test_case dirname
warn_body() {
atf_check -e "inline:plop\n" -s exit:0 /usr/libexec/flua $(atf_get_srcdir)/warn.lua
}
err_body() {
atf_check -e "inline:plop\n" -s exit:1 /usr/libexec/flua $(atf_get_srcdir)/err.lua
}
dirname_body() {
atf_check -o "inline:/my/path/\n" -s exit:0 /usr/libexec/flua $(atf_get_srcdir)/dirname.lua
}
atf_init_test_cases() {
atf_add_test_case warn
atf_add_test_case err
atf_add_test_case dirname
}

View file

@ -0,0 +1,4 @@
#!/usr/libexec/flua
local n = require("nuage")
n.warn("plop")

586
libexec/nuageinit/yaml.lua Normal file
View file

@ -0,0 +1,586 @@
-- SPDX-License-Identifier: MIT
--
-- Copyright (c) 2017 Dominic Letz dominicletz@exosite.com
local table_print_value
table_print_value = function(value, indent, done)
indent = indent or 0
done = done or {}
if type(value) == "table" and not done [value] then
done [value] = true
local list = {}
for key in pairs (value) do
list[#list + 1] = key
end
table.sort(list, function(a, b) return tostring(a) < tostring(b) end)
local last = list[#list]
local rep = "{\n"
local comma
for _, key in ipairs (list) do
if key == last then
comma = ''
else
comma = ','
end
local keyRep
if type(key) == "number" then
keyRep = key
else
keyRep = string.format("%q", tostring(key))
end
rep = rep .. string.format(
"%s[%s] = %s%s\n",
string.rep(" ", indent + 2),
keyRep,
table_print_value(value[key], indent + 2, done),
comma
)
end
rep = rep .. string.rep(" ", indent) -- indent it
rep = rep .. "}"
done[value] = false
return rep
elseif type(value) == "string" then
return string.format("%q", value)
else
return tostring(value)
end
end
local table_print = function(tt)
print('return '..table_print_value(tt))
end
local table_clone = function(t)
local clone = {}
for k,v in pairs(t) do
clone[k] = v
end
return clone
end
local string_trim = function(s, what)
what = what or " "
return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1")
end
local push = function(stack, item)
stack[#stack + 1] = item
end
local pop = function(stack)
local item = stack[#stack]
stack[#stack] = nil
return item
end
local context = function (str)
if type(str) ~= "string" then
return ""
end
str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\"");
return ", near \"" .. str .. "\""
end
local Parser = {}
function Parser.new (self, tokens)
self.tokens = tokens
self.parse_stack = {}
self.refs = {}
self.current = 0
return self
end
local exports = {version = "1.2"}
local word = function(w) return "^("..w..")([%s$%c])" end
local tokens = {
{"comment", "^#[^\n]*"},
{"indent", "^\n( *)"},
{"space", "^ +"},
{"true", word("enabled"), const = true, value = true},
{"true", word("true"), const = true, value = true},
{"true", word("yes"), const = true, value = true},
{"true", word("on"), const = true, value = true},
{"false", word("disabled"), const = true, value = false},
{"false", word("false"), const = true, value = false},
{"false", word("no"), const = true, value = false},
{"false", word("off"), const = true, value = false},
{"null", word("null"), const = true, value = nil},
{"null", word("Null"), const = true, value = nil},
{"null", word("NULL"), const = true, value = nil},
{"null", word("~"), const = true, value = nil},
{"id", "^\"([^\"]-)\" *(:[%s%c])"},
{"id", "^'([^']-)' *(:[%s%c])"},
{"string", "^\"([^\"]-)\"", force_text = true},
{"string", "^'([^']-)'", force_text = true},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"},
{"doc", "^%-%-%-[^%c]*"},
{",", "^,"},
{"string", "^%b{} *[^,%c]+", noinline = true},
{"{", "^{"},
{"}", "^}"},
{"string", "^%b[] *[^,%c]+", noinline = true},
{"[", "^%["},
{"]", "^%]"},
{"-", "^%-", noinline = true},
{":", "^:"},
{"pipe", "^(|)(%d*[+%-]?)", sep = "\n"},
{"pipe", "^(>)(%d*[+%-]?)", sep = " "},
{"id", "^([%w][%w %-_]*)(:[%s%c])"},
{"string", "^[^%c]+", noinline = true},
{"string", "^[^,%]}%c ]+"}
};
exports.tokenize = function (str)
local token
local row = 0
local ignore
local indents = 0
local lastIndents
local stack = {}
local indentAmount = 0
local inline = false
str = str:gsub("\r\n","\010")
while #str > 0 do
for i in ipairs(tokens) do
local captures = {}
if not inline or tokens[i].noinline == nil then
captures = {str:match(tokens[i][2])}
end
if #captures > 0 then
captures.input = str:sub(0, 25)
token = table_clone(tokens[i])
token[2] = captures
local str2 = str:gsub(tokens[i][2], "", 1)
token.raw = str:sub(1, #str - #str2)
str = str2
if token[1] == "{" or token[1] == "[" then
inline = true
elseif token.const then
-- Since word pattern contains last char we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
elseif token[1] == "id" then
-- Since id pattern contains last semi-colon we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
-- Trim
token[2][1] = string_trim(token[2][1])
elseif token[1] == "string" then
-- Finding numbers
local snip = token[2][1]
if not token.force_text then
if snip:match("^(-?%d+%.%d+)$") or snip:match("^(-?%d+)$") then
token[1] = "number"
end
end
elseif token[1] == "comment" then
ignore = true;
elseif token[1] == "indent" then
row = row + 1
inline = false
lastIndents = indents
if indentAmount == 0 then
indentAmount = #token[2][1]
end
if indentAmount ~= 0 then
indents = (#token[2][1] / indentAmount);
else
indents = 0
end
if indents == lastIndents then
ignore = true;
elseif indents > lastIndents + 2 then
error("SyntaxError: invalid indentation, got " .. tostring(indents)
.. " instead of " .. tostring(lastIndents) .. context(token[2].input))
elseif indents > lastIndents + 1 then
push(stack, token)
elseif indents < lastIndents then
local input = token[2].input
token = {"dedent", {"", input = ""}}
token.input = input
while lastIndents > indents + 1 do
lastIndents = lastIndents - 1
push(stack, token)
end
end
end -- if token[1] == XXX
token.row = row
break
end -- if #captures > 0
end
if not ignore then
if token then
push(stack, token)
token = nil
else
error("SyntaxError " .. context(str))
end
end
ignore = false;
end
return stack
end
Parser.peek = function (self, offset)
offset = offset or 1
return self.tokens[offset + self.current]
end
Parser.advance = function (self)
self.current = self.current + 1
return self.tokens[self.current]
end
Parser.advanceValue = function (self)
return self:advance()[2][1]
end
Parser.accept = function (self, type)
if self:peekType(type) then
return self:advance()
end
end
Parser.expect = function (self, type, msg)
return self:accept(type) or
error(msg .. context(self:peek()[1].input))
end
Parser.expectDedent = function (self, msg)
return self:accept("dedent") or (self:peek() == nil) or
error(msg .. context(self:peek()[2].input))
end
Parser.peekType = function (self, val, offset)
return self:peek(offset) and self:peek(offset)[1] == val
end
Parser.ignore = function (self, items)
local advanced
repeat
advanced = false
for _,v in pairs(items) do
if self:peekType(v) then
self:advance()
advanced = true
end
end
until advanced == false
end
Parser.ignoreSpace = function (self)
self:ignore{"space"}
end
Parser.ignoreWhitespace = function (self)
self:ignore{"space", "indent", "dedent"}
end
Parser.parse = function (self)
local ref = nil
if self:peekType("string") and not self:peek().force_text then
local char = self:peek()[2][1]:sub(1,1)
if char == "&" then
ref = self:peek()[2][1]:sub(2)
self:advanceValue()
self:ignoreSpace()
elseif char == "*" then
ref = self:peek()[2][1]:sub(2)
return self.refs[ref]
end
end
local result
local c = {
indent = self:accept("indent") and 1 or 0,
token = self:peek()
}
push(self.parse_stack, c)
if c.token[1] == "doc" then
result = self:parseDoc()
elseif c.token[1] == "-" then
result = self:parseList()
elseif c.token[1] == "{" then
result = self:parseInlineHash()
elseif c.token[1] == "[" then
result = self:parseInlineList()
elseif c.token[1] == "id" then
result = self:parseHash()
elseif c.token[1] == "string" then
result = self:parseString("\n")
elseif c.token[1] == "timestamp" then
result = self:parseTimestamp()
elseif c.token[1] == "number" then
result = tonumber(self:advanceValue())
elseif c.token[1] == "pipe" then
result = self:parsePipe()
elseif c.token.const == true then
self:advanceValue();
result = c.token.value
else
error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input))
end
pop(self.parse_stack)
while c.indent > 0 do
c.indent = c.indent - 1
local term = "term "..c.token[1]..": '"..c.token[2][1].."'"
self:expectDedent("last ".. term .." is not properly dedented")
end
if ref then
self.refs[ref] = result
end
return result
end
Parser.parseDoc = function (self)
self:accept("doc")
return self:parse()
end
Parser.inline = function (self)
local current = self:peek(0)
if not current then
return {}, 0
end
local inline = {}
local i = 0
while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do
inline[self:peek(i)[1]] = true
i = i - 1
end
return inline, -i
end
Parser.isInline = function (self)
local _, i = self:inline()
return i > 0
end
Parser.parent = function(self, level)
level = level or 1
return self.parse_stack[#self.parse_stack - level]
end
Parser.parentType = function(self, type, level)
return self:parent(level) and self:parent(level).token[1] == type
end
Parser.parseString = function (self)
if self:isInline() then
local result = self:advanceValue()
--[[
- a: this looks
flowing: but is
no: string
--]]
local types = self:inline()
if types["id"] and types["-"] then
if not self:peekType("indent") or not self:peekType("indent", 2) then
return result
end
end
--[[
a: 1
b: this is
a flowing string
example
c: 3
--]]
if self:peekType("indent") then
self:expect("indent", "text block needs to start with indent")
local addtl = self:accept("indent")
result = result .. "\n" .. self:parseTextBlock("\n")
self:expectDedent("text block ending dedent missing")
if addtl then
self:expectDedent("text block ending dedent missing")
end
end
return result
else
--[[
a: 1
b:
this is also
a flowing string
example
c: 3
--]]
return self:parseTextBlock("\n")
end
end
Parser.parsePipe = function (self)
local pipe = self:expect("pipe")
self:expect("indent", "text block needs to start with indent")
local result = self:parseTextBlock(pipe.sep)
self:expectDedent("text block ending dedent missing")
return result
end
Parser.parseTextBlock = function (self, sep)
local token = self:advance()
local result = string_trim(token.raw, "\n")
local indents = 0
while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do
local newtoken = self:advance()
while token.row < newtoken.row do
result = result .. sep
token.row = token.row + 1
end
if newtoken[1] == "indent" then
indents = indents + 1
elseif newtoken[1] == "dedent" then
indents = indents - 1
else
result = result .. string_trim(newtoken.raw, "\n")
end
end
return result
end
Parser.parseHash = function (self, hash)
hash = hash or {}
local indents = 0
if self:isInline() then
local id = self:advanceValue()
self:expect(":", "expected semi-colon after id")
self:ignoreSpace()
if self:accept("indent") then
indents = indents + 1
hash[id] = self:parse()
else
hash[id] = self:parse()
if self:accept("indent") then
indents = indents + 1
end
end
self:ignoreSpace();
end
while self:peekType("id") do
local id = self:advanceValue()
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreSpace();
end
while indents > 0 do
self:expectDedent("expected dedent")
indents = indents - 1
end
return hash
end
Parser.parseInlineHash = function (self)
local id
local hash = {}
local i = 0
self:accept("{")
while not self:accept("}") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreWhitespace()
if self:peekType("id") then
id = self:advanceValue()
if id then
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreWhitespace()
end
end
i = i + 1
end
return hash
end
Parser.parseList = function (self)
local list = {}
while self:accept("-") do
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
end
return list
end
Parser.parseInlineList = function (self)
local list = {}
local i = 0
self:accept("[")
while not self:accept("]") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
i = i + 1
end
return list
end
Parser.parseTimestamp = function (self)
local capture = self:advance()[2]
return os.time{
year = capture[1],
month = capture[2],
day = capture[3],
hour = capture[4] or 0,
min = capture[5] or 0,
sec = capture[6] or 0,
isdst = false,
} - os.time{year=1970, month=1, day=1, hour=8}
end
exports.eval = function (str)
return Parser:new(exports.tokenize(str)):parse()
end
exports.dump = table_print
return exports

View file

@ -313,6 +313,12 @@ SMRCD= sendmail
SMRCDPACKAGE= sendmail
.endif
.if ${MK_NUAGEINIT} != "no"
CONFGROUPS+= NIUAGEINIT
NIUAGEINIT= nuageinit
NIUAGEINITPACKAGE= nuageinit
.endif
.if ${MK_UNBOUND} != "no"
CONFGROUPS+= UNBOUND
UNBOUND+= local_unbound

67
libexec/rc/rc.d/nuageinit Executable file
View file

@ -0,0 +1,67 @@
#!/bin/sh
#
# PROVIDE: nuageinit
# REQUIRE: mountcritlocal
# BEFORE: NETWORKING
# KEYWORD: firstboot
. /etc/rc.subr
name="nuageinit"
desc="Limited Cloud Init configuration"
start_cmd="nuageinit_start"
stop_cmd=":"
rcvar="nuageinit_enable"
nuageinit_start()
{
local citype
# detect cloud init provider
# according to the specification of the config drive
# it either formatted in vfat or iso9660 and labeled
# config-2
for f in iso9660 msdosfs; do
drive=/dev/$f/config-2
if [ -e $drive ]; then
citype=config-2
break
fi
drive=/dev/$f/cidata
if [ -e $drive ]; then
citype=nocloud
break
fi
unset drive
done
if [ -z "$drive" ]; then
# try to detect networked based instance
err 1 "Impossible to find a cloud init provider"
fi
mkdir -p /media/nuageinit
fs=$(fstyp $drive)
mount -t $fs $drive /media/nuageinit
# according to the specification, the content is either
# in the openstack or ec2 directory
case "$citype" in
config-2)
for d in openstack ec2; do
dir=/media/nuageinit/$d/latest
if [ -d $dir ]; then
/usr/libexec/nuageinit $dir $citype
break
fi
done
;;
nocloud)
/usr/libexec/nuageinit /media/nuageinit $citype
;;
esac
if [ -n "$drive" ]; then
umount /media/nuageinit
fi
rmdir /media/nuageinit
}
load_rc_config $name
run_rc_command "$1"

View file

@ -148,6 +148,7 @@ __DEFAULT_YES_OPTIONS = \
NLS_CATALOGS \
NS_CACHING \
NTP \
NUAGEINIT \
NVME \
OFED \
OPENSSL \

View file

@ -7347,6 +7347,27 @@ OLD_DIRS+=var/spool/clientmqueue
OLD_FILES+=var/db/services.db
.endif
.if ${MK_NUAGEINIT} == no
OLD_FILES+=etc/rc.d/nuageinit
OLD_FILES+=usr/libexec/nuageinit
OLD_FILES+=usr/share/flua/nuage.lua
OLD_FILES+=usr/share/flua/yaml.lua
OLD_FILES+=usr/tests
OLD_FILES+=usr/tests/libexec/nuageinit/utils
OLD_FILES+=usr/tests/libexec/nuageinit/nuage
OLD_FILES+=usr/tests/libexec/nuageinit/warn.lua
OLD_FILES+=usr/tests/libexec/nuageinit/dirname.lua
OLD_FILES+=usr/tests/libexec/nuageinit/err.lua
OLD_FILES+=usr/tests/libexec/nuageinit/adduser.lua
OLD_FILES+=usr/tests/libexec/nuageinit/addsshkeys.lua
OLD_FILES+=usr/tests/libexec/nuageinit/Kyuafile
OLD_FILES+=usr/tests/libexec/nuageinit/ktrace.out
OLD_FILES+=usr/tests/libexec/nuageinit/addgroup.lua
OLD_FILES+=usr/tests/libexec/nuageinit/sethostname.lua
OLD_FILES+=usr/tests/libexec/nuageinit/nuageinit
OLD_FILES+=usr/tests/libexec/nuageinit/addsshkey.lua
.endif
.if ${MK_SHAREDOCS} == no
OLD_FILES+=usr/share/doc/pjdfstest/README
OLD_DIRS+=usr/share/doc/pjdfstest

View file

@ -0,0 +1 @@
Do not install the limited cloud init support scripts.