aboutsummaryrefslogtreecommitdiff
path: root/libexec/nuageinit
diff options
context:
space:
mode:
authorBaptiste Daroussin <bapt@FreeBSD.org>2025-06-26 11:32:07 +0000
committerBaptiste Daroussin <bapt@FreeBSD.org>2025-07-08 14:31:47 +0000
commitd056f72c358b4f962b09f5c752567ff543fe87c6 (patch)
tree919c6b63f984bf60545c286a78e391955a90628d /libexec/nuageinit
parentee1fd6b260c04560f424e4dc974b966b41af8dba (diff)
Diffstat (limited to 'libexec/nuageinit')
-rw-r--r--libexec/nuageinit/nuage.lua88
-rwxr-xr-xlibexec/nuageinit/nuageinit25
-rw-r--r--libexec/nuageinit/nuageinit.738
-rw-r--r--libexec/nuageinit/tests/Makefile1
-rw-r--r--libexec/nuageinit/tests/addfile.lua71
-rw-r--r--libexec/nuageinit/tests/nuage.sh10
-rw-r--r--libexec/nuageinit/tests/nuageinit.sh37
7 files changed, 264 insertions, 6 deletions
diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua
index deb441ee25ba..cdc0fc6cf2a7 100644
--- a/libexec/nuageinit/nuage.lua
+++ b/libexec/nuageinit/nuage.lua
@@ -7,6 +7,39 @@ local unistd = require("posix.unistd")
local sys_stat = require("posix.sys.stat")
local lfs = require("lfs")
+local function decode_base64(input)
+ local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+ input = string.gsub(input, '[^'..b..'=]', '')
+
+ local result = {}
+ local bits = ''
+
+ -- convert all characters in bits
+ for i = 1, #input do
+ local x = input:sub(i, i)
+ if x == '=' then
+ break
+ end
+ local f = b:find(x) - 1
+ for j = 6, 1, -1 do
+ bits = bits .. (f % 2^j - f % 2^(j-1) > 0 and '1' or '0')
+ end
+ end
+
+ for i = 1, #bits, 8 do
+ local byte = bits:sub(i, i + 7)
+ if #byte == 8 then
+ local c = 0
+ for j = 1, 8 do
+ c = c + (byte:sub(j, j) == '1' and 2^(8 - j) or 0)
+ end
+ table.insert(result, string.char(c))
+ end
+ end
+
+ return table.concat(result)
+end
+
local function warnmsg(str, prepend)
if not str then
return
@@ -441,6 +474,58 @@ local function upgrade_packages()
return run_pkg_cmd("upgrade")
end
+local function addfile(file, defer)
+ if type(file) ~= "table" then
+ return false, "Invalid object"
+ end
+ if defer and not file.defer then
+ return true
+ end
+ if not defer and file.defer then
+ return true
+ end
+ if not file.path then
+ return false, "No path provided for the file to write"
+ end
+ local content = nil
+ if file.content then
+ if file.encoding then
+ if file.encoding == "b64" or file.encoding == "base64" then
+ content = decode_base64(file.content)
+ else
+ return false, "Unsupported encoding: " .. file.encoding
+ end
+ else
+ content = file.content
+ end
+ end
+ local mode = "w"
+ if file.append then
+ mode = "a"
+ end
+
+ local root = os.getenv("NUAGE_FAKE_ROOTDIR")
+ if not root then
+ root = ""
+ end
+ local filepath = root .. file.path
+ local f = assert(io.open(filepath, mode))
+ if content then
+ f:write(content)
+ end
+ f:close()
+ if file.permissions then
+ -- convert from octal to decimal
+ local perm = tonumber(file.permissions, 8)
+ sys_stat.chmod(file.path, perm)
+ end
+ if file.owner then
+ local owner, group = string.match(file.owner, "([^:]+):([^:]+)")
+ unistd.chown(file.path, owner, group)
+ end
+ return true
+end
+
local n = {
warn = warnmsg,
err = errmsg,
@@ -456,7 +541,8 @@ local n = {
install_package = install_package,
update_packages = update_packages,
upgrade_packages = upgrade_packages,
- addsudo = addsudo
+ addsudo = addsudo,
+ addfile = addfile
}
return n
diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
index 5af1b84c1848..84133d4373c5 100755
--- a/libexec/nuageinit/nuageinit
+++ b/libexec/nuageinit/nuageinit
@@ -188,6 +188,25 @@ local function install_packages(packages)
end
end
+local function write_files(files, defer)
+ if not files then
+ return
+ end
+ for n, file in pairs(files) do
+ local r, errstr = nuage.addfile(file, defer)
+ if not r then
+ nuage.warn("Skipping write_files entry number " .. n .. ": " .. errstr)
+ end
+ end
+end
+
+local function write_files_not_defered(obj)
+ write_files(obj.write_files, false)
+end
+
+local function write_files_defered(obj)
+ write_files(obj.write_files, true)
+end
-- Set network configuration from user_data
local function network_config(obj)
if obj.network == nil then return end
@@ -456,13 +475,15 @@ if line == "#cloud-config" then
ssh_authorized_keys,
network_config,
ssh_pwauth,
- runcmd
+ runcmd,
+ write_files_not_defered,
}
local post_network_calls = {
packages,
users,
- chpasswd
+ chpasswd,
+ write_files_defered,
}
f = io.open(ni_path .. "/" .. ud)
diff --git a/libexec/nuageinit/nuageinit.7 b/libexec/nuageinit/nuageinit.7
index 1d2f83fe62e0..3bb440ebac95 100644
--- a/libexec/nuageinit/nuageinit.7
+++ b/libexec/nuageinit/nuageinit.7
@@ -2,7 +2,7 @@
.\"
.\" Copyright (c) 2025 Baptiste Daroussin <bapt@FreeBSD.org>
.\"
-.Dd June 16, 2025
+.Dd June 26, 2025
.Dt NUAGEINIT 7
.Os
.Sh NAME
@@ -239,6 +239,42 @@ where x is a number, then the password is considered encrypted,
otherwise the password is considered plaintext.
.El
.El
+.It Ic write_files
+An array of objects representing files to be created at first boot.
+The files are being created before the installation of any packages
+and the creation of the users.
+The only mandatory field is:
+.Ic path .
+It accepts the following keys for each objects:
+.Bl -tag -width "permissions"
+.It Ic content
+The content to be written to the file.
+If this key is not existing then an empty file will be created.
+.It Ic encoding
+Specifiy the encoding used for content.
+If not specified, then plain text is considered.
+Only
+.Ar b64
+and
+.Ar base64
+are supported for now.
+.It Ic path
+The path of the file to be created.
+.Pq Note intermerdiary directories will not be created .
+.It Ic permissions
+A string representing the permission of the file in octal.
+.It Ic owner
+A string representing the owner, two forms are possible:
+.Ar user
+or
+.Ar user:group .
+.It Ic append
+A boolean to specify the content should be appended to the file if the file
+exists.
+.It Ic defer
+A boolean to specify that the files should be created after the packages are
+installed and the users are created.
+.El
.El
.Sh EXAMPLES
Here is an example of a YAML configuration for
diff --git a/libexec/nuageinit/tests/Makefile b/libexec/nuageinit/tests/Makefile
index ccb81c090445..0b813dbd98be 100644
--- a/libexec/nuageinit/tests/Makefile
+++ b/libexec/nuageinit/tests/Makefile
@@ -9,5 +9,6 @@ ${PACKAGE}FILES+= dirname.lua
${PACKAGE}FILES+= err.lua
${PACKAGE}FILES+= sethostname.lua
${PACKAGE}FILES+= warn.lua
+${PACKAGE}FILES+= addfile.lua
.include <bsd.test.mk>
diff --git a/libexec/nuageinit/tests/addfile.lua b/libexec/nuageinit/tests/addfile.lua
new file mode 100644
index 000000000000..98d020e557c0
--- /dev/null
+++ b/libexec/nuageinit/tests/addfile.lua
@@ -0,0 +1,71 @@
+#!/bin/libexec/flua
+
+local n = require("nuage")
+local lfs = require("lfs")
+
+local f = {
+ content = "plop"
+}
+
+local r, err = n.addfile(f, false)
+if r or err ~= "No path provided for the file to write" then
+ n.err("addfile should not accept a file to write without a path")
+end
+
+local function addfile_and_getres(file)
+ local r, err = n.addfile(file, false)
+ if not r then
+ n.err(err)
+ end
+ local root = os.getenv("NUAGE_FAKE_ROOTDIR")
+ if not root then
+ root = ""
+ end
+ local filepath = root .. file.path
+ local resf = assert(io.open(filepath, "r"))
+ local str = resf:read("*all")
+ resf:close()
+ return str
+end
+
+-- simple file
+f.path="/tmp/testnuage"
+local str = addfile_and_getres(f)
+if str ~= f.content then
+ n.err("Invalid file content")
+end
+
+-- the file is overwriten
+f.content = "test"
+
+str = addfile_and_getres(f)
+if str ~= f.content then
+ n.err("Invalid file content, not overwritten")
+end
+
+-- try to append now
+f.content = "more"
+f.append = true
+
+str = addfile_and_getres(f)
+if str ~= "test" .. f.content then
+ n.err("Invalid file content, not appended")
+end
+
+-- base64
+f.content = "YmxhCg=="
+f.encoding = "base64"
+f.append = false
+
+str = addfile_and_getres(f)
+if str ~= "bla\n" then
+ n.err("Invalid file content, base64 decode")
+end
+
+-- b64
+f.encoding = "b64"
+str = addfile_and_getres(f)
+if str ~= "bla\n" then
+ n.err("Invalid file content, b64 decode")
+ print("==>" .. str .. "<==")
+end
diff --git a/libexec/nuageinit/tests/nuage.sh b/libexec/nuageinit/tests/nuage.sh
index 293a0a4a9a83..91260fc33a2f 100644
--- a/libexec/nuageinit/tests/nuage.sh
+++ b/libexec/nuageinit/tests/nuage.sh
@@ -1,5 +1,5 @@
#-
-# Copyright (c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+# Copyright (c) 2022-2025 Baptiste Daroussin <bapt@FreeBSD.org>
#
# SPDX-License-Identifier: BSD-2-Clause
#
@@ -10,6 +10,7 @@ atf_test_case sethostname
atf_test_case addsshkey
atf_test_case adduser
atf_test_case addgroup
+atf_test_case addfile
sethostname_body()
{
@@ -56,10 +57,17 @@ addgroup_body()
atf_check -o inline:"impossible_groupname:*:1001:\n" grep impossible_groupname etc/group
}
+addfile_body()
+{
+ mkdir tmp
+ atf_check /usr/libexec/flua $(atf_get_srcdir)/addfile.lua
+}
+
atf_init_test_cases()
{
atf_add_test_case sethostname
atf_add_test_case addsshkey
atf_add_test_case adduser
atf_add_test_case addgroup
+ atf_add_test_case addfile
}
diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh
index 44830f67e4c8..639c87181f95 100644
--- a/libexec/nuageinit/tests/nuageinit.sh
+++ b/libexec/nuageinit/tests/nuageinit.sh
@@ -1,5 +1,5 @@
#-
-# Copyright (c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+# Copyright (c) 2022-2025 Baptiste Daroussin <bapt@FreeBSD.org>
#
# SPDX-License-Identifier: BSD-2-Clause
#
@@ -29,6 +29,7 @@ atf_test_case config2_userdata_update_packages
atf_test_case config2_userdata_upgrade_packages
atf_test_case config2_userdata_shebang
atf_test_case config2_userdata_fqdn_and_hostname
+atf_test_case config2_userdata_write_files
setup_test_adduser()
{
@@ -847,6 +848,39 @@ EOF
fi
}
+config2_userdata_write_files_body()
+{
+ mkdir -p media/nuageinit
+ setup_test_adduser
+ printf "{}" > media/nuageinit/meta_data.json
+ cat > media/nuageinit/user_data <<EOF
+#cloud-config
+write_files:
+- content: "plop"
+ path: /file1
+- path: /emptyfile
+- content: !!binary |
+ YmxhCg==
+ path: /file_base64
+ encoding: b64
+ permissions: '0755'
+ owner: nobody
+- content: "bob"
+ path: "/foo"
+ defer: true
+EOF
+ atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2
+ atf_check -o inline:"plop" cat file1
+ atf_check -o inline:"" cat emptyfile
+ atf_check -o inline:"bla\n" cat file_base64
+ test -f foo && atf_fail "foo creation should have been defered"
+ atf_check -o match:"^-rwxr-xr-x.*nobody" ls -l file_base64
+ rm file1 emptyfile file_base64
+ atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet
+ test -f file1 -o -f emptyfile -o -f file_base64 && atf_fail "defer not working properly"
+ atf_check -o inline:"bob" cat foo
+}
+
config2_userdata_fqdn_and_hostname_body()
{
mkdir -p media/nuageinit
@@ -892,4 +926,5 @@ atf_init_test_cases()
atf_add_test_case config2_userdata_upgrade_packages
atf_add_test_case config2_userdata_shebang
atf_add_test_case config2_userdata_fqdn_and_hostname
+ atf_add_test_case config2_userdata_write_files
}