Skip to content

Commit 5060635

Browse files
feat(ISV-6605): Fix static-tests error message on incorrect replaces version
1 parent 1147cba commit 5060635

5 files changed

Lines changed: 154 additions & 151 deletions

File tree

docs/users/static_checks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ The test aims to verify if a bundle referenced by the `replaces` value is availa
108108
catalog version where the given bundle is going to be released to. The list of
109109
catalog version is determined by the `com.redhat.openshift.versions` annotation if present.
110110
If the annotation is not present the bundle targets all supported ocp version.
111+
It also check that specified bundle version exists in operator directory structure.
111112

112113
To fix the issue either change a range of versions where a bundle is going to be
113114
released by updating the annotation or change the `replaces` value.

operatorcert/static_tests/common/bundle.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from typing import Any
77

88
from jsonschema.validators import Draft202012Validator
9+
from operatorcert import utils
910
from operatorcert.operator_repo import Bundle
1011
from operatorcert.operator_repo.checks import CheckResult, Fail, Warn
12+
from operatorcert.static_tests.helpers import skip_fbc
1113

1214

1315
def _check_consistency(
@@ -225,3 +227,63 @@ def check_operator_version_directory_name(bundle: Bundle) -> Iterator[CheckResul
225227
f"the expected operator CSV version '{csv_version}' from "
226228
f"./{bundle.csv_file_name.relative_to(bundle.operator.repo.root)}."
227229
)
230+
231+
232+
@skip_fbc
233+
def check_replaces_availability(bundle: Bundle) -> Iterator[CheckResult]:
234+
"""
235+
Check if the current bundle and the replaced bundle support the same OCP versions
236+
237+
Args:
238+
bundle (Bundle): Operator bundle
239+
240+
Yields:
241+
Iterator[CheckResult]: Failure if the version of the replaced bundle
242+
does not match with the current bundle
243+
"""
244+
245+
replaces = bundle.csv.get("spec", {}).get("replaces")
246+
if not replaces:
247+
return
248+
delimiter = ".v" if ".v" in replaces else "."
249+
replaces_version = replaces.split(delimiter, 1)[1]
250+
251+
ver_to_dir = {
252+
x.csv_operator_version: x.operator_version
253+
for x in bundle.operator.all_bundles()
254+
}
255+
256+
if replaces_version not in ver_to_dir:
257+
yield Fail(
258+
f"{bundle} attempts to replace version '{replaces_version}' which"
259+
f" does not exist. Available versions: {sorted(ver_to_dir.keys())}"
260+
)
261+
return
262+
263+
replaces_bundle = bundle.operator.bundle(ver_to_dir[replaces_version])
264+
265+
ocp_versions_str = bundle.annotations.get("com.redhat.openshift.versions")
266+
replaces_ocp_version_str = replaces_bundle.annotations.get(
267+
"com.redhat.openshift.versions"
268+
)
269+
if ocp_versions_str == replaces_ocp_version_str:
270+
return
271+
organization = bundle.operator.repo.config.get("organization")
272+
273+
indexes = set(utils.get_ocp_supported_versions(organization, ocp_versions_str))
274+
replaces_indexes = set(
275+
utils.get_ocp_supported_versions(organization, replaces_ocp_version_str)
276+
)
277+
278+
if indexes - replaces_indexes == set():
279+
return
280+
yield Fail(
281+
f"Replaces bundle {replaces_bundle} {sorted(replaces_indexes)} does not support "
282+
f"the same OCP versions as bundle {bundle} {sorted(indexes)}. In order to fix this issue, "
283+
"align the OCP version range to match the range of the replaced bundle. "
284+
"This can be done by setting the `com.redhat.openshift.versions` annotation in the "
285+
"`metadata/annotations.yaml` file.\n"
286+
f"`{bundle}` - `{ocp_versions_str}`\n"
287+
f"`{replaces_bundle}` - `{replaces_ocp_version_str}`"
288+
)
289+
yield from []

operatorcert/static_tests/community/bundle.py

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -321,60 +321,6 @@ def check_api_version_constraints(bundle: Bundle) -> Iterator[CheckResult]:
321321
)
322322

323323

324-
@skip_fbc
325-
def check_replaces_availability(bundle: Bundle) -> Iterator[CheckResult]:
326-
"""
327-
Check if the current bundle and the replaced bundle support the same OCP versions
328-
329-
Args:
330-
bundle (Bundle): Operator bundle
331-
332-
Yields:
333-
Iterator[CheckResult]: Failure if the version of the replaced bundle
334-
does not match with the current bundle
335-
"""
336-
337-
replaces = bundle.csv.get("spec", {}).get("replaces")
338-
if not replaces:
339-
return
340-
delimiter = ".v" if ".v" in replaces else "."
341-
replaces_version = replaces.split(delimiter, 1)[1]
342-
343-
ver_to_dir = {
344-
x.csv_operator_version: x.operator_version
345-
for x in bundle.operator.all_bundles()
346-
}
347-
replaces_bundle = bundle.operator.bundle(ver_to_dir[replaces_version])
348-
349-
ocp_versions_str = bundle.annotations.get("com.redhat.openshift.versions")
350-
replaces_ocp_version_str = replaces_bundle.annotations.get(
351-
"com.redhat.openshift.versions"
352-
)
353-
if ocp_versions_str == replaces_ocp_version_str:
354-
# The annotations match, no need to check further
355-
return
356-
organization = bundle.operator.repo.config.get("organization")
357-
358-
indexes = set(utils.get_ocp_supported_versions(organization, ocp_versions_str))
359-
replaces_indexes = set(
360-
utils.get_ocp_supported_versions(organization, replaces_ocp_version_str)
361-
)
362-
363-
if indexes - replaces_indexes == set():
364-
# The replaces bundle supports all the same versions as the current bundle
365-
return
366-
yield Fail(
367-
f"Replaces bundle {replaces_bundle} {sorted(replaces_indexes)} does not support "
368-
f"the same OCP versions as bundle {bundle} {sorted(indexes)}. In order to fix this issue, "
369-
"align the OCP version range to match the range of the replaced bundle. "
370-
"This can be done by setting the `com.redhat.openshift.versions` annotation in the "
371-
"`metadata/annotations.yaml` file.\n"
372-
f"`{bundle}` - `{ocp_versions_str}`\n"
373-
f"`{replaces_bundle}` - `{replaces_ocp_version_str}`"
374-
)
375-
yield from []
376-
377-
378324
NON_FBC_SUGGESTION = (
379325
"[File Based Catalog (FBC)]"
380326
"(https://github.com/redhat-openshift-ecosystem/community-operators-prod/"

tests/static_tests/common/test_bundle.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
2-
from typing import Any
2+
from typing import Any, Optional
3+
from unittest.mock import MagicMock, patch
34

45
import pytest
56
from operatorcert.operator_repo import Repo
@@ -10,6 +11,7 @@
1011
check_validate_schema_bundle_release_config,
1112
check_network_policy_presence,
1213
check_operator_version_directory_name,
14+
check_replaces_availability,
1315
)
1416
from tests.utils import bundle_files, create_files
1517

@@ -752,3 +754,91 @@ def test_check_operator_version_directory_name(
752754
assert {
753755
(x.__class__, x.reason) for x in check_operator_version_directory_name(bundle)
754756
} == expected_results
757+
758+
759+
@pytest.mark.parametrize(
760+
"bundle_version_annotation,replaces_version_annotation,replaces_csv_value,ocp_range,expected",
761+
[
762+
pytest.param(None, None, None, [], set(), id="No replaces"),
763+
pytest.param(None, None, "hello.v0.0.1", [], set(), id="No annotations"),
764+
pytest.param(
765+
"v4.10", "v4.10", "hello.v0.0.1", [], set(), id="Same annotations"
766+
),
767+
pytest.param(
768+
"v4.15",
769+
"v4.15,v4.16",
770+
"hello.v0.0.1",
771+
[["v4.15", "v4.16"], ["v4.15", "v4.16"]],
772+
set(),
773+
id="Different annotation, versions match",
774+
),
775+
pytest.param(
776+
"v4.15",
777+
"v4.16",
778+
"hello.v0.0.1",
779+
[["v4.15", "v4.16"], ["v4.16"]],
780+
{
781+
Fail(
782+
"Replaces bundle Bundle(hello/0.0.1) ['v4.16'] does not support "
783+
"the same OCP versions as bundle Bundle(hello/0.0.2) ['v4.15', 'v4.16']. "
784+
"In order to fix this issue, align the OCP version range to match the "
785+
"range of the replaced bundle. "
786+
"This can be done by setting the `com.redhat.openshift.versions` annotation "
787+
"in the `metadata/annotations.yaml` file.\n"
788+
"`Bundle(hello/0.0.2)` - `v4.15`\n"
789+
"`Bundle(hello/0.0.1)` - `v4.16`"
790+
)
791+
},
792+
id="Different annotation, different version",
793+
),
794+
pytest.param(
795+
None,
796+
None,
797+
"hello.v0.0.5",
798+
[],
799+
{
800+
Fail(
801+
"Bundle(hello/0.0.2) attempts to replace version '0.0.5' which"
802+
" does not exist. Available versions: ['0.0.1', '0.0.2']"
803+
)
804+
},
805+
id="Nonexistent replaces version",
806+
),
807+
],
808+
)
809+
@patch("operatorcert.static_tests.common.bundle.utils.get_ocp_supported_versions")
810+
def test_check_replaces_availability(
811+
mock_get_ocp_supported_versions: MagicMock,
812+
bundle_version_annotation: str,
813+
replaces_version_annotation: str,
814+
replaces_csv_value: Optional[str],
815+
ocp_range: Any,
816+
expected: Any,
817+
tmp_path: Path,
818+
) -> None:
819+
bundle_annotation = {
820+
"com.redhat.openshift.versions": bundle_version_annotation,
821+
}
822+
replaces_bundle_annotation = {
823+
"com.redhat.openshift.versions": replaces_version_annotation,
824+
}
825+
csv = {"spec": {"replaces": replaces_csv_value}} if replaces_csv_value else {}
826+
create_files(
827+
tmp_path,
828+
bundle_files("hello", "0.0.1", annotations=replaces_bundle_annotation),
829+
bundle_files(
830+
"hello",
831+
"0.0.2",
832+
annotations=bundle_annotation,
833+
csv=csv,
834+
),
835+
)
836+
837+
mock_get_ocp_supported_versions.side_effect = ocp_range
838+
839+
repo = Repo(tmp_path)
840+
operator = repo.operator("hello")
841+
bundle = operator.bundle("0.0.2")
842+
errors = list(check_replaces_availability(bundle))
843+
844+
assert set(errors) == expected

tests/static_tests/community/test_bundle.py

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
check_dangling_bundles,
1212
check_osdk_bundle_validate_operator_framework,
1313
check_osdk_bundle_validate_operatorhub,
14-
check_replaces_availability,
1514
check_required_fields,
1615
check_using_fbc,
1716
ocp_to_k8s_ver,
@@ -511,101 +510,6 @@ def test_check_api_version_constraints(
511510
assert set(check_api_version_constraints(bundle)) == expected
512511

513512

514-
def test_check_replaces_availability_no_replaces(
515-
tmp_path: Path,
516-
) -> None:
517-
bundle_annotation = {
518-
"com.redhat.openshift.versions": "v4.10",
519-
}
520-
replaces_bundle_annotation = {
521-
"com.redhat.openshift.versions": "v4.11",
522-
}
523-
create_files(
524-
tmp_path,
525-
bundle_files("hello", "0.0.1", annotations=replaces_bundle_annotation),
526-
bundle_files(
527-
"hello",
528-
"0.0.2",
529-
annotations=bundle_annotation,
530-
),
531-
)
532-
533-
repo = Repo(tmp_path)
534-
operator = repo.operator("hello")
535-
bundle = operator.bundle("0.0.2")
536-
errors = list(check_replaces_availability(bundle))
537-
538-
assert set(errors) == set()
539-
540-
541-
@pytest.mark.parametrize(
542-
"bundle_version_annotation,replaces_version_annotation,ocp_range,expected",
543-
[
544-
pytest.param(None, None, [], set(), id="No annotations"),
545-
pytest.param("v4.10", "v4.10", [], set(), id="Same annotations"),
546-
pytest.param(
547-
"v4.15",
548-
"v4.15,v4.16",
549-
[["v4.15", "v4.16"], ["v4.15", "v4.16"]],
550-
set(),
551-
id="Different annotation, versions match",
552-
),
553-
pytest.param(
554-
"v4.15",
555-
"v4.16",
556-
[["v4.15", "v4.16"], ["v4.16"]],
557-
{
558-
Fail(
559-
"Replaces bundle Bundle(hello/0.0.1) ['v4.16'] does not support "
560-
"the same OCP versions as bundle Bundle(hello/0.0.2) ['v4.15', 'v4.16']. "
561-
"In order to fix this issue, align the OCP version range to match the "
562-
"range of the replaced bundle. "
563-
"This can be done by setting the `com.redhat.openshift.versions` annotation "
564-
"in the `metadata/annotations.yaml` file.\n"
565-
"`Bundle(hello/0.0.2)` - `v4.15`\n"
566-
"`Bundle(hello/0.0.1)` - `v4.16`"
567-
)
568-
},
569-
id="Different annotation, different version",
570-
),
571-
],
572-
)
573-
@patch("operatorcert.static_tests.community.bundle.utils.get_ocp_supported_versions")
574-
def test_check_replaces_availability(
575-
mock_get_ocp_supported_versions: MagicMock,
576-
bundle_version_annotation: str,
577-
replaces_version_annotation: str,
578-
ocp_range: Any,
579-
expected: Any,
580-
tmp_path: Path,
581-
) -> None:
582-
bundle_annotation = {
583-
"com.redhat.openshift.versions": bundle_version_annotation,
584-
}
585-
replaces_bundle_annotation = {
586-
"com.redhat.openshift.versions": replaces_version_annotation,
587-
}
588-
create_files(
589-
tmp_path,
590-
bundle_files("hello", "0.0.1", annotations=replaces_bundle_annotation),
591-
bundle_files(
592-
"hello",
593-
"0.0.2",
594-
annotations=bundle_annotation,
595-
csv={"spec": {"replaces": "hello.v0.0.1"}},
596-
),
597-
)
598-
599-
mock_get_ocp_supported_versions.side_effect = ocp_range
600-
601-
repo = Repo(tmp_path)
602-
operator = repo.operator("hello")
603-
bundle = operator.bundle("0.0.2")
604-
errors = list(check_replaces_availability(bundle))
605-
606-
assert set(errors) == expected
607-
608-
609513
@pytest.mark.parametrize(
610514
"files, bundle_to_check, expected",
611515
[

0 commit comments

Comments
 (0)