Page MenuHomeFreeBSD

D44141.id135171.diff
No OneTemporary

D44141.id135171.diff

diff --git a/libexec/Makefile b/libexec/Makefile
--- a/libexec/Makefile
+++ b/libexec/Makefile
@@ -27,6 +27,7 @@
${_rshd} \
${_rtld-elf} \
save-entropy \
+ ${_nuageinit} \
${_smrsh} \
${_tests} \
${_tftp-proxy} \
@@ -119,6 +120,10 @@
_tests= tests
.endif
+.if ${MK_NUAGEINIT} != "no"
+_nuageinit= nuageinit
+.endif
+
.include <bsd.arch.inc.mk>
.include <bsd.subdir.mk>
diff --git a/libexec/nuageinit/Makefile b/libexec/nuageinit/Makefile
new file mode 100644
--- /dev/null
+++ b/libexec/nuageinit/Makefile
@@ -0,0 +1,7 @@
+PACKAGE= nuageinit
+SCRIPTS= nuageinit
+#SUBDIR= nuage
+FILES= nuage.lua yaml.lua
+FILESDIR= ${SHAREDIR}/flua
+
+.include <bsd.prog.mk>
diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua
new file mode 100644
--- /dev/null
+++ b/libexec/nuageinit/nuage.lua
@@ -0,0 +1,203 @@
+-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+--
+-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+-- 1. Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+-- 2. Redistributions in binary form must reproduce the above copyright
+-- notice, this list of conditions and the following disclaimer in the
+-- documentation and/or other materials provided with the distribution.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+-- SUCH DAMAGE.
+
+local pu = require("posix.unistd")
+
+function warn(str)
+ io.stderr:write(str.."\n")
+end
+
+function err(str)
+ io.stderr:write(str.."\n")
+ os.exit(1)
+end
+
+function sethostname(hostname)
+ if hostname == nil then return end
+
+ local f,err = io.open("/etc/rc.conf.d/hostname", "w")
+ if not f then
+ warn("Impossible to open /etc/rc.conf.d/hostname: "..err)
+ return
+ end
+ f:write("hostname=\""..hostname.."\"\n")
+ f:close()
+end
+
+local function splitlist(list)
+ ret = {}
+ if type(list) == "string" then
+ for str in string.gmatch(list, "([^, ]+)") do
+ table.insert(ret, str)
+ end
+ elseif type(list) == "table" then
+ ret = list
+ else
+ warn("Invalid type ".. type(list) ..", expecting table or string")
+ end
+ return ret
+end
+
+function adduser(pwd)
+ if (type(pwd) ~= "table") then
+ warn("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 cmd = precmd .. "pw useradd -n ".. pwd.name .. " -M 0755 -w none "
+ cmd = cmd .. extraargs .. " -c '".. pwd.gecos
+ cmd = cmd .. "' -d '" .. pwd.home .. "' -s "..pwd.shell .. postcmd
+
+ r,err = os.execute(cmd)
+ if not r then
+ warn("nuageinit: fail to add user "..pwd.name);
+ warn(cmd)
+ return nil
+ end
+ if pwd.locked then
+ os.execute("pw lock " .. pwd.name)
+ end
+ return pwd.home
+end
+
+function addgroup(grp)
+ if (type(grp) ~= "table") then
+ warn("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 cmd = "pw groupadd -n ".. grp.name .. extraargs
+ r, err = os.execute(cmd)
+ if not r then
+ warn("nuageinit: fail to add group ".. grp.name);
+ return false
+ end
+ return true
+end
+
+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
+ warn("nuageinit: impossible to create ".. dotssh_path)
+ return
+ end
+ chowndotssh = true
+ dirattrs = lfs.attributes(homedir)
+ end
+ end
+
+ f = io.open(ak_path, "w+")
+ if not f then
+ warn("nuageinit: impossible to open "..ak_path)
+ if needchown then
+ pu.chown(ak_path, dirattrs.uid, dirattrs.gid)
+ end
+ if needrchown then
+ pu.chown(dotssh_path, dirattrs.uid, dirattrs.gid)
+ end
+ 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
+
+function dirname(oldpath)
+ local path = oldpath:gsub("[^/]+/*$", "")
+ if path == "" then
+ return oldpath
+ end
+ return path
+end
+
+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
diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
new file mode 100755
--- /dev/null
+++ b/libexec/nuageinit/nuageinit
@@ -0,0 +1,233 @@
+#!/usr/libexec/flua
+
+-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+--
+-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+-- 1. Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+-- 2. Redistributions in binary form must reproduce the above copyright
+-- notice, this list of conditions and the following disclaimer in the
+-- documentation and/or other materials provided with the distribution.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+-- SUCH DAMAGE.
+
+require("nuage")
+local yaml = require("yaml")
+
+if #arg ~= 2 then
+ print("Usage ".. arg[0] .." <cloud-init directory> [config-2|nocloud]")
+ os.exit(1)
+end
+path = arg[1]
+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"
+}
+
+if citype == "config-2" then
+ local parser = ucl.parser()
+ local res,err = parser:parse_file(path..'/meta_data.json')
+
+ if not res then
+ err("Error parsing meta_data.json: " .. err)
+ end
+ local obj = parser:get_object()
+ local sshkeys = obj["public_keys"]
+ if sshkeys then
+ local homedir = adduser(default_user)
+ addsshkey(homedir, v)
+ end
+ sethostname(obj["hostname"])
+
+ -- network
+ parser = ucl.parser()
+ local res,err = parser:parse_file(path..'/network_data.json')
+ if not res then
+ err("Error parsing network_data.json: " .. err)
+ end
+ obj = parser:get_object()
+
+ -- grab ifaces
+ local ns = io.popen('netstat -i --libxo json')
+ local netres = ns:read("*a")
+ ns:close()
+ parser = ucl.parser()
+ local res,err = parser:parse_string(netres)
+ local ifaces = parser:get_object()
+
+ local myifaces = {}
+ for _,iface in pairs(ifaces["statistics"]["interface"]) do
+ for o in iface["network"]:gmatch("<Link#%d>") do
+ local s = iface["address"]:lower()
+ myifaces[s] = iface["name"]
+ end
+ end
+
+ local mylinks = {}
+ for _,v in pairs(obj["links"]) do
+ local s = v["ethernet_mac_address"]:lower()
+ mylinks[v["id"]] = myifaces[s]
+ end
+
+ local network = io.open("/etc/rc.conf.d/network", "w")
+ local routing = io.open("/etc/rc.conf.d/routing", "w")
+ local ipv6 = {}
+ for _,v in pairs(obj["networks"]) do
+ if v["type"] == "ipv4_dhcp" then
+ network:write("ifconfig_"..mylinks[v["link"]].."=\"DHCP\"\n")
+ end
+ if v["type"] == "ipv6" then
+ table.insert(ipv6, mylinks[v["link"]])
+ network:write("ifconfig_"..mylinks[v["link"]].."_ipv6=\"inet6 "..v["ip_address"].."\"\n")
+ routing:write("ipv6_defaultrouter=\""..v["gateway"].."\"\n")
+ routing:write("ipv6_route_"..mylinks[v["link"]].."=\""..v["gateway"].." -prefixlen 128 -interface "..mylinks[v["link"]].."\"\n")
+ if v["route"] then
+ for _,r in v["route"] do
+ -- 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
+ err("Not implemented yet")
+ ::next::
+ end
+ end
+ end
+ end
+ network:write("ipv6_network_interfaces=\"")
+ for _,v in pairs(ipv6) do
+ network:write(v.. " ")
+ end
+ network:write("\"\n")
+ routing:write("ipv6_static_routes=\"")
+ for _,v in pairs(ipv6) do
+ routing:write(v.. " ")
+ end
+ routing:write("\"\n")
+ network:write("ipv6_default_interface=\""..ipv6[1].."\"\n")
+ network:close()
+ routing:close()
+ local ssh = io.open("/etc/rc.conf.d/sshd", "w+")
+ ssh:write("sshd_enable=\"yes\"\n")
+ ssh:close()
+elseif citype == "nocloud" then
+ local parser = ucl.parser()
+ -- /!\ we only accept yaml parseable by libucl, not all forms are parseable
+ local res,err = parser:parse_file(path..'/meta-data')
+ if not res then
+ err("nuageinit: error parsing nocloud meta_data: " .. err)
+ end
+ local obj = parser:get_object()
+ local hostname = obj['local-hostname']
+ if not hostname then
+ hostname = obj['hostname']
+ end
+ sethostname(hostname)
+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
+ local f = io.open(path.."/user-data")
+ local obj = yaml.eval(f:read("*a"))
+ f:close()
+ if not obj then
+ err("nuageinit: error parsing cloud-config file: user-data: " .. err)
+ end
+ if obj.groups then
+ for _,g in pairs(obj.groups) do
+ if (type(g) == "string") then
+ local r = addgroup({name = g})
+ if not r then
+ warn("nuageinit: failed to add group: ".. g)
+ end
+ elseif type(g) == "table" then
+ for k,v in pairs(g) do
+ addgroup({name = k, members = v})
+ end
+ else
+ warn("nuageinit: invalid type : "..type(u).." for users entry number "..n);
+ end
+ end
+ end
+ if obj.users then
+ for _,u in pairs(obj.users) do
+ if type(u) == "string" then
+ print(u)
+ adduser({name = u})
+ elseif type(u) == "table" then
+ -- ignore users without a username
+ if u.name == nil then
+ goto unext
+ end
+ homedir = adduser(u)
+ if u.ssh_authorized_keys then
+ for _,v in ipairs(u.ssh_authorized_keys) do
+ addsshkey(homedir, v)
+ end
+ end
+ else
+ warn("nuageinit: invalid type : "..type(u).." for users entry number "..n);
+ end
+ ::unext::
+ end
+ else
+ -- default user if none are defined
+ adduser(default_user)
+ end
+ if obj.write_files then
+ for _,o in pairs(obj.write_files) do
+ if o.encoding then
+ warn("nuageinit: only plain text files are supported")
+ goto wrnext
+ end
+ local r,err = mkdir_p(dirname(o.path))
+ if not r then
+ warn("nuageinit: impossible to create directory "..dirname(o.path)..": "..err)
+ goto wrnext
+ end
+ local f = io.open(o.path, "w")
+ f:write(o.content .. "\n")
+ f:close()
+ if o.permissions then
+ setmode(o.path, o.permissions)
+ end
+ ::wrnext::
+ end
+ end
+ if obj.ssh_authorized_keys then
+ local homedir = adduser(default_user)
+ for _,k in ipairs(obj.ssh_authorized_keys) do
+ addsshkey(homedir, k)
+ end
+ end
+else
+ local res,err = os.execute(path..'/user-data')
+ if not res then
+ err("nuageinit: error executing user-data script: ".. err)
+ end
+end
diff --git a/libexec/nuageinit/yaml.lua b/libexec/nuageinit/yaml.lua
new file mode 100644
--- /dev/null
+++ b/libexec/nuageinit/yaml.lua
@@ -0,0 +1,582 @@
+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
diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile
--- a/libexec/rc/rc.d/Makefile
+++ b/libexec/rc/rc.d/Makefile
@@ -313,6 +313,12 @@
SMRCDPACKAGE= sendmail
.endif
+.if ${MK_NUAGEINIT} != "no"
+CONFGROUPS+= NIUAGEINIT
+NIUAGEINIT= nuageinit
+NIUAGEINITPACKAGE= nuageinit
+.endif
+
.if ${MK_UNBOUND} != "no"
CONFGROUPS+= UNBOUND
UNBOUND+= local_unbound
diff --git a/libexec/rc/rc.d/nuageinit b/libexec/rc/rc.d/nuageinit
new file mode 100755
--- /dev/null
+++ b/libexec/rc/rc.d/nuageinit
@@ -0,0 +1,122 @@
+#!/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"
+
+fetch_config()
+{
+ mkdir -p /media/nuageinit
+ cd /media/nuageinit
+ case "$1" in
+ EC2)
+ fetch http://169.254.169.254/latest/meta-data/instance-id ||
+ fetch http://[fd00:ec2::254]/latest/meta-data/instance-id
+ fetch http://169.254.169.254/latest/meta-data/local-hostname ||
+ fetch http://[fd00:ec2::254]/latest/meta-data/local-hostname
+ fetch http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key ||
+ fetch http://[fd00:ec2::254]/latest/meta-data/public-keys/0/openssh-key
+ fetch http://169.254.169.254/latest/user-data ||
+ fetch http://[fd00:ec2::254]/latest/user-data
+ ;;
+ OPENSTACK)
+ fetch http://169.254.169.254/openstack/latest/meta_data.json \
+ fetch http://[fe80::a9fe:a9fe]/openstack/latest/network_data.json
+ ;;
+ GCE)
+ err 1 "Not yet implemented"
+ ;;
+ esac
+ cd -
+}
+
+detect_instance()
+{
+ case $(kenv -q smbios.system.uuid) in
+ ec2*|EC2*) echo "EC2"
+ return ;;
+ esac
+ case $(kenv -q smbios.system.product) in
+ Google*) echo "GCE"
+ return ;;
+ OpenStack*)
+ echo "OPENSTACK"
+ return;;
+ esac
+ case $(kenv -q smbios.system.serial) in
+ Google*) echo "GCE"
+ return ;;
+ esac
+}
+
+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
+ netconfig=$(detect_instance)
+ if [ -z "$netconfig" ]; then
+ err 1 "Impossible to find a cloud init provider"
+ fi
+ fi
+ mkdir -p /media/nuageinit
+ if [ -n "$drive" ]; then
+ fs=$(fstyp $drive)
+ mount -t $fs $drive /media/nuageinit
+ else
+ fetch_config $netconfig
+ fi
+ # 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
+ ;;
+ *)
+ /usr/libexec/nuageinit /media/nuageinit $netconfig
+ ;;
+ esac
+ if [ -n "$drive" ]; then
+ umount /media/nuageinit
+ fi
+ rmdir /media/nuageinit
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/share/mk/src.opts.mk b/share/mk/src.opts.mk
--- a/share/mk/src.opts.mk
+++ b/share/mk/src.opts.mk
@@ -148,6 +148,7 @@
NLS_CATALOGS \
NS_CACHING \
NTP \
+ NUAGEINIT \
NVME \
OFED \
OPENSSL \
diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc
--- a/tools/build/mk/OptionalObsoleteFiles.inc
+++ b/tools/build/mk/OptionalObsoleteFiles.inc
@@ -7347,6 +7347,13 @@
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
+.endif
+
.if ${MK_SHAREDOCS} == no
OLD_FILES+=usr/share/doc/pjdfstest/README
OLD_DIRS+=usr/share/doc/pjdfstest
diff --git a/tools/build/options/WITHOUT_NUAGEINIT b/tools/build/options/WITHOUT_NUAGEINIT
new file mode 100644
--- /dev/null
+++ b/tools/build/options/WITHOUT_NUAGEINIT
@@ -0,0 +1,2 @@
+.\" $FreeBSD$
+Do not install the limited cloud init support scripts.

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 24, 12:59 AM (12 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
32053352
Default Alt Text
D44141.id135171.diff (32 KB)

Event Timeline