This stage takes /usr/lib/passwd and /usr/etc/passwd from an OSTree checkout, merges them into one file, and store it as /etc/passwd in the buildroot. It does the same for /etc/group. The reason for doing this is that there is an issue with unstable UIDs and GIDs when creating OSTree commits from scratch. When there is a package that creates a system user or a system group, it can change the UID and GID of users and groups that are created later. This is not a problem in traditional deployments because already created users and groups never change their UIDs and GIDs, but with OSTree we recreate the files from scratch and then replace the previous one so it can actually change. By copying the files to the build root before doing any other operations, we can make sure that the UIDs and GIDs of already existing users and groups won't change. Co-author: Christian Kellner <christian@kellner.me>
149 lines
5 KiB
Python
149 lines
5 KiB
Python
#
|
|
# Tests for the 'osbuild.util.ostree' module.
|
|
#
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from osbuild.util import ostree
|
|
|
|
from .. import test
|
|
|
|
|
|
def run(*args, check=True, encoding="utf-8", **kwargs):
|
|
res = subprocess.run(*args,
|
|
encoding=encoding,
|
|
check=check,
|
|
**kwargs)
|
|
return res
|
|
|
|
|
|
class TestObjectStore(unittest.TestCase):
|
|
|
|
# pylint: disable=no-self-use
|
|
@unittest.skipUnless(test.TestBase.have_rpm_ostree(), "rpm-ostree missing")
|
|
def test_treefile_empty(self):
|
|
# check we produce a valid treefile from an empty object
|
|
tf = ostree.Treefile()
|
|
|
|
with tf.as_tmp_file() as f:
|
|
run(["rpm-ostree", "compose", "tree", "--print-only", f])
|
|
|
|
def test_treefile_types(self):
|
|
tf = ostree.Treefile()
|
|
|
|
tf["repos"] = ["a", "b", "c"] # valid list of strings
|
|
tf["selinux"] = True # valid boolean
|
|
tf["ref"] = "ref/sample/tip" # valid string
|
|
|
|
with self.assertRaises(ValueError):
|
|
tf["repos"] = "not a list" # not a list
|
|
|
|
with self.assertRaises(ValueError):
|
|
tf["repos"] = [1, 2, 3] # not a string list
|
|
|
|
with self.assertRaises(ValueError):
|
|
tf["selinux"] = "not a bool" # not a boolean
|
|
|
|
def test_treefile_dump(self):
|
|
tf = ostree.Treefile()
|
|
test_ref = "a/sample/ref"
|
|
tf["ref"] = test_ref
|
|
|
|
with tf.as_tmp_file() as path:
|
|
with open(path, "r") as f:
|
|
js = json.load(f)
|
|
self.assertEqual(js["ref"], test_ref)
|
|
self.assertEqual(tf["ref"], test_ref)
|
|
|
|
@unittest.skipUnless(test.TestBase.have_rpm_ostree(), "rpm-ostree missing")
|
|
def test_treefile_full(self):
|
|
params = {
|
|
"ref": "osbuild/ostree/devel",
|
|
"repos": ["fedora", "osbuild"],
|
|
"selinux": True,
|
|
"boot-location": "new",
|
|
"etc-group-members": ["wheel"],
|
|
"machineid-compat": True
|
|
}
|
|
|
|
tf = ostree.Treefile()
|
|
for p, v in params.items():
|
|
tf[p] = v
|
|
|
|
with tf.as_tmp_file() as path:
|
|
r = run(["rpm-ostree",
|
|
"compose",
|
|
"tree",
|
|
"--print-only",
|
|
path],
|
|
stdout=subprocess.PIPE)
|
|
self.assertEqual(r.returncode, 0)
|
|
js = json.loads(r.stdout)
|
|
|
|
for p, v in params.items():
|
|
self.assertEqual(v, js[p])
|
|
|
|
|
|
class TestPasswdLike(unittest.TestCase):
|
|
|
|
def test_merge_passwd(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
primary_file_lines = [
|
|
"root:x:0:0:root:/root:/bin/bash\n",
|
|
"bin:x:1:1:bin:/bin:/sbin/nologin\n",
|
|
"daemon:x:2:2:daemon:/sbin:/sbin/nologin\n"
|
|
]
|
|
secondary_file_lines = [
|
|
"daemon:x:9:9:daemon:/sbin:/sbin/nologin\n"
|
|
"lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\n",
|
|
"sync:x:5:0:sync:/sbin:/bin/sync\n"
|
|
]
|
|
result_file_lines = [
|
|
"root:x:0:0:root:/root:/bin/bash\n",
|
|
"bin:x:1:1:bin:/bin:/sbin/nologin\n",
|
|
"daemon:x:2:2:daemon:/sbin:/sbin/nologin\n",
|
|
"lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\n",
|
|
"sync:x:5:0:sync:/sbin:/bin/sync\n"
|
|
]
|
|
with open(os.path.join(tmpdir, "primary"), "w") as f:
|
|
f.writelines(primary_file_lines)
|
|
with open(os.path.join(tmpdir, "secondary"), "w") as f:
|
|
f.writelines(secondary_file_lines)
|
|
|
|
passwd = ostree.PasswdLike.from_file(os.path.join(tmpdir, "primary"))
|
|
passwd.merge_with_file(os.path.join(tmpdir, "secondary"))
|
|
passwd.dump_to_file(os.path.join(tmpdir, "result"))
|
|
|
|
with open(os.path.join(tmpdir, "result"), "r") as f:
|
|
self.assertEqual(sorted(f.readlines()), sorted(result_file_lines))
|
|
|
|
def test_merge_group(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
primary_file_lines = [
|
|
"root:x:0:\n",
|
|
"bin:x:1:\n"
|
|
]
|
|
secondary_file_lines = [
|
|
"bin:x:4:\n",
|
|
"daemon:x:2:\n"
|
|
]
|
|
result_file_lines = [
|
|
"root:x:0:\n",
|
|
"bin:x:1:\n",
|
|
"daemon:x:2:\n"
|
|
]
|
|
with open(os.path.join(tmpdir, "primary"), "w") as f:
|
|
f.writelines(primary_file_lines)
|
|
with open(os.path.join(tmpdir, "secondary"), "w") as f:
|
|
f.writelines(secondary_file_lines)
|
|
|
|
passwd = ostree.PasswdLike.from_file(os.path.join(tmpdir, "primary"))
|
|
passwd.merge_with_file(os.path.join(tmpdir, "secondary"))
|
|
passwd.dump_to_file(os.path.join(tmpdir, "result"))
|
|
|
|
with open(os.path.join(tmpdir, "result"), "r") as f:
|
|
self.assertEqual(sorted(f.readlines()), sorted(result_file_lines))
|