Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F153714273
D44141.id135170.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
D44141.id135170.diff
View Options
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,206 @@
+-- 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.
+
+-- $FreeBSD$
+--
+
+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,234 @@
+#!/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.
+
+-- $FreeBSD$
+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,124 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# 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
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 24, 3:54 AM (15 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
32058912
Default Alt Text
D44141.id135170.diff (32 KB)
Attached To
Mode
D44141: nuageinit: add basic support for cloudinit.
Attached
Detach File
Event Timeline
Log In to Comment