From e3b9fbd6bafad97cd7f8aac25672490a9afe5ced Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Tue, 3 Jun 2025 22:05:06 -0400 Subject: [PATCH] bootc-base-imagectl: support injecting directories Right now, the expectation for adding unpackaged content in a custom base image flow is to do it after the main compose. The problem however is that sometimes you want that content to affect the main compose itself, so doing it afterwards is not sufficient. The primary use case for this is sysusers.d dropins where you need to make sure that sysusers in scriptlets don't pick UIDs/GIDs already reserved on target client systems. One way to work around this is to synthesize an RPM that ships the dropin, and then ensure that it somehow runs as early as possible in the transaction. This is doable but obviously quite a hack. Enable this instead by adding a generic `--add-dir` switch which then just translates to `ostree-layers` in the override manifest. The dnf equivalent would be to first install e.g. `filesystem` and `setup`, add files to the rootfs, and then install all the other packages. See also discussions in https://github.com/coreos/rpm-ostree/pull/5354. --- bootc-base-imagectl | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/bootc-base-imagectl b/bootc-base-imagectl index 545385f..fbe879a 100755 --- a/bootc-base-imagectl +++ b/bootc-base-imagectl @@ -22,20 +22,35 @@ def run_build_rootfs(args): else: manifest_path = f'/{MANIFESTDIR}/{args.manifest}.yaml' - tmp_manifest = None + rpmostree_argv = ['rpm-ostree', 'compose', 'rootfs'] + + override_manifest = {} + tmp_ostree_repo = None if args.install: additional_pkgs = set(args.install) if len(additional_pkgs) > 0: - final_manifest = { - "include": manifest_path, - "packages": list(additional_pkgs), - } - tmp_manifest = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.json', delete_on_close=False) - json.dump(final_manifest, tmp_manifest) - tmp_manifest.close() - manifest_path = tmp_manifest.name + override_manifest['packages'] = list(additional_pkgs) + if args.add_dir: + tmp_ostree_repo = tempfile.mkdtemp(dir='/var/tmp') + subprocess.check_call(['ostree', 'init', '--repo', tmp_ostree_repo, '--mode=bare']) + rpmostree_argv.append(f"--ostree-repo={tmp_ostree_repo}") + override_manifest['ostree-layers'] = [] + + for dir in args.add_dir: + base = os.path.basename(dir) + # capture output to hide commit digest printed + subprocess.check_output(['ostree', 'commit', '--repo', tmp_ostree_repo, '-b', f'overlay/{base}', dir, + '--owner-uid=0', '--owner-gid=0', '--no-xattrs', '--mode-ro-executables']) + override_manifest['ostree-layers'].append(f'overlay/{base}') + + tmp_manifest = None + if override_manifest: + override_manifest['include'] = manifest_path + tmp_manifest = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.json', delete_on_close=False) + json.dump(override_manifest, tmp_manifest) + tmp_manifest.close() + manifest_path = tmp_manifest.name - rpmostree_argv = ['rpm-ostree', 'compose', 'rootfs'] try: if args.cachedir != "": rpmostree_argv.append(f"--cachedir={args.cachedir}") @@ -66,6 +81,8 @@ def run_build_rootfs(args): finally: if tmp_manifest is not None: del tmp_manifest + if tmp_ostree_repo: + shutil.rmtree(tmp_ostree_repo) # Copy our own build configuration into the target if configured; # this is used for the first stage build. But by default *secondary* @@ -123,6 +140,7 @@ if __name__ == "__main__": build_rootfs.add_argument("--manifest", help="Use the specified manifest", action='store', default='default') build_rootfs.add_argument("--install", help="Add a package", action='append', default=[], metavar='PACKAGE') build_rootfs.add_argument("--cachedir", help="Cache repo metadata and RPMs in specified directory", action='store', default='') + build_rootfs.add_argument("--add-dir", help='Copy dir contents into the target', action='append', default=[], metavar='DIR') build_rootfs.add_argument("source_root", help="Path to the source root directory used for dnf configuration (default=/)", nargs='?', default='/') build_rootfs.add_argument("target", help="Path to the target root directory that will be generated.") build_rootfs.set_defaults(func=run_build_rootfs)