diff --git a/artcommon/artcommonlib/constants.py b/artcommon/artcommonlib/constants.py index 209ffcf8a2..874989c4e4 100644 --- a/artcommon/artcommonlib/constants.py +++ b/artcommon/artcommonlib/constants.py @@ -225,6 +225,19 @@ "extras": "ocp-art-advisory", "microshift-bootc": "ocp-art-advisory", } +LP_RPA_KINDS = { + "oadp": "oadp-advisory", + "mta": "mta-advisory", + "rhmtc": "mtc-advisory", + "quay": "quay-advisory", + "multicluster-engine": "mce-advisory", + "rhacm2": "acm-advisory", + "cert-manager": "cm-advisory", + "external-secrets-operator": "eso-advisory", + "zero-trust-workload-identity-manager": "zt-advisory", + "openshift-logging": "logging-advisory", + "logging": "logging-advisory", +} OCP_RPA_ENVS = ["stage", "prod"] COREOS_RHEL10_STREAMS = [ diff --git a/elliott/elliottlib/cli/konflux_release_cli.py b/elliott/elliottlib/cli/konflux_release_cli.py index 1d65afbc4a..25ea8ffcfc 100644 --- a/elliott/elliottlib/cli/konflux_release_cli.py +++ b/elliott/elliottlib/cli/konflux_release_cli.py @@ -7,7 +7,7 @@ import aiohttp import click from artcommonlib import logutil -from artcommonlib.constants import OCP_RPA_BASE_URL, OCP_RPA_ENVS, OCP_RPA_KINDS +from artcommonlib.constants import LP_RPA_KINDS, OCP_RPA_BASE_URL, OCP_RPA_ENVS, OCP_RPA_KINDS from artcommonlib.util import ( get_utc_now_formatted_str, new_roundtrip_yaml_handler, @@ -88,27 +88,47 @@ async def _validate_snapshot_against_single_rpa(kind: str, rpa_name: str, snapsh async def validate_snapshot_against_rpa(group: str, env: str, kind: str, snapshot_components: List[str]) -> None: - match = re.fullmatch(r"openshift-(\d+)\.(\d+)", group) - if group.startswith("openshift-") and not match: - raise ValueError(f"Unrecognized openshift group format, refusing to skip RPA validation: {group!r}") - if not match: - return - major, minor = match.group(1), match.group(2) - # FBC allowedPackages use OLM package names which don't match Konflux component names if kind == "fbc": LOGGER.info("Skipping RPA validation for FBC releases (different naming scheme)") return - if kind not in OCP_RPA_KINDS: - raise ValueError(f"Unsupported release kind for RPA validation: {kind!r}. Supported: {sorted(OCP_RPA_KINDS)}") - if env not in OCP_RPA_ENVS: raise ValueError(f"Unsupported release env for RPA validation: {env!r}. Supported: {OCP_RPA_ENVS}") + # Detect OCP vs LP product: check OCP format first, then LP products from allow-list + ocp_match = re.fullmatch(r"openshift-(\d+)\.(\d+)", group) + if ocp_match: + # OCP path: openshift-X.Y + major, minor = ocp_match.group(1), ocp_match.group(2) + if kind not in OCP_RPA_KINDS: + raise ValueError( + f"Unsupported release kind for RPA validation: {kind!r}. Supported: {sorted(OCP_RPA_KINDS)}" + ) + rpa_base = OCP_RPA_KINDS[kind] + elif group.startswith("openshift-"): + # Unrecognized openshift format + raise ValueError(f"Unrecognized openshift group format, refusing to skip RPA validation: {group!r}") + else: + # LP path: {product}-{major}.{minor}, check against allow-list + lp_match = re.match(r"^([a-z0-9-]+)-(\d+)\.(\d+)$", group) + if not lp_match: + LOGGER.info(f"Skipping RPA validation for unrecognized group format: {group!r}") + return + + product_name = lp_match.group(1) + if product_name not in LP_RPA_KINDS: + LOGGER.info(f"Skipping RPA validation for unsupported LP product: {product_name!r}") + return + + major, minor = lp_match.group(2), lp_match.group(3) + if kind != "image": + raise ValueError(f"Unsupported release kind for LP RPA validation: {kind!r}. LP supports 'image' only") + rpa_base = LP_RPA_KINDS[product_name] + envs_to_check = [env] + [e for e in OCP_RPA_ENVS if e != env] for check_env in envs_to_check: - rpa_name = f"{OCP_RPA_KINDS[kind]}-{check_env}-{major}-{minor}" + rpa_name = f"{rpa_base}-{check_env}-{major}-{minor}" await _validate_snapshot_against_single_rpa(kind, rpa_name, snapshot_components) diff --git a/elliott/tests/test_konflux_release_cli.py b/elliott/tests/test_konflux_release_cli.py index 392d32c3b1..ecaa2bd98d 100644 --- a/elliott/tests/test_konflux_release_cli.py +++ b/elliott/tests/test_konflux_release_cli.py @@ -788,8 +788,42 @@ async def test_validate_rpa_checks_both_envs_stage_first(self, mock_fetch_rpa): self.assertEqual(calls, ["ocp-art-advisory-stage-4-18", "ocp-art-advisory-prod-4-18"]) @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock) - async def test_validate_rpa_skipped_for_non_openshift(self, mock_fetch_rpa): - await validate_snapshot_against_rpa("oadp-1.5", "prod", "image", ["comp1"]) + async def test_validate_rpa_lp_oadp_success(self, mock_fetch_rpa): + rpa_data = {"spec": {"data": {"mapping": {"components": [{"name": "oadp-1-5-oadp-operator"}]}}}} + mock_fetch_rpa.return_value = rpa_data + + await validate_snapshot_against_rpa("oadp-1.5", "prod", "image", ["oadp-1-5-oadp-operator"]) + self.assertEqual(mock_fetch_rpa.await_count, 2) + mock_fetch_rpa.assert_any_await("oadp-advisory-prod-1-5") + mock_fetch_rpa.assert_any_await("oadp-advisory-stage-1-5") + + @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock) + async def test_validate_rpa_lp_mta_success(self, mock_fetch_rpa): + rpa_data = {"spec": {"data": {"mapping": {"components": [{"name": "mta-8-2-mta-operator"}]}}}} + mock_fetch_rpa.return_value = rpa_data + + await validate_snapshot_against_rpa("mta-8.2", "stage", "image", ["mta-8-2-mta-operator"]) + self.assertEqual(mock_fetch_rpa.await_count, 2) + calls = [c.args[0] for c in mock_fetch_rpa.await_args_list] + self.assertEqual(calls, ["mta-advisory-stage-8-2", "mta-advisory-prod-8-2"]) + + @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock) + async def test_validate_rpa_lp_logging_success(self, mock_fetch_rpa): + rpa_data = {"spec": {"data": {"mapping": {"components": [{"name": "logging-6-6-logging-operator"}]}}}} + mock_fetch_rpa.return_value = rpa_data + + await validate_snapshot_against_rpa("logging-6.6", "prod", "image", ["logging-6-6-logging-operator"]) + self.assertEqual(mock_fetch_rpa.await_count, 2) + mock_fetch_rpa.assert_any_await("logging-advisory-prod-6-6") + + @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock) + async def test_validate_rpa_lp_unsupported_product_skipped(self, mock_fetch_rpa): + await validate_snapshot_against_rpa("unknown-product-1.0", "prod", "image", ["comp1"]) + mock_fetch_rpa.assert_not_called() + + @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock) + async def test_validate_rpa_lp_invalid_format_skipped(self, mock_fetch_rpa): + await validate_snapshot_against_rpa("invalid_group_format", "prod", "image", ["comp1"]) mock_fetch_rpa.assert_not_called() @patch("elliottlib.cli.konflux_release_cli.fetch_rpa", new_callable=AsyncMock)