From 0b41717e432cdf2a60f30eb0737ef9e00d0af993 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Mon, 22 Feb 2021 11:21:56 -0500 Subject: [PATCH 1/7] deps.txt: add python3-createrepo_c Prep for https://github.com/coreos/coreos-assembler/pull/2028. (Had to split this out because Prow tests don't rebuild the container from scratch but use the buildroot image.) --- src/deps.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deps.txt b/src/deps.txt index ca21b6fb87..b9c55d3b61 100644 --- a/src/deps.txt +++ b/src/deps.txt @@ -13,7 +13,7 @@ sudo dumb-init # For composes -rpm-ostree createrepo_c openssh-clients +rpm-ostree createrepo_c openssh-clients python3-createrepo_c dnf-utils # For generating ISO images From 2c3ee582fae489b78b0ae03cbf2052fec42c16e3 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Mon, 22 Feb 2021 11:30:27 -0500 Subject: [PATCH 2/7] oscontainer: tweak handling of `oscontainer.yaml` file Make it more generic. Prep for using it for other things. --- src/cmd-upload-oscontainer | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cmd-upload-oscontainer b/src/cmd-upload-oscontainer index d684fc2924..e2591c3f66 100755 --- a/src/cmd-upload-oscontainer +++ b/src/cmd-upload-oscontainer @@ -42,12 +42,14 @@ with open(metapath) as f: configdir = os.path.abspath('src/config') oscconfigpath = f'{configdir}/oscontainer.yaml' +# XXX: fold oscontainer.yaml handling into oscontainer.py +configyaml = {} if os.path.exists(oscconfigpath): with open(oscconfigpath) as f: - c = yaml.safe_load(f) - base = c.get('base') - if base is not None: - args.from_image = base + configyaml = yaml.safe_load(f) + +if 'base' in configyaml: + args.from_image = configyaml['base'] extensions_src = 'src/config/extensions.yaml' extensions_destdir = None From e8c7553a3e7aec22c2ad9ebfe4e381509ccfc8e9 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Tue, 9 Feb 2021 16:38:31 -0500 Subject: [PATCH 3/7] cmdlib.sh: use BASH_SOURCE[0] to get self-path This is more robust than `$0` in the case where we're sourced from a script that's *not* in `/usr/lib/coreos-assembler`. --- src/cmdlib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdlib.sh b/src/cmdlib.sh index a8f5149720..0d93b55512 100755 --- a/src/cmdlib.sh +++ b/src/cmdlib.sh @@ -2,7 +2,7 @@ set -euo pipefail # Shared shell script library -DIR=$(dirname "$0") +DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") RFC3339="%Y-%m-%dT%H:%M:%SZ" # Detect what platform we are on From 8a509081f34870875114991017949ee7def0fb10 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 28 Jan 2021 17:10:30 -0500 Subject: [PATCH 4/7] Add `cosa buildextend-extensions` This elevates extensions to be "first-class" artifacts. We include a tarball of the extensions in the build dir, as well as details in `meta.json` (this can be used for example by the release browser). This command will replace the `download-extensions` script. A major advantage is that it leverages rpm-ostree's new support for extension composes: https://github.com/coreos/rpm-ostree/pull/2439 One awkward bit is that our Python code has to call back into the shell library because we need to make use of the `runvm` path in the unprivileged case, since that's where the cache is located. --- mantle/cosa/cosa_v1.go | 10 ++- mantle/cosa/schema_doc.go | 36 ++++++++++ src/cmd-buildextend-extensions | 125 +++++++++++++++++++++++++++++++++ src/cmdlib.sh | 8 +++ src/cosalib/cmdlib.py | 11 +++ src/schema/v1.json | 36 ++++++++++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100755 src/cmd-buildextend-extensions diff --git a/mantle/cosa/cosa_v1.go b/mantle/cosa/cosa_v1.go index 1c460fecec..476537de65 100644 --- a/mantle/cosa/cosa_v1.go +++ b/mantle/cosa/cosa_v1.go @@ -1,6 +1,6 @@ package cosa -// generated by "schematyper ../src/schema/v1.json -o cosa/cosa_v1.go.tmp --package=cosa --root-type=Build --ptr-for-omit" -- DO NOT EDIT +// generated by "../tools/bin/schematyper ../src/schema/v1.json -o cosa/cosa_v1.go.tmp --package=cosa --root-type=Build --ptr-for-omit" -- DO NOT EDIT type AliyunImage struct { ImageID string `json:"id"` @@ -39,6 +39,7 @@ type Build struct { CosaDelayedMetaMerge bool `json:"coreos-assembler.delayed-meta-merge,omitempty"` CosaImageChecksum string `json:"coreos-assembler.image-config-checksum,omitempty"` CosaImageVersion int `json:"coreos-assembler.image-genver,omitempty"` + Extensions *Extensions `json:"extensions,omitempty"` FedoraCoreOsParentCommit string `json:"fedora-coreos.parent-commit,omitempty"` FedoraCoreOsParentVersion string `json:"fedora-coreos.parent-version,omitempty"` Gcp *Gcp `json:"gcp,omitempty"` @@ -95,6 +96,13 @@ type Cloudartifact struct { URL string `json:"url"` } +type Extensions struct { + Manifest map[string]interface{} `json:"manifest"` + Path string `json:"path"` + RpmOstreeState string `json:"rpm-ostree-state"` + Sha256 string `json:"sha256"` +} + type Gcp struct { ImageFamily string `json:"family,omitempty"` ImageName string `json:"image"` diff --git a/mantle/cosa/schema_doc.go b/mantle/cosa/schema_doc.go index 8da96b1a56..5445cb9111 100644 --- a/mantle/cosa/schema_doc.go +++ b/mantle/cosa/schema_doc.go @@ -186,6 +186,7 @@ var generatedSchemaJSON = `{ "ibmcloud", "images", "oscontainer", + "extensions", "parent-pkgdiff", "pkgdiff", "release-payload", @@ -524,6 +525,41 @@ var generatedSchemaJSON = `{ "title":"Oscontainer", "$ref": "#/definitions/image" }, + "extensions": { + "$id":"#/properties/extensions", + "type":"object", + "title":"Extensions", + "required": [ + "path", + "sha256", + "rpm-ostree-state", + "manifest" + ], + "properties": { + "path": { + "$id": "#/artifact/Path", + "type":"string", + "title":"Path" + }, + "sha256": { + "$id": "#/artifact/sha256", + "type":"string", + "title":"SHA256" + }, + "rpm-ostree-state": { + "$id":"#/properties/extensions/items/properties/rpm-ostree-state", + "type":"string", + "title":"RpmOstreeState", + "default":"", + "minLength": 64 + }, + "manifest": { + "$id":"#/properties/extensions/items/properties/manifest", + "type":"object", + "title":"Manifest" + } + } + }, "ostree-commit": { "$id":"#/properties/ostree-commit", "type":"string", diff --git a/src/cmd-buildextend-extensions b/src/cmd-buildextend-extensions new file mode 100755 index 0000000000..ec3c36ef9a --- /dev/null +++ b/src/cmd-buildextend-extensions @@ -0,0 +1,125 @@ +#!/usr/bin/python3 -u + +import argparse +import os +import shutil +import sys + +import createrepo_c as cr + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from cosalib import cmdlib +from cosalib.builds import Builds +from cosalib.meta import GenericBuildMeta + + +def main(): + args = parse_args() + + workdir = os.path.abspath(os.getcwd()) + + builds = Builds() + builddir = builds.get_build_dir(args.build) + buildmeta = GenericBuildMeta(workdir=workdir, build=args.build) + + if 'extensions' in buildmeta and not args.force: + print(f"Extensions already exist: {buildmeta['extensions']['path']}") + print("Use --force to force a rebuild") + return + + treefile_src = 'src/config/manifest.yaml' + extensions_src = 'src/config/extensions.yaml' + if not os.path.exists(extensions_src): + raise Exception(f"Missing {extensions_src}") + + commit = buildmeta['ostree-commit'] + ostree_tarball = os.path.join(builddir, buildmeta['images']['ostree']['path']) + cmdlib.import_ostree_commit('tmp/repo', commit, ostree_tarball) + + tmpworkdir = prepare_tmpworkdir() + changed = run_rpmostree(tmpworkdir, commit, treefile_src, extensions_src) + if not changed: + # For now, rpm-ostree will always detect a change because we don't seed + # state from the previous build, so we won't hit this. Need to rework + # how change detection is wired in `cmd-build` to do this properly. + return + + outputdir = f"{tmpworkdir}/output" + with open(f'{outputdir}/.rpm-ostree-state-chksum', encoding='utf-8') as f: + rpm_ostree_state_chksum = f.read() + + pkglist = create_yumrepo(outputdir) + extensions_tarball = create_tarball(buildmeta, outputdir, tmpworkdir) + extensions_tarball_base = os.path.basename(extensions_tarball) + + buildmeta['extensions'] = { + "path": extensions_tarball_base, + "sha256": cmdlib.sha256sum_file(extensions_tarball), + "rpm-ostree-state": rpm_ostree_state_chksum, + "manifest": pkglist, + } + + cmdlib.rm_allow_noent(f'{builddir}/{extensions_tarball_base}') + shutil.move(extensions_tarball, builddir) + buildmeta.write(artifact_name='extensions') + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--build", help="Build ID", default='latest') + parser.add_argument("--force", help="Force rebuild", action='store_true') + return parser.parse_args() + + +def prepare_tmpworkdir(): + tmpworkdir = 'tmp/extensions' + if os.path.exists(tmpworkdir): + shutil.rmtree(tmpworkdir) + os.mkdir(tmpworkdir) + return tmpworkdir + + +def run_rpmostree(workdir, commit, treefile, extensions): + cmdlib.cmdlib_sh(f''' + changed_stamp={workdir}/changed + runcompose_extensions {workdir}/output {treefile} {extensions} \ + --base-rev {commit}''') + return os.path.exists(f'{workdir}/changed') + + +def create_yumrepo(repodir): + cmdlib.run_verbose(['createrepo_c', repodir]) + # we could also have rpm-ostree output the pkglist for us, but meh... we + # need to run createrepo_c anyway and it's nice that we're using it as the + # source of truth, since that's what rpm-ostree clients will also use + repomd = cr.Repomd(os.path.join(repodir, "repodata/repomd.xml")) + pkglist = {} + + def cb(pkg): + epoch = '' + if pkg.epoch and int(pkg.epoch) > 0: + epoch = f'{pkg.epoch}:' + pkglist[pkg.name] = f'{epoch}{pkg.version}-{pkg.release}.{pkg.arch}' + + for record in repomd.records: + if record.type == 'primary': + primary_xml = os.path.join(repodir, record.location_href) + cr.xml_parse_primary(primary_xml, do_files=False, pkgcb=cb) + break + + if len(pkglist) == 0: + raise Exception("No RPMs found in output dir") + return pkglist + + +def create_tarball(buildmeta, srcdir, destdir): + destdir = os.path.abspath(destdir) + basearch = buildmeta['coreos-assembler.basearch'] + tarfile = f'{destdir}/{buildmeta["name"]}-{buildmeta["buildid"]}-extensions.{basearch}.tar' + cmdlib.run_verbose(['tar', '-cf', tarfile, '.'], cwd=srcdir) + return tarfile + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/cmdlib.sh b/src/cmdlib.sh index 0d93b55512..0d811ba32c 100755 --- a/src/cmdlib.sh +++ b/src/cmdlib.sh @@ -381,6 +381,14 @@ runcompose_tree() { fi } +runcompose_extensions() { + local outputdir=$1; shift + impl_rpmostree_compose extensions "$@" --output-dir "$outputdir" + if has_privileges; then + sudo chown -R -h "${USER}":"${USER}" "${outputdir}" + fi +} + impl_rpmostree_compose() { local cmd=$1; shift local workdir=${workdir:-$(pwd)} diff --git a/src/cosalib/cmdlib.py b/src/cosalib/cmdlib.py index 7eaee371f3..bb32573309 100644 --- a/src/cosalib/cmdlib.py +++ b/src/cosalib/cmdlib.py @@ -33,6 +33,8 @@ retry_if_exception_type(IncompleteReadError) | retry_if_exception_type(ReadTimeoutError)) +THISDIR = os.path.dirname(os.path.abspath(__file__)) + def retry_callback(retry_state): print(f"Retrying after {retry_state.outcome.exception()}") @@ -309,3 +311,12 @@ def image_info(image): return out except Exception as e: raise Exception(f"failed to inspect {image} with qemu", e) + + +# Hackily run some bash code from cmdlib.sh helpers. +def cmdlib_sh(script): + subprocess.check_call(['bash', '-c', f''' + set -euo pipefail + source {THISDIR}/../cmdlib.sh + {script} + ''']) diff --git a/src/schema/v1.json b/src/schema/v1.json index 0d8e6ec35f..758a052a7f 100644 --- a/src/schema/v1.json +++ b/src/schema/v1.json @@ -181,6 +181,7 @@ "ibmcloud", "images", "oscontainer", + "extensions", "parent-pkgdiff", "pkgdiff", "release-payload", @@ -519,6 +520,41 @@ "title":"Oscontainer", "$ref": "#/definitions/image" }, + "extensions": { + "$id":"#/properties/extensions", + "type":"object", + "title":"Extensions", + "required": [ + "path", + "sha256", + "rpm-ostree-state", + "manifest" + ], + "properties": { + "path": { + "$id": "#/artifact/Path", + "type":"string", + "title":"Path" + }, + "sha256": { + "$id": "#/artifact/sha256", + "type":"string", + "title":"SHA256" + }, + "rpm-ostree-state": { + "$id":"#/properties/extensions/items/properties/rpm-ostree-state", + "type":"string", + "title":"RpmOstreeState", + "default":"", + "minLength": 64 + }, + "manifest": { + "$id":"#/properties/extensions/items/properties/manifest", + "type":"object", + "title":"Manifest" + } + } + }, "ostree-commit": { "$id":"#/properties/ostree-commit", "type":"string", From 904f3867e75a3aeae14c7ad56f369547f8af9728 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 28 Jan 2021 17:19:46 -0500 Subject: [PATCH 5/7] oscontainer: use extensions artifact, drop download-extensions Use the tarball produced by `buildextend-extensions` as the source of truth for extensions and drop `download-extensions` now that we no longer need it. --- src/cmd-upload-oscontainer | 19 +++++------ src/download-extensions | 67 -------------------------------------- src/oscontainer.py | 11 ++++++- 3 files changed, 18 insertions(+), 79 deletions(-) delete mode 100755 src/download-extensions diff --git a/src/cmd-upload-oscontainer b/src/cmd-upload-oscontainer index e2591c3f66..8e20585f83 100755 --- a/src/cmd-upload-oscontainer +++ b/src/cmd-upload-oscontainer @@ -40,6 +40,14 @@ metapath = f"{latest_build_path}/meta.json" with open(metapath) as f: meta = json.load(f) +# for backcompat, we auto-build extensions if they're missing +if os.path.exists('src/config/extensions.yaml'): + if 'extensions' not in meta: + cmdlib.run_verbose(['coreos-assembler', 'buildextend-extensions']) + with open(metapath) as f: + meta = json.load(f) + assert 'extensions' in meta + configdir = os.path.abspath('src/config') oscconfigpath = f'{configdir}/oscontainer.yaml' # XXX: fold oscontainer.yaml handling into oscontainer.py @@ -51,15 +59,6 @@ if os.path.exists(oscconfigpath): if 'base' in configyaml: args.from_image = configyaml['base'] -extensions_src = 'src/config/extensions.yaml' -extensions_destdir = None -if os.path.exists(extensions_src): - extensions_destdir = 'tmp/extensions' - if os.path.exists(extensions_destdir): - shutil.rmtree(extensions_destdir) - os.mkdir(extensions_destdir) - cmdlib.run_verbose(['/usr/lib/coreos-assembler/download-extensions', extensions_destdir]) - print("Preparing to upload oscontainer for build: {}".format(latest_build)) ostree_commit = meta['ostree-commit'] @@ -108,8 +107,6 @@ os.environ['REGISTRY_AUTH_FILE'] = authfile cosa_argv.extend(['/usr/lib/coreos-assembler/oscontainer.py', '--workdir=./tmp', 'build', f"--from={args.from_image}"]) for d in args.add_directory: cosa_argv.append(f"--add-directory={d}") -if extensions_destdir is not None: - cosa_argv.append(f"--add-directory={extensions_destdir}") cosa_argv.append(f"--display-name={display_name}") subprocess.check_call(cosa_argv + [f'--digestfile={digestfile}', diff --git a/src/download-extensions b/src/download-extensions deleted file mode 100755 index 74d5710806..0000000000 --- a/src/download-extensions +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -# RPMs as operating system extensions, distinct from the base ostree commit/image -# https://github.com/openshift/enhancements/blob/master/enhancements/rhcos/extensions.md - -import os -import sys -import yaml -from cosalib import cmdlib - -destdir = sys.argv[1] -tmpdir = 'tmp' -# yum wants this to be absolute -configdir = os.path.abspath('src/config') -extsrcpath = f'{configdir}/extensions.yaml' -extjson = f'{tmpdir}/extensions.json' -basearch = cmdlib.get_basearch() - -with open(extsrcpath) as f: - extensions = yaml.safe_load(f) - -# The "v2" format here is that there's an extensions/ directory, with subdirectories -# for each extension - except you should ignore "repodata/". -edestdir = f'{destdir}/extensions' -os.mkdir(edestdir) - -# Stuff that's not part of the extension -dependenciesdir = f'{edestdir}/dependencies' -os.mkdir(dependenciesdir) - - -# Downloads packages from specified repos -def yumdownload(destdir, pkgs): - # FIXME eventually use rpm-ostree for this - # shellcheck disable=SC2068 - args = ['yum', f'--setopt=reposdir={configdir}', f'--arch={basearch}', 'download'] - args.extend(pkgs) - cmdlib.run_verbose(args, cwd=destdir) - - -# Reuseable function for setting up an extension -# Assumes it is running in "${destdir}/extensions" -# 1 = extension name -# 2 = package string/glob -# 3 = OPTIONAL: dependencies string/glob -def createext(extname, pkgs): - print(f"Creating extension {extname}") - extdir = f"{edestdir}/{extname}" - os.mkdir(extdir) - primary = pkgs[0] - yumdownload(extdir, [primary]) - - deps = pkgs[1:] - if len(deps) > 0: - print(f"Downloading dependencies for {extname}") - yumdownload(dependenciesdir, deps) - - -for (name, ext) in extensions['extensions'].items(): - pkgs = ext['packages'] - extarches = ext.get('architectures') - if extarches is not None and basearch not in extarches: - print(f"Skipping extension {name} for this architecture") - continue - createext(name, pkgs) - -# Create the yum/dnf repo -cmdlib.run_verbose(['createrepo_c', '--no-database', '.'], cwd=edestdir) diff --git a/src/oscontainer.py b/src/oscontainer.py index ff4eeee307..e9968ba3ce 100755 --- a/src/oscontainer.py +++ b/src/oscontainer.py @@ -169,12 +169,15 @@ def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, f.write(nevra) f.write('\n') + meta = {} + builddir = None if os.path.isfile('builds/builds.json'): with open('builds/builds.json') as fb: builds = json.load(fb)['builds'] latest_build = builds[0]['id'] arch = cmdlib.get_basearch() - metapath = f"builds/{latest_build}/{arch}/meta.json" + builddir = f"builds/{latest_build}/{arch}" + metapath = f"{builddir}/meta.json" with open(metapath) as f: meta = json.load(f) rhcos_commit = meta['coreos-assembler.container-config-git']['commit'] @@ -184,6 +187,12 @@ def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, config += ['-l', f"com.coreos.coreos-assembler-commit={cosa_commit}"] config += ['-l', f"com.coreos.redhat-coreos-commit={rhcos_commit}"] + if 'extensions' in meta: + tarball = os.path.abspath(os.path.join(builddir, meta['extensions']['path'])) + dest_dir = os.path.join(mnt, 'extensions') + os.makedirs(dest_dir, exist_ok=True) + run_verbose(["tar", "-xf", tarball], cwd=dest_dir) + if display_name is not None: config += ['-l', 'io.openshift.build.version-display-names=machine-os=' + display_name, '-l', 'io.openshift.build.versions=machine-os=' + ostree_version] From 99df4b3c083ec7ac1e28b1901b7143a7bcb417f8 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 28 Jan 2021 17:24:28 -0500 Subject: [PATCH 6/7] oscontainer: add com.coreos.os-extensions label This label contains a semi-colon-separated list of extensions baked in the oscontainer. This will be used by the humans and the MCO for validation: https://github.com/openshift/os/issues/409#issuecomment-764977556 --- src/oscontainer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/oscontainer.py b/src/oscontainer.py index e9968ba3ce..2ea605c6f5 100755 --- a/src/oscontainer.py +++ b/src/oscontainer.py @@ -193,6 +193,13 @@ def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, os.makedirs(dest_dir, exist_ok=True) run_verbose(["tar", "-xf", tarball], cwd=dest_dir) + with open(os.path.join(dest_dir, 'extensions.json')) as f: + extensions = json.load(f) + + extensions_label = ';'.join([ext for (ext, obj) in extensions['extensions'].items() + if obj.get('kind', 'os-extension') == 'os-extension']) + config += ['-l', f"com.coreos.os-extensions={extensions_label}"] + if display_name is not None: config += ['-l', 'io.openshift.build.version-display-names=machine-os=' + display_name, '-l', 'io.openshift.build.versions=machine-os=' + ostree_version] From 5b46c8b34a60d40322c32b12cfd14bb505c68b48 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 28 Jan 2021 17:25:47 -0500 Subject: [PATCH 7/7] oscontainer: add com.coreos.rpm.* labels These labels allow one to query the RPM version of a subset of packages without having to download them. See also: https://github.com/openshift/os/issues/409#issuecomment-764977556 --- src/cmd-upload-oscontainer | 3 +++ src/oscontainer.py | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/cmd-upload-oscontainer b/src/cmd-upload-oscontainer index 8e20585f83..2cd3d694f0 100755 --- a/src/cmd-upload-oscontainer +++ b/src/cmd-upload-oscontainer @@ -108,6 +108,9 @@ cosa_argv.extend(['/usr/lib/coreos-assembler/oscontainer.py', '--workdir=./tmp', for d in args.add_directory: cosa_argv.append(f"--add-directory={d}") cosa_argv.append(f"--display-name={display_name}") +if 'labeled-packages' in configyaml: + pkgs = ' '.join(configyaml['labeled-packages']) + cosa_argv.append(f"--labeled-packages={pkgs}") subprocess.check_call(cosa_argv + [f'--digestfile={digestfile}', '--push', tmprepo, diff --git a/src/oscontainer.py b/src/oscontainer.py index 2ea605c6f5..fea728a156 100755 --- a/src/oscontainer.py +++ b/src/oscontainer.py @@ -97,7 +97,7 @@ def oscontainer_extract(containers_storage, tmpdir, src, dest, def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, base_image, push=False, tls_verify=True, add_directories=[], cert_dir="", authfile="", digestfile=None, - display_name=None): + display_name=None, labeled_pkgs=[]): r = OSTree.Repo.new(Gio.File.new_for_path(src)) r.open(None) @@ -159,11 +159,16 @@ def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, if ostree_version is not None: config += ['-l', 'version=' + ostree_version] + base_pkgs = RpmOstree.db_query_all(r, rev, None) + for pkg in base_pkgs: + name = pkg.get_name() + if name in labeled_pkgs: + config += ['-l', f"com.coreos.rpm.{name}={pkg.get_evr()}.{pkg.get_arch()}"] + # Generate pkglist.txt in to the oscontainer at / pkg_list_dest = os.path.join(mnt, 'pkglist.txt') - pkgs = RpmOstree.db_query_all(r, rev, None) # should already be sorted, but just re-sort to be sure - nevras = sorted([pkg.get_nevra() for pkg in pkgs]) + nevras = sorted([pkg.get_nevra() for pkg in base_pkgs]) with open(pkg_list_dest, 'w') as f: for nevra in nevras: f.write(nevra) @@ -200,6 +205,11 @@ def oscontainer_build(containers_storage, tmpdir, src, ref, image_name_and_tag, if obj.get('kind', 'os-extension') == 'os-extension']) config += ['-l', f"com.coreos.os-extensions={extensions_label}"] + for pkgname in meta['extensions']['manifest']: + if pkgname in labeled_pkgs: + evra = meta['extensions']['manifest'][pkgname] + config += ['-l', f"com.coreos.rpm.{pkgname}={evra}"] + if display_name is not None: config += ['-l', 'io.openshift.build.version-display-names=machine-os=' + display_name, '-l', 'io.openshift.build.versions=machine-os=' + ostree_version] @@ -266,6 +276,7 @@ def main(): parser_build.add_argument("--display-name", help="Name used for an OpenShift component") parser_build.add_argument("--add-directory", help="Copy in all content from referenced directory DIR", metavar='DIR', action='append', default=[]) + parser_build.add_argument("--labeled-packages", help="Packages whose NEVRAs are included as labels on the image") parser_build.add_argument( "--digestfile", help="Write image digest to file", @@ -277,6 +288,10 @@ def main(): action='store_true') args = parser.parse_args() + labeled_pkgs = [] + if args.labeled_packages is not None: + labeled_pkgs = args.labeled_packages.split() + containers_storage = None tmpdir = None if args.workdir is not None: @@ -306,7 +321,8 @@ def main(): push=args.push, tls_verify=not args.disable_tls_verify, cert_dir=args.cert_dir, - authfile=args.authfile) + authfile=args.authfile, + labeled_pkgs=labeled_pkgs) finally: if containers_storage is not None and os.path.isdir(containers_storage): shutil.rmtree(containers_storage)