Skip to content
Merged
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
16 changes: 13 additions & 3 deletions conan/api/subapi/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,21 @@ def package_path(self, pref: PkgReference):
return ref_layout.finalize()
return _check_folder_existence(pref, "package", ref_layout.package())

def check_integrity(self, package_list):
"""Check if the recipes and packages are corrupted (it will raise a ConanExcepcion)"""
def check_integrity(self, package_list, return_pkg_list=False):
"""
Check if the recipes and packages are corrupted
:parameter package_list: PackagesList to check
:parameter return_pkg_list: If True, return a PackagesList with corrupted artifacts
:return: PackagesList with corrupted artifacts if return_pkg_list is True
:raises: ConanExcepcion if there are corrupted artifacts and return_pkg_list is False
"""
cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf)
checker = IntegrityChecker(cache)
checker.check(package_list)
corrupted_pkg_list = checker.check(package_list)
if return_pkg_list:
return corrupted_pkg_list
if corrupted_pkg_list:
raise ConanException("There are corrupted artifacts, check the error logs")

def clean(self, package_list, source=True, build=True, download=True, temp=True,
backup_sources=False):
Expand Down
14 changes: 11 additions & 3 deletions conan/cli/commands/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ def cache_clean(conan_api: ConanAPI, parser, subparser, *args):
conan_api.cache.clean(package_list)


@conan_subcommand()
def print_list_check_integrity_json(data):
results = data["results"]
myjson = json.dumps(results, indent=4)
cli_out_write(myjson)

@conan_subcommand(formatters={"text": lambda _: (),
"json": print_list_check_integrity_json})
def cache_check_integrity(conan_api: ConanAPI, parser, subparser, *args):
"""
Check the integrity of the local cache for the given references
Expand All @@ -146,8 +152,10 @@ def cache_check_integrity(conan_api: ConanAPI, parser, subparser, *args):
else:
ref_pattern = ListPattern(args.pattern, rrev="*", package_id="*", prev="*")
package_list = conan_api.list.select(ref_pattern, package_query=args.package_query)
conan_api.cache.check_integrity(package_list)
ConanOutput().success("Integrity check: ok")

corrupted_artifacts = conan_api.cache.check_integrity(package_list, return_pkg_list=True)
return {"results": {"Local Cache": corrupted_artifacts.serialize()},
"conan_error": "There are corrupted artifacts, check the error logs" if corrupted_artifacts else ""}


@conan_subcommand(formatters={"text": print_list_text,
Expand Down
23 changes: 15 additions & 8 deletions conan/internal/cache/integrity_check.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

from conan.api.model.list import PackagesList
from conan.api.output import ConanOutput
from conan.errors import ConanException
from conan.api.model import PkgReference
from conan.api.model import RecipeReference

Expand All @@ -20,14 +20,21 @@ class IntegrityChecker:
def __init__(self, cache):
self._cache = cache

def check(self, pkg_list):
corrupted = False
def check(self, pkg_list) -> PackagesList:
corrupted_pkglist = PackagesList()
for ref, packages in pkg_list.items():
corrupted = self._recipe_corrupted(ref) or corrupted
for pref in packages:
corrupted = self._package_corrupted(pref) or corrupted
if corrupted:
raise ConanException("There are corrupted artifacts, check the error logs")
# Check if any of the packages are corrupted
if self._recipe_corrupted(ref):
# If the recipe is corrupted, all its packages are considered corrupted
corrupted_pkglist.add_ref(ref)
else:
# Do not check any binary if the recipe is corrupted
for pref in packages:
if self._package_corrupted(pref):
corrupted_pkglist.add_ref(ref)
# Cannot add package reference without having the recipe reference already added
corrupted_pkglist.add_pref(pref)
return corrupted_pkglist

def _recipe_corrupted(self, ref: RecipeReference):
layout = self._cache.recipe_layout(ref)
Expand Down
51 changes: 37 additions & 14 deletions test/integration/command/cache/test_cache_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@


@pytest.mark.parametrize("use_pkglist", [True, False])
def test_cache_integrity(use_pkglist):
@pytest.mark.parametrize("output_pkglist", [True, False])
def test_cache_integrity(use_pkglist, output_pkglist):
t = TestClient()
t.save({"conanfile.py": GenConanfile()})
t.run("create . --name pkg1 --version 1.0")
Expand All @@ -29,7 +30,14 @@ def test_cache_integrity(use_pkglist):
t.run("list *:*#* -f=json", redirect_stdout="pkglist.json")
arg = "--list=pkglist.json" if use_pkglist else "*"

t.run(f"cache check-integrity {arg}", assert_error=True)
if output_pkglist:
arg += " --format=json"

t.run(
f"cache check-integrity {arg}",
assert_error=True,
redirect_stdout="pkglist.json" if output_pkglist else None,
)
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
Expand All @@ -40,17 +48,21 @@ def test_cache_integrity(use_pkglist):
assert "pkg4/4.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out

t.run("remove pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
t.run("remove pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
t.run("remove pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
if output_pkglist:
t.run("remove --list=pkglist.json -c")
else:
t.run("remove pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
t.run("remove pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
t.run("remove pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
t.run("cache check-integrity *")
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg4/4.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out


def test_cache_integrity_missing_recipe_manifest():
@pytest.mark.parametrize("output_pkglist", [True, False])
def test_cache_integrity_missing_recipe_manifest(output_pkglist):
t = TestClient()
t.save({"conanfile.py": GenConanfile()})
t.run("create . --name pkg1 --version 1.0")
Expand All @@ -60,21 +72,25 @@ def test_cache_integrity_missing_recipe_manifest():
os.remove(manifest)
t.run("create . --name pkg3 --version=3.0")

t.run("cache check-integrity *", assert_error=True)
if output_pkglist:
t.run("cache check-integrity * -f json", assert_error=True, redirect_stdout="pkglist.json")
else:
t.run("cache check-integrity *", assert_error=True)
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: ERROR: Manifest missing" in t.out
assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "ERROR: There are corrupted artifacts, check the error logs" in t.out

t.run("remove pkg2* -c")
t.run(f"remove {'--list pkglist.json' if output_pkglist else 'pkg2*'} -c")
t.run("cache check-integrity *")
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "Integrity check: ok" in t.out


def test_cache_integrity_missing_package_manifest():
@pytest.mark.parametrize("output_pkglist", [True, False])
def test_cache_integrity_missing_package_manifest(output_pkglist):
t = TestClient()
t.save({"conanfile.py": GenConanfile()})
t.run("create . --name pkg1 --version 1.0")
Expand All @@ -84,22 +100,26 @@ def test_cache_integrity_missing_package_manifest():
os.remove(manifest)
t.run("create . --name pkg3 --version=3.0")

t.run("cache check-integrity *", assert_error=True)
if output_pkglist:
t.run("cache check-integrity * -f json", assert_error=True, redirect_stdout="pkglist.json")
else:
t.run("cache check-integrity *", assert_error=True)
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: Manifest missing" in t.out
assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "ERROR: There are corrupted artifacts, check the error logs" in t.out

t.run("remove pkg2* -c")
t.run(f"remove {'--list pkglist.json' if output_pkglist else 'pkg2*'} -c")
t.run("cache check-integrity *")
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "Integrity check: ok" in t.out


def test_cache_integrity_missing_package_conaninfo():
@pytest.mark.parametrize("output_pkglist", [True, False])
def test_cache_integrity_missing_package_conaninfo(output_pkglist):
t = TestClient()
t.save({"conanfile.py": GenConanfile()})
t.run("create . --name pkg1 --version 1.0")
Expand All @@ -108,12 +128,15 @@ def test_cache_integrity_missing_package_conaninfo():
conaninfo = os.path.join(layout.package(), "conaninfo.txt")
os.remove(conaninfo)

t.run("cache check-integrity *", assert_error=True)
if output_pkglist:
t.run("cache check-integrity * -f json", assert_error=True, redirect_stdout="pkglist.json")
else:
t.run("cache check-integrity *", assert_error=True)
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out

t.run("remove pkg2* -c")
t.run(f"remove {'--list pkglist.json' if output_pkglist else 'pkg2*'} -c")
t.run("cache check-integrity *")
assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
"#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
Expand Down
Loading