Last active
May 30, 2022 18:30
-
-
Save peterk87/c28167ceffbb36dcbcf5b5b225c4043d to your computer and use it in GitHub Desktop.
Get "mulled-v2-{hash}" for multi-package containers (https://github.com/BioContainers/multi-package-containers) comma-delimited multi-package definitions (like in the hash.tsv file)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import argparse | |
import hashlib | |
import sys | |
from collections import namedtuple | |
from typing import List, Dict, Tuple, Union | |
# from https://github.com/galaxyproject/galaxy/blob/f12ee5ce6d602cd4c8b4cfc2112988b84a4f255e/lib/galaxy/tool_util/deps/mulled/util.py#L185 | |
Target = namedtuple("Target", ["package_name", "version", "build", "package"]) | |
# from https://github.com/galaxyproject/galaxy/blob/f12ee5ce6d602cd4c8b4cfc2112988b84a4f255e/lib/galaxy/tool_util/deps/mulled/util.py#L188 | |
def build_target(package_name, version=None, build=None, tag=None): | |
"""Use supplied arguments to build a :class:`Target` object.""" | |
if tag is not None: | |
assert version is None | |
assert build is None | |
version, build = split_tag(tag) | |
# conda package and quay image names are lowercase | |
return Target(package_name.lower(), version, build, package_name) | |
# from https://github.com/galaxyproject/galaxy/blob/f12ee5ce6d602cd4c8b4cfc2112988b84a4f255e/lib/galaxy/tool_util/deps/mulled/util.py#L210 | |
def _simple_image_name(targets, image_build=None): | |
target = targets[0] | |
suffix = "" | |
if target.version is not None: | |
build = target.build | |
if build is None and image_build is not None and image_build != "0": | |
# Special case image_build == "0", which has been built without a suffix | |
print("WARNING: Hard-coding image build instead of using Conda build - this is not recommended.") | |
build = image_build | |
suffix += f":{target.version}" | |
if build is not None: | |
suffix += f"--{build}" | |
return f"{target.package_name}{suffix}" | |
# from https://github.com/galaxyproject/galaxy/blob/f12ee5ce6d602cd4c8b4cfc2112988b84a4f255e/lib/galaxy/tool_util/deps/mulled/util.py#L264 | |
def v2_image_name(targets, image_build=None, name_override=None): | |
"""Generate mulled hash version 2 container identifier for supplied arguments. | |
If a single target is specified, simply use the supplied name and version as | |
the repository name and tag respectively. If multiple targets are supplied, | |
hash the package names as the repository name and hash the package versions (if set) | |
as the tag. | |
>>> single_targets = [build_target("samtools", version="1.3.1")] | |
>>> v2_image_name(single_targets) | |
'samtools:1.3.1' | |
>>> single_targets = [build_target("samtools", version="1.3.1", build="py_1")] | |
>>> v2_image_name(single_targets) | |
'samtools:1.3.1--py_1' | |
>>> single_targets = [build_target("samtools", version="1.3.1")] | |
>>> v2_image_name(single_targets, image_build="0") | |
'samtools:1.3.1' | |
>>> single_targets = [build_target("samtools", version="1.3.1", build="py_1")] | |
>>> v2_image_name(single_targets, image_build="0") | |
'samtools:1.3.1--py_1' | |
>>> multi_targets = [build_target("samtools", version="1.3.1"), build_target("bwa", version="0.7.13")] | |
>>> v2_image_name(multi_targets) | |
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:4d0535c94ef45be8459f429561f0894c3fe0ebcf' | |
>>> multi_targets_on_versionless = [build_target("samtools", version="1.3.1"), build_target("bwa")] | |
>>> v2_image_name(multi_targets_on_versionless) | |
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:b0c847e4fb89c343b04036e33b2daa19c4152cf5' | |
>>> multi_targets_versionless = [build_target("samtools"), build_target("bwa")] | |
>>> v2_image_name(multi_targets_versionless) | |
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40' | |
""" | |
if name_override is not None: | |
print( | |
"WARNING: Overriding mulled image name, auto-detection of 'mulled' package attributes will fail to detect result." | |
) | |
return name_override | |
targets = list(targets) | |
if len(targets) == 1: | |
return _simple_image_name(targets, image_build=image_build) | |
else: | |
targets_order = sorted(targets, key=lambda t: t.package_name) | |
package_name_buffer = "\n".join(map(lambda t: t.package_name, targets_order)) | |
package_hash = hashlib.sha1() | |
package_hash.update(package_name_buffer.encode()) | |
versions = map(lambda t: t.version, targets_order) | |
if any(versions): | |
# Only hash versions if at least one package has versions... | |
version_name_buffer = "\n".join(map(lambda t: t.version or "null", targets_order)) | |
version_hash = hashlib.sha1() | |
version_hash.update(version_name_buffer.encode()) | |
version_hash_str = version_hash.hexdigest() | |
else: | |
version_hash_str = "" | |
if not image_build: | |
build_suffix = "" | |
elif version_hash_str: | |
# tagged verson is <version_hash>-<build> | |
build_suffix = f"-{image_build}" | |
else: | |
# tagged version is simply the build | |
build_suffix = image_build | |
suffix = "" | |
if version_hash_str or build_suffix: | |
suffix = f":{version_hash_str}{build_suffix}" | |
return f"mulled-v2-{package_hash.hexdigest()}{suffix}" | |
def combo_line_to_build_targets(s: str) -> List[Target]: | |
"""List of Target tuples from multi-package comma-delimited definition | |
See https://github.com/BioContainers/multi-package-containers/blob/master/combinations/hash.tsv for multi-package definitions built into multi-package containers | |
""" | |
out = [] | |
package = None | |
version = None | |
build = None | |
for package_version in s.split(','): | |
if '=' in package_version: | |
sp = package_version.split('=') | |
if len(sp) == 3: | |
package, version, build = sp | |
elif len(sp) == 2: | |
package, version = sp | |
else: | |
raise Exception(f'Could not parse package/version/build definition from "{package_version}" in "{s}"') | |
else: | |
package = package_version | |
version = None | |
out.append( | |
build_target( | |
package_name=package, | |
version=version, | |
build=build, | |
) | |
) | |
return out | |
def main(): | |
parser = argparse.ArgumentParser( | |
description='Get "mulled-v2-{hash}" for multi-package containers (https://github.com/BioContainers/multi-package-containers) comma-delimited multi-package definitions (like in the hash.tsv file)', | |
) | |
parser.add_argument('packages', metavar='P', type=str, nargs='+', | |
help='Multi-package string (e.g. "minimap2=2.18,samtools=1.12")') | |
args = parser.parse_args() | |
if not args.packages: | |
print('No multi-package containers specified!') | |
sys.exit(1) | |
for p in args.packages: | |
targets = combo_line_to_build_targets(p) | |
print(f'Packages : {p}') | |
print(f'# packages : {len(targets)}') | |
mulled_name = v2_image_name(targets) | |
print(f'Mulled name : {mulled_name}') | |
if ':' in mulled_name: | |
packages_hash, versions_hash = mulled_name.split(':') | |
else: | |
packages_hash, versions_hash = mulled_name, '' | |
print(f'Image Name : {packages_hash}') | |
print(f'Version/Tag Name : {versions_hash}') | |
print(f'Quay.io URL : https://quay.io/repository/biocontainers/{packages_hash}') | |
print(f'Docker pull command (quess): quay.io/biocontainers/{mulled_name}-0') | |
print() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment