Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 4 additions & 34 deletions doozer/doozerlib/assembly_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,12 @@ async def check_rhcos_issues(self, rhcos_build: RHCOSBuildInspector) -> List[Ass
build_dict = external_rpms.get(entry["tag"], {}).get(package_name)
if not build_dict:
continue
installed_rpm = installed_packages.get(package_name)
if installed_rpm and installed_rpm["nvr"] != build_dict["nvr"]:
if not self._is_installed_rpm_in_tag(installed_rpm, entry["tag"]):
self.runtime.logger.info(
"Installed rpm %s in RHCOS %s (%s) is not tagged in %s; skipping check_external_packages complaint",
installed_rpm["nvr"],
rhcos_build.build_id,
rhcos_build.brew_arch,
entry["tag"],
)
continue
if entry["condition"] == "match":
desired_packages[package_name] = build_dict["nvr"]
elif entry["condition"] == "greater_equal":
# installed_rpm = installed_packages.get(package_name)
# desired_packages[package_name] = build_dict["nvr"] if not installed_rpm or compare_nvr(build_dict, installed_rpm) > 0 else installed_rpm["nvr"]
installed_rpm = installed_packages.get(package_name)
if not installed_rpm or compare_nvr(build_dict, installed_rpm) >= 0:
desired_packages[package_name] = build_dict["nvr"]
else:
Expand Down Expand Up @@ -430,19 +422,10 @@ def check_group_image_consistency(
build_dict = external_rpms.get(entry["tag"], {}).get(package_name)
if not build_dict:
continue
installed_rpm = installed_packages.get(package_name)
if installed_rpm and installed_rpm["nvr"] != build_dict["nvr"]:
if not self._is_installed_rpm_in_tag(installed_rpm, entry["tag"]):
image_meta.logger.info(
"Installed rpm %s in image %s is not tagged in %s; skipping check_external_packages complaint",
installed_rpm["nvr"],
dgk,
entry["tag"],
)
continue
if entry["condition"] == "match":
desired_packages[package_name] = build_dict["nvr"]
elif entry["condition"] == "greater_equal":
installed_rpm = installed_packages.get(package_name)
if not installed_rpm or compare_nvr(build_dict, installed_rpm) >= 0:
desired_packages[package_name] = build_dict["nvr"]
else:
Expand Down Expand Up @@ -586,19 +569,6 @@ def get_group_rpm_build_dicts(self, el_ver: int) -> Dict[str, Optional[Dict]]:

return self._rpm_build_cache[el_ver]

def _is_installed_rpm_in_tag(self, installed_rpm: Optional[Dict], tag: str) -> bool:
"""Check if an installed RPM build is tagged in the given Brew tag.
:param installed_rpm: installed package build dict (must have 'id' key), or None
:param tag: Brew tag name to check
:return: True if the installed RPM is tagged in the given tag
"""
if not installed_rpm:
return False
tag_names = {
t["name"] for t in self.brew_session.listTags(brew.KojiWrapperOpts(caching=True), build=installed_rpm["id"])
}
return tag in tag_names

def get_external_rpm_build_dicts(self):
"""Get external rpm build dicts from rhaos candidate Brew tags.
:return: a dict. key is Brew tag name, value is another (component_name, rpm_build) dict
Expand Down
3 changes: 1 addition & 2 deletions doozer/doozerlib/backend/konflux_fbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1602,8 +1602,7 @@ def _catagorize_catalog_blobs(self, blobs: List[Dict]):
raise IOError(f"Found unsupported schema: {schema}")
if not package_name:
raise IOError(f"Couldn't determine package name for unknown schema: {schema}")
blob_key = package_name if schema == "olm.deprecations" else blob["name"]
categorized_blobs.setdefault(package_name, {}).setdefault(schema, {})[blob_key] = blob
categorized_blobs.setdefault(package_name, {}).setdefault(schema, {})[blob["name"]] = blob
return categorized_blobs


Expand Down
3 changes: 1 addition & 2 deletions doozer/doozerlib/backend/rebaser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,10 +1042,9 @@ async def _update_build_dir(
from doozerlib.lockfile_prototype.rebaser_hooks import apply_dockerfile_transforms

lockfile_has_packages = bool(getattr(metadata, "lockfile_packages", None))
upgrades_dropped = getattr(metadata, "lockfile_upgrades_dropped", False)
apply_dockerfile_transforms(
dest_dir,
strip_updates=not downstream_parents or not lockfile_has_packages or upgrades_dropped,
strip_updates=not downstream_parents or not lockfile_has_packages,
logger=self._logger,
)

Expand Down
58 changes: 4 additions & 54 deletions doozer/doozerlib/cli/scan_sources_konflux.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from artcommonlib.pushd import Dir
from artcommonlib.release_util import isolate_timestamp_in_release
from artcommonlib.rhcos import get_latest_layered_rhcos_build, get_primary_container_name
from artcommonlib.rpm_utils import parse_nvr, to_nevra
from artcommonlib.rpm_utils import parse_nvr
from artcommonlib.util import deep_merge, fetch_slsa_attestation, uses_konflux_imagestream_override
from artcommonlib.variants import BuildVariant
from async_lru import alru_cache
Expand Down Expand Up @@ -629,12 +629,11 @@ async def scan_image(self, image_meta: ImageMetadata):
stage = 'external image checks'
await self.scan_external_image_changes(image_meta)

# Check for changes in image arches
stage = 'arch checks'
await self.scan_arch_changes(image_meta)

# For OCP variant, perform additional checks that don't apply to OKD
if self.variant != BuildVariant.OKD:
# Check for changes in image arches (skip for OKD - arch changes don't trigger OKD rebuilds)
stage = 'arch checks'
await self.scan_arch_changes(image_meta)
# Check for changes in the network mode (skip for OKD - it always uses open network)
stage = 'network mode checks'
await self.scan_network_mode_changes(image_meta)
Expand Down Expand Up @@ -1277,10 +1276,6 @@ async def scan_rpm_changes(self, image_meta: ImageMetadata):
# Check for changes in non-ART RPMs
build_record_inspector = KonfluxBuildRecordInspector(self.runtime, build_record)
non_latest_rpms = await build_record_inspector.find_non_latest_rpms(self.package_rpm_finder)

if non_latest_rpms:
non_latest_rpms = await self._filter_parent_inherited_rpms(image_meta, non_latest_rpms)

rebuild_hints = [
f"Outdated RPM {installed_rpm} installed in {build_record.nvr} ({arch}) when {latest_rpm} was available in repo {repo}"
for arch, non_latest in non_latest_rpms.items()
Expand All @@ -1293,51 +1288,6 @@ async def scan_rpm_changes(self, image_meta: ImageMetadata):
else:
self.logger.info('No package changes detected for %s', build_record.nvr)

async def _filter_parent_inherited_rpms(
self,
image_meta: ImageMetadata,
non_latest_rpms: dict[str, list[tuple[str, str, str]]],
) -> dict[str, list[tuple[str, str, str]]]:
"""
Filter out outdated RPMs inherited from the parent image.

If an outdated RPM is at the same version as in the parent image,
rebuilding this image cannot fix it — the parent must be updated
first. Suppressing these prevents pointless rebuild loops.
"""
parent_key = image_meta.config["from"].member
if not parent_key:
return non_latest_rpms

parent_build = self.latest_image_build_records_map.get(parent_key)
if not parent_build:
return non_latest_rpms

parent_rpms = self.package_rpm_finder.get_brew_rpms_from_build_record(parent_build)
parent_nevras = {to_nevra(rpm) for rpm in parent_rpms}

filtered = {}
suppressed = 0
for arch, rpm_list in non_latest_rpms.items():
kept = []
for installed_rpm, latest_rpm, repo in rpm_list:
if installed_rpm in parent_nevras:
suppressed += 1
else:
kept.append((installed_rpm, latest_rpm, repo))
if kept:
filtered[arch] = kept

if suppressed:
self.logger.info(
"%s: suppressed %d outdated RPMs inherited from parent %s",
image_meta.distgit_key,
suppressed,
parent_key,
)

return filtered

@skip_check_if_changing
async def scan_extra_packages(self, image_meta: ImageMetadata):
"""
Expand Down
3 changes: 1 addition & 2 deletions doozer/doozerlib/lockfile_prototype/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# RPM pseudo-packages that appear in rpmdb but are not installable via DNF
RPM_PSEUDO_PACKAGES = frozenset({"gpg-pubkey"})

VALID_PKG_NAME = re.compile(r"^[a-zA-Z0-9*][a-zA-Z0-9._+\-*]*$")
VALID_PKG_NAME = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._+\-]*$")


# rpm-lockfile-prototype stores extracted RPMDBs under
Expand All @@ -53,7 +53,6 @@
RPMDB_CACHE_ERROR_PATTERNS = [
"database disk image is malformed",
"failed loading RPMDB",
"No such file or directory",
]


Expand Down
2 changes: 0 additions & 2 deletions doozer/doozerlib/lockfile_prototype/rebaser_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ async def generate_lockfile(
pkg_names.add(name)
metadata.lockfile_packages = sorted(pkg_names)

metadata.lockfile_upgrades_dropped = generator.upgrades_dropped

return shared_dnf_cache


Expand Down
23 changes: 9 additions & 14 deletions doozer/doozerlib/lockfile_prototype/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,13 @@ async def resolve(

if rc != 0:
if image_pullspec and self._is_rpmdb_corrupt(stderr):
self._clear_rpmdb_cache(image_pullspec)
self.logger.info("Retrying rpm-lockfile-prototype after RPMDB cache error")
rc, _, stderr = await cmd_gather_async(cmd, check=False, env=env)
if rc == 0:
return LockfileData.model_validate(yaml.safe_load(out_file.read_text()))
error_summary = stderr.strip().rsplit("\n", 1)[-1]
self.logger.warning("Retry also failed (exit code %d): %s", rc, error_summary)
self.logger.debug("Full retry stderr:\n%s", stderr)
cleared = self._clear_rpmdb_cache(image_pullspec)
if cleared:
self.logger.info("Retrying rpm-lockfile-prototype after clearing corrupt RPMDB cache")
retry_rc, _, retry_stderr = await cmd_gather_async(cmd, check=False, env=env)
if retry_rc == 0:
return LockfileData.model_validate(yaml.safe_load(out_file.read_text()))
self.logger.warning("Retry also failed (exit code %d): %s", retry_rc, retry_stderr)

raise RuntimeError(f"rpm-lockfile-prototype failed (exit code {rc}): {stderr}")

Expand Down Expand Up @@ -168,9 +167,8 @@ def parse_missing_packages(error_text: str) -> set[str]:
"""
Parse missing package names from rpm-lockfile-prototype error output.

Handles the CLI format ("missing packages: X, Y"), DNF install/upgrade
errors ("No match for argument: X"), and DNF reinstall errors
("no package matched: X").
Handles both the CLI format ("missing packages: X, Y") and the
DNF format ("No match for argument: X").

Arg(s):
error_text (str): Error message from rpm-lockfile-prototype.
Expand All @@ -185,7 +183,4 @@ def parse_missing_packages(error_text: str) -> set[str]:
m = re.search(r"No match for argument:\s*(\S+)", line.strip())
if m:
missing.add(m.group(1).strip().rstrip(":"))
m = re.search(r"no package matched:\s*(\S+)", line.strip())
if m:
missing.add(m.group(1).strip().rstrip(":"))
return {p for p in missing if VALID_PKG_NAME.match(p)}
5 changes: 1 addition & 4 deletions doozer/tests/backend/test_konflux_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,10 +1015,7 @@ class TestSkipTasks(IsolatedAsyncioTestCase):
"taskRunTemplate": {"serviceAccountName": "default"},
"pipelineSpec": {
"tasks": [
{
"name": "clone-repository",
"params": [{"name": "refspec", "value": ""}, {"name": "fetchTags", "value": "false"}],
},
{"name": "clone-repository", "params": [{"name": "refspec", "value": ""}]},
{"name": "build-images", "params": [{"name": "SBOM_TYPE", "value": ""}]},
{"name": "clair-scan", "params": []},
{"name": "sast-snyk-check", "params": []},
Expand Down
13 changes: 1 addition & 12 deletions doozer/tests/backend/test_konflux_fbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,27 +680,17 @@ async def test_fetch_olm_bundle_blob(self, mock_render):
mock_render.assert_called_once_with("test-image-pullspec", migrate_level="none", auth=ANY)

def test_categorize_catalog_blobs(self):
deprecation_blob = {
"schema": "olm.deprecations",
"package": "test-package",
"entries": [
{"reference": {"schema": "olm.bundle", "name": "test-bundle.v1.0.0"}, "message": "deprecated"},
],
}
catalog_blobs = [
{"schema": "olm.package", "name": "test-package"},
{"schema": "olm.channel", "name": "test-channel", "package": "test-package"},
{"schema": "olm.bundle", "name": "test-bundle", "package": "test-package"},
deprecation_blob,
{"schema": "olm.package", "name": "test-package2"},
{"schema": "olm.channel", "name": "test-channel2", "package": "test-package2"},
{"schema": "olm.bundle", "name": "test-bundle2", "package": "test-package2"},
]
actual = self.rebaser._catagorize_catalog_blobs(catalog_blobs)
self.assertEqual(actual.keys(), {"test-package", "test-package2"})
self.assertEqual(
actual["test-package"].keys(), {"olm.package", "olm.channel", "olm.bundle", "olm.deprecations"}
)
self.assertEqual(actual["test-package"].keys(), {"olm.package", "olm.channel", "olm.bundle"})
self.assertEqual(
actual["test-package"]["olm.package"]["test-package"], {"schema": "olm.package", "name": "test-package"}
)
Expand All @@ -712,7 +702,6 @@ def test_categorize_catalog_blobs(self):
actual["test-package"]["olm.bundle"]["test-bundle"],
{"schema": "olm.bundle", "name": "test-bundle", "package": "test-package"},
)
self.assertEqual(actual["test-package"]["olm.deprecations"]["test-package"], deprecation_blob)
self.assertEqual(actual["test-package2"].keys(), {"olm.package", "olm.channel", "olm.bundle"})
self.assertEqual(
actual["test-package2"]["olm.package"]["test-package2"], {"schema": "olm.package", "name": "test-package2"}
Expand Down
Loading
Loading