def build_rpm(package_dir: Path, arch: str, rpm: Rpm, gpg_signing_key: str) -> Path: 'Returns the filename of the built RPM.' with temp_dir(dir=package_dir) as td, tempfile.NamedTemporaryFile() as tf, \ Path.resource(__package__, 'busybox', exe=True) as busybox_path: tf.write(rpm.spec(busybox_path).encode()) tf.flush() work_dir = Path(generate_work_dir()) format_kwargs = { "quoted_arch": shlex.quote(arch), "quoted_buildroot": Path(work_dir / 'build').shell_quote(), "quoted_home": Path(work_dir / 'home').shell_quote(), "quoted_spec_file": shlex.quote(tf.name), "quoted_work_dir": work_dir.shell_quote(), # We get the uid of the current user so that we can chown the # work_dir *inside* the running container. The nspawn'd build # appliance container needs to run as root so that it can mkdir # the `work_dir` which exists at /. If we don't chown the # resulting tree that `rpmbuild` creates the rename would would # fail. "current_uid": os.getuid(), } opts = new_nspawn_opts( cmd=[ 'sh', '-uec', '''\ /usr/bin/rpmbuild \ -bb \ --target {quoted_arch} \ --buildroot {quoted_buildroot} \ {quoted_spec_file} \ && chown -R {current_uid} {quoted_work_dir} \ '''.format(**format_kwargs), ], layer=_build_appliance(), bindmount_ro=[(tf.name, tf.name), (busybox_path, busybox_path)], bindmount_rw=[(td, work_dir)], user=pwd.getpwnam('root'), setenv=['HOME={quoted_home}'.format(**format_kwargs)], ) run_non_booted_nspawn(opts, PopenArgs()) # `rpmbuild` has a non-configurable output layout, so # we'll move the resulting rpm into our package dir. rpms_dir = td / 'home/rpmbuild/RPMS' / arch rpm_name, = rpms_dir.listdir() os.rename(rpms_dir / rpm_name, package_dir / rpm_name) sign_rpm(package_dir / rpm_name, gpg_signing_key) return rpm_name
def spec(self, busybox_path: Path) -> str: format_kwargs = { **self._asdict(), 'quoted_contents': shlex.quote(f'{self.name} {self.version} {self.release}' if self. override_contents is None else self.override_contents), 'quoted_busybox_path': busybox_path.shell_quote(), } common_spec = textwrap.dedent('''\ Summary: The "{name}" package. Name: rpm-test-{name} Version: {version} Release: {release} Provides: virtual-{name} License: MIT Group: Facebook/Script Vendor: Facebook, Inc. Packager: [email protected] %description %install mkdir -p "$RPM_BUILD_ROOT"/rpm_test echo {quoted_contents} > "$RPM_BUILD_ROOT"/rpm_test/{name}.txt mkdir -p "$RPM_BUILD_ROOT"/bin ''').format(**format_kwargs) return common_spec + textwrap.dedent(('''\ %files /rpm_test/{name}.txt ''') if not self.test_post_install else ( '''\ cp {quoted_busybox_path} "$RPM_BUILD_ROOT"/bin/sh %post ''' # yum-dnf-from-snapshot prepares /dev in a subtle way to protect # host system from side-effects of rpm post-install scripts. # The command below lets us test that /dev/null is prepared # properly: if "echo > /dev/null" fails, tests will catch the # absence of post.txt '''\ echo > /dev/null && echo 'stuff' > \ "$RPM_BUILD_ROOT"/rpm_test/post.txt %files /bin/sh /rpm_test/{name}.txt ''')).format(**format_kwargs)