From nobody Fri Mar 15 08:22:58 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4Twy1G4tY2z5Ct2D; Fri, 15 Mar 2024 08:22:58 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4Twy1G3XSwz4lww; Fri, 15 Mar 2024 08:22:58 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1710490978; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=khUh1DnpFYzDByMM74qBLTHLVAjyqZQSlhsRvvbN308=; b=kWsp2HGkroZQe6Mhs03XHZns2NcRkLam08mfLsilAp3Gkh3DrLCQg3W0flmCMW/hb5CHWc xwya0hBJJuVEBdqjZFlaIFRZg/30LZm1LttI7v5CtORp80B/zGpGhbweX9bDJzlbeyWLe8 tUWP9ypWoqO19aJSsingOIkEJZEkZ9qtrok13ROzt9su/mXNQh9qN4V67a2C6Dkmr/Sltg pXeKK2UBuQ7JHBKYHqpQPz5odncf1suDvZC+s++JeYcpKi5BRr2alcRBrqKUr3N/QWF24m zGS2f5EOX3UBYGhxgHC/2rn63qbMGEj1+uldHh/z8G8UjKb4ETwgQy+b28oSQA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1710490978; a=rsa-sha256; cv=none; b=lHyS5yUZksKn/YX+Mh7yEU4+BxIZFYMfLr0Lr4+CKCPk/dOacD0maPUrEYL3TyNcWGtqYt Qo9FMNGGW4V+XApmztxKBohWiX4eDZWd/RBxggqgbvmFIJcWvUvo8PQq8YafiDfvb90NMJ 6hAf2btekwQ0w7yD3E6CmqbPJhUA9SNIsSwwKlZZazdj9HiqklAmMXqrHEqDZkejviSJ/f +l2JfPi85QGKQhDy/YlIVW/nd1KCqZAEHpEsLRi9BDOn/kh2/fBbktf9HnY7S/XXpIKFlb vwt31DcXSWCMMnKOs4tXh+PDaEikdK5AbBT6DChqnngwZlfCGzC9+ZjBmVXIUQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1710490978; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=khUh1DnpFYzDByMM74qBLTHLVAjyqZQSlhsRvvbN308=; b=gosbilEtv1qcGQ6X6DpilHKrcT/N9teU07fOjXJrNtaoDDZ5+cS9CK2oZUUWE0ZC9oAAgB Cj7GG3FvL0btw+RatX4Q6Ftxxp4Boufp5uCvfC4+zV8DTvIb37QpPb07h+Wc0w6cYC5N3r m38QWWg9AsE3f1wC+B9IEW1ysweXsaQBgPWc0d7Cr530bxgzVa1muUcolit0AXVysArLWU aIA5DAoRwqKdsq9w9BhV4EMpOR9SKL9zeP0zONbAlHlB+PEy9wbJdhN8ryHgGaeA00efdh JIfNR5DsEVSyIpxNt8qhszrFgOdyU6hJ0gx5mzfZl5m1sh3b8In3UmrePE4HdA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4Twy1G37w7z13pm; Fri, 15 Mar 2024 08:22:58 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 42F8Mw0v037873; Fri, 15 Mar 2024 08:22:58 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 42F8Mwie037870; Fri, 15 Mar 2024 08:22:58 GMT (envelope-from git) Date: Fri, 15 Mar 2024 08:22:58 GMT Message-Id: <202403150822.42F8Mwie037870@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Baptiste Daroussin Subject: git: a42d6f76018e - main - nuageinit: add basic support for cloudinit. List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: a42d6f76018e4ed8324e319ab48aac904bda437c Auto-Submitted: auto-generated The branch main has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=a42d6f76018e4ed8324e319ab48aac904bda437c commit a42d6f76018e4ed8324e319ab48aac904bda437c Author: Baptiste Daroussin AuthorDate: 2022-11-23 19:00:39 +0000 Commit: Baptiste Daroussin CommitDate: 2024-03-15 08:22:16 +0000 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 --- libexec/Makefile | 5 + libexec/nuageinit/Makefile | 11 + libexec/nuageinit/nuage.lua | 214 +++++++++++ libexec/nuageinit/nuageinit | 312 ++++++++++++++++ libexec/nuageinit/tests/Makefile | 13 + libexec/nuageinit/tests/addgroup.lua | 15 + libexec/nuageinit/tests/addsshkey.lua | 2 + libexec/nuageinit/tests/adduser.lua | 15 + libexec/nuageinit/tests/dirname.lua | 8 + libexec/nuageinit/tests/err.lua | 4 + libexec/nuageinit/tests/nuage.sh | 52 +++ libexec/nuageinit/tests/nuageinit.sh | 338 ++++++++++++++++++ libexec/nuageinit/tests/sethostname.lua | 4 + libexec/nuageinit/tests/utils.sh | 21 ++ libexec/nuageinit/tests/warn.lua | 4 + libexec/nuageinit/yaml.lua | 586 +++++++++++++++++++++++++++++++ libexec/rc/rc.d/Makefile | 6 + libexec/rc/rc.d/nuageinit | 67 ++++ share/mk/src.opts.mk | 1 + tools/build/mk/OptionalObsoleteFiles.inc | 21 ++ tools/build/options/WITHOUT_NUAGEINIT | 1 + 21 files changed, 1700 insertions(+) diff --git a/libexec/Makefile b/libexec/Makefile index ee354fa60e79..8287690eeb3c 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -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 .include diff --git a/libexec/nuageinit/Makefile b/libexec/nuageinit/Makefile new file mode 100644 index 000000000000..64c5ec316f3d --- /dev/null +++ b/libexec/nuageinit/Makefile @@ -0,0 +1,11 @@ +PACKAGE= nuageinit +SCRIPTS= nuageinit +FILES= nuage.lua yaml.lua +FILESDIR= ${SHAREDIR}/flua + +.include + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua new file mode 100644 index 000000000000..55486ae2b122 --- /dev/null +++ b/libexec/nuageinit/nuage.lua @@ -0,0 +1,214 @@ +-- SPDX-License-Identifier: BSD-2-Clause +-- +-- Copyright(c) 2022 Baptiste Daroussin + +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 diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit new file mode 100755 index 000000000000..08224061d1b1 --- /dev/null +++ b/libexec/nuageinit/nuageinit @@ -0,0 +1,312 @@ +#!/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() + 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 diff --git a/libexec/nuageinit/tests/Makefile b/libexec/nuageinit/tests/Makefile new file mode 100644 index 000000000000..d5b3bd9dcc82 --- /dev/null +++ b/libexec/nuageinit/tests/Makefile @@ -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 diff --git a/libexec/nuageinit/tests/addgroup.lua b/libexec/nuageinit/tests/addgroup.lua new file mode 100644 index 000000000000..60a0d8346793 --- /dev/null +++ b/libexec/nuageinit/tests/addgroup.lua @@ -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 diff --git a/libexec/nuageinit/tests/addsshkey.lua b/libexec/nuageinit/tests/addsshkey.lua new file mode 100644 index 000000000000..3aa5f7619ec2 --- /dev/null +++ b/libexec/nuageinit/tests/addsshkey.lua @@ -0,0 +1,2 @@ +local n = require("nuage") +n.addsshkey(".", "mykey") diff --git a/libexec/nuageinit/tests/adduser.lua b/libexec/nuageinit/tests/adduser.lua new file mode 100644 index 000000000000..9366d2abd0f4 --- /dev/null +++ b/libexec/nuageinit/tests/adduser.lua @@ -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 diff --git a/libexec/nuageinit/tests/dirname.lua b/libexec/nuageinit/tests/dirname.lua new file mode 100644 index 000000000000..d1268e48575c --- /dev/null +++ b/libexec/nuageinit/tests/dirname.lua @@ -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 diff --git a/libexec/nuageinit/tests/err.lua b/libexec/nuageinit/tests/err.lua new file mode 100644 index 000000000000..c62fa1098f09 --- /dev/null +++ b/libexec/nuageinit/tests/err.lua @@ -0,0 +1,4 @@ +#!/usr/libexec/flua + +local n = require("nuage") +n.err("plop") diff --git a/libexec/nuageinit/tests/nuage.sh b/libexec/nuageinit/tests/nuage.sh new file mode 100644 index 000000000000..bbf306eae51f --- /dev/null +++ b/libexec/nuageinit/tests/nuage.sh @@ -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 +} diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh new file mode 100644 index 000000000000..926233bcf66d --- /dev/null +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -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 [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_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) + 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 <