Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SHA256 hashes in explicit files #14048

Merged
merged 14 commits into from
Aug 27, 2024
30 changes: 25 additions & 5 deletions conda/cli/main_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from argparse import ArgumentParser, Namespace, _SubParsersAction
from os.path import isdir, isfile

from .. import __version__

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -94,6 +96,11 @@ def configure_parser(sub_parsers: _SubParsersAction, **kwargs) -> ArgumentParser
action="store_true",
help="Add MD5 hashsum when using --explicit.",
)
p.add_argument(
"--sha256",
action="store_true",
help="Add SHA256 hashsum when using --explicit.",
)
p.add_argument(
"-e",
"--export",
Expand Down Expand Up @@ -138,6 +145,7 @@ def print_export_header(subdir):
print("# This file may be used to create an environment using:")
print("# $ conda create --name <env> --file <this file>")
print(f"# platform: {subdir}")
print(f"# created-by: conda {__version__}")


def get_packages(installed, regex):
Expand Down Expand Up @@ -244,12 +252,13 @@ def print_packages(
return exitcode


def print_explicit(prefix, add_md5=False, remove_auth=True):
def print_explicit(prefix, add_md5=False, remove_auth=True, add_sha256=False):
from ..base.constants import UNKNOWN_CHANNEL
from ..base.context import context
from ..common import url as common_url
from ..core.prefix_data import PrefixData

if add_md5 and add_sha256:
raise ValueError("Only one of add_md5 and add_sha256 can be chosen")
if not isdir(prefix):
from ..exceptions import EnvironmentLocationNotFound

Expand All @@ -263,8 +272,14 @@ def print_explicit(prefix, add_md5=False, remove_auth=True):
continue
if remove_auth:
url = common_url.remove_auth(common_url.split_anaconda_token(url)[0])
md5 = prefix_record.get("md5")
print(url + (f"#{md5}" if add_md5 and md5 else ""))
if add_md5:
md5 = prefix_record.get("md5")
print(url + (f"#{md5}" if md5 else ""))
elif add_sha256:
sha256 = prefix_record.get("sha256")
jaimergp marked this conversation as resolved.
Show resolved Hide resolved
print(url + (f"#{sha256}" if sha256 else ""))
jaimergp marked this conversation as resolved.
Show resolved Hide resolved
else:
print(url)


def execute(args: Namespace, parser: ArgumentParser) -> int:
Expand All @@ -279,6 +294,11 @@ def execute(args: Namespace, parser: ArgumentParser) -> int:

raise EnvironmentLocationNotFound(prefix)

if args.md5 and args.sha256:
from ..exceptions import ArgumentError

raise ArgumentError("Only one of --md5 and --sha256 can be specified at the same time")

regex = args.regex
if args.full_name:
regex = rf"^{regex}$"
Expand All @@ -297,7 +317,7 @@ def execute(args: Namespace, parser: ArgumentParser) -> int:
return 0

if args.explicit:
print_explicit(prefix, args.md5, args.remove_auth)
print_explicit(prefix, args.md5, args.remove_auth, args.sha256)
return 0

if args.canonical:
Expand Down
17 changes: 12 additions & 5 deletions conda/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ def conda_installed_files(prefix, exclude_self_build=False):
url_pat = re.compile(
r"(?:(?P<url_p>.+)(?:[/\\]))?"
r"(?P<fn>[^/\\#]+(?:\.tar\.bz2|\.conda))"
r"(:?#(?P<md5>[0-9a-f]{32}))?$"
r"(:?#("
jaimergp marked this conversation as resolved.
Show resolved Hide resolved
r"(?P<md5>[0-9a-f]{32})"
r"|(?P<sha256>[0-9a-f]{64})"
r"))?$"
)


Expand Down Expand Up @@ -81,11 +84,15 @@ def explicit(
m = url_pat.match(spec)
if m is None:
raise ParseError(f"Could not parse explicit URL: {spec}")
url_p, fn, md5sum = m.group("url_p"), m.group("fn"), m.group("md5")
url_p, fn = m.group("url_p"), m.group("fn")
url = join_url(url_p, fn)
# url_p is everything but the tarball_basename and the md5sum

fetch_specs.append(MatchSpec(url, md5=md5sum) if md5sum else MatchSpec(url))
# url_p is everything but the tarball_basename and the checksum
checksums = {}
if md5 := m.group("md5"):
checksums["md5"] = md5
if sha256 := m.group("sha256"):
checksums["sha256"] = sha256
fetch_specs.append(MatchSpec(url, **checksums))

if context.dry_run:
raise DryRunExit()
Expand Down
21 changes: 11 additions & 10 deletions tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def test_export(
conda_cli: CondaCLIFixture,
path_factory: PathFactoryFixture,
monkeypatch: MonkeyPatch,
request,
):
"""Test that `conda list --export` output can be used to create a similar environment."""
monkeypatch.setenv("CONDA_CHANNELS", "defaults")
Expand All @@ -39,27 +38,29 @@ def test_export(
assert output == output2


# Using --quiet here as a no-op flag for test simplicity
@pytest.mark.parametrize("checksum_flag", ("--quiet", "--md5", "--sha256"))
@pytest.mark.integration
def test_explicit(
tmp_env: TmpEnvFixture,
conda_cli: CondaCLIFixture,
path_factory: PathFactoryFixture,
request,
checksum_flag: str,
):
"""Test that `conda list --explicit` output can be used to recreate an identical environment."""
# use "cheap" packages with no dependencies
with tmp_env("pkgs/main::zlib", "conda-forge::ca-certificates") as prefix:
assert package_is_installed(prefix, "pkgs/main::zlib")
assert package_is_installed(prefix, "conda-forge::ca-certificates")

output, _, _ = conda_cli("list", "--prefix", prefix, "--explicit")
output, _, _ = conda_cli("list", "--prefix", prefix, "--explicit", checksum_flag)

env_txt = path_factory(suffix=".txt")
env_txt.write_text(output)
env_txt = path_factory(suffix=".txt")
env_txt.write_text(output)

with tmp_env("--file", env_txt) as prefix2:
assert package_is_installed(prefix2, "pkgs/main::zlib")
assert package_is_installed(prefix2, "conda-forge::ca-certificates")
with tmp_env("--file", env_txt) as prefix2:
assert package_is_installed(prefix2, "pkgs/main::zlib")
assert package_is_installed(prefix2, "conda-forge::ca-certificates")

output2, _, _ = conda_cli("list", "--prefix", prefix2, "--explicit")
assert output == output2
output2, _, _ = conda_cli("list", "--prefix", prefix2, "--explicit", checksum_flag)
assert output == output2
Loading