comparison env/lib/python3.9/site-packages/planemo/commands/cmd_container_register.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """Module describing the planemo ``container_register`` command."""
2 import os
3 import string
4
5 import click
6 from galaxy.tool_util.deps.container_resolvers.mulled import targets_to_mulled_name
7 from galaxy.tool_util.deps.mulled.mulled_build import (
8 base_image_for_targets,
9 DEFAULT_BASE_IMAGE,
10 )
11 from galaxy.tool_util.deps.mulled.util import (
12 conda_build_target_str,
13 v2_image_name,
14 )
15
16 from planemo import options
17 from planemo.cli import command_function
18 from planemo.conda import (
19 best_practice_search,
20 build_conda_context,
21 collect_conda_target_lists_and_tool_paths
22 )
23 from planemo.git import add, branch, commit, push
24 from planemo.github_util import (
25 clone_fork_branch,
26 DEFAULT_REMOTE_NAME,
27 get_repository_object,
28 pull_request,
29 )
30 from planemo.mulled import conda_to_mulled_targets
31
32 REGISTRY_TARGET_NAME = "multi-package-containers"
33 REGISTRY_TARGET_PATH = "combinations"
34 REGISTRY_REPOSITORY = "BioContainers/multi-package-containers"
35 DEFAULT_MESSAGE = "Add container $hash.\n**Hash**: $hash\n\n**Packages**:\n$packages\nBase Image:$base_image\n\n"
36 DEFAULT_MESSAGE += "**For** :\n$tools\n\nGenerated with Planemo."
37 CONTENTS = "#targets\tbase_image\timage_build\n$targets\t$base_image\t$image_build\n"
38 BIOCONTAINERS_PLATFORM = 'linux-64'
39
40
41 @click.command('container_register')
42 @options.optional_tools_arg(multiple=True)
43 @options.recursive_option()
44 @options.mulled_namespace_option()
45 @options.conda_target_options()
46 @click.option(
47 "output_directory",
48 "--output_directory",
49 type=click.Path(
50 file_okay=False,
51 dir_okay=True,
52 resolve_path=True,
53 ),
54 default=None,
55 help=("Container registration directory (defaults to ~/.planemo/multi-package-containers."),
56 )
57 @click.option(
58 "-m",
59 "--message",
60 default=DEFAULT_MESSAGE,
61 help="Commit and pull request message template for registration interactions."
62 )
63 @click.option(
64 "--pull_request/--no_pull_request",
65 is_flag=True,
66 default=True,
67 help="Fork and create a pull request against %s for these changes." % REGISTRY_REPOSITORY
68 )
69 @click.option(
70 "--force_push/--no_force_push",
71 is_flag=True,
72 default=False,
73 help="Force push branch for pull request in case it already exists.",
74 )
75 @command_function
76 def cli(ctx, paths, **kwds):
77 """Register multi-requirement containers as needed.
78
79 BioContainers publishes all Bioconda packages automatically as individual
80 container images. These however are not enough for tools with multiple
81 best-practice requirements. Such requirements should be recorded and published
82 so that a container can be created and registered for these tools.
83 """
84 registry_target = RegistryTarget(ctx, **kwds)
85 conda_context = build_conda_context(ctx, **kwds)
86
87 combinations_added = 0
88 conda_targets_list, tool_paths_list = collect_conda_target_lists_and_tool_paths(ctx, paths, recursive=kwds["recursive"])
89 for conda_targets, tool_paths in zip(conda_targets_list, tool_paths_list):
90 ctx.vlog("Handling conda_targets [%s]" % conda_targets)
91 mulled_targets = conda_to_mulled_targets(conda_targets)
92 mulled_targets_str = "- " + "\n- ".join(map(conda_build_target_str, mulled_targets))
93
94 if len(mulled_targets) < 1:
95 ctx.log("Skipping registration, no targets discovered.")
96 continue
97
98 name = v2_image_name(mulled_targets)
99 tag = "0"
100 name_and_tag = "%s-%s" % (name, tag)
101 target_filename = os.path.join(registry_target.output_directory, "%s.tsv" % name_and_tag)
102 if os.path.exists(target_filename):
103 ctx.log("Target file '%s' already exists, skipping" % target_filename)
104 continue
105
106 if targets_to_mulled_name(mulled_targets, hash_func='v2', namespace=kwds["mulled_namespace"]):
107 ctx.vlog("quay repository already exists, skipping")
108 continue
109
110 if registry_target.has_pull_request_for(name):
111 ctx.vlog("Found matching open pull request for [%s], skipping" % name)
112 continue
113
114 best_practice_requirements = True
115 for conda_target in conda_targets:
116 best_hit, exact = best_practice_search(conda_target, conda_context=conda_context, platform=BIOCONTAINERS_PLATFORM)
117 if not best_hit:
118 ctx.log("Target [%s] is not available in best practice channels - skipping" % conda_target)
119 best_practice_requirements = False
120 if not exact:
121 ctx.log("Target version [%s] for package [%s] is not available in best practice channels - please specify the full version",
122 conda_target.version,
123 conda_target.package)
124
125 if not best_practice_requirements:
126 continue
127
128 base_image = DEFAULT_BASE_IMAGE
129 for conda_target in conda_targets:
130 base_image = base_image_for_targets([conda_target], conda_context=conda_context)
131 if base_image != DEFAULT_BASE_IMAGE:
132 ctx.log("%s requires '%s' as base image" % (conda_target, base_image))
133 break
134
135 registry_target.write_targets(ctx, target_filename, mulled_targets, tag, base_image)
136 tools_str = "\n".join(map(lambda p: "- " + os.path.basename(p), tool_paths))
137 registry_target.handle_pull_request(ctx, name, target_filename, mulled_targets_str, tools_str, base_image, **kwds)
138 combinations_added += 1
139
140
141 class RegistryTarget(object):
142 """Abstraction around mulled container registry (both directory and Github repo)."""
143
144 def __init__(self, ctx, **kwds):
145 output_directory = kwds["output_directory"]
146 pr_titles = []
147 target_repository = None
148 self.remote_name = DEFAULT_REMOTE_NAME
149 do_pull_request = kwds.get("pull_request", True)
150 if output_directory is None:
151 target_repository = os.path.join(ctx.workspace, REGISTRY_TARGET_NAME)
152 output_directory = os.path.join(target_repository, REGISTRY_TARGET_PATH)
153 self.remote_name = clone_fork_branch(
154 ctx,
155 "https://github.com/%s" % REGISTRY_REPOSITORY,
156 target_repository,
157 fork=do_pull_request,
158 ) or self.remote_name
159 pr_titles = [pr.title for pr in open_prs(ctx)]
160
161 self.do_pull_request = do_pull_request
162 self.pr_titles = pr_titles
163 self.output_directory = output_directory
164 self.target_repository = target_repository
165
166 def has_pull_request_for(self, name):
167 has_pr = False
168 if self.do_pull_request:
169 if any([name in t for t in self.pr_titles]):
170 has_pr = True
171
172 return has_pr
173
174 def handle_pull_request(self, ctx, name, target_filename, packages_str, tools_str, base_image, **kwds):
175 if self.do_pull_request:
176 message = kwds["message"]
177 message = string.Template(message).safe_substitute({
178 "hash": name,
179 "packages": packages_str,
180 "tools": tools_str,
181 "base_image": base_image,
182 })
183 branch_name = name.replace(":", "-")
184 branch(ctx, self.target_repository, branch_name, from_branch="master")
185 add(ctx, self.target_repository, target_filename)
186 commit(ctx, self.target_repository, message=message)
187 force_push = kwds.get("force_push", False)
188 push(ctx, repo_path=self.target_repository, to=self.remote_name, branch=branch_name, force=force_push)
189 pull_request(ctx, self.target_repository, message=message, repo=REGISTRY_REPOSITORY)
190
191 def write_targets(self, ctx, target_filename, mulled_targets, tag, base_image):
192 with open(target_filename, "w") as f:
193 targets = to_target_str(mulled_targets)
194 f.write(string.Template(CONTENTS).safe_substitute(
195 targets=targets,
196 base_image=base_image,
197 image_build=tag
198 )
199 )
200 ctx.log("Wrote requirements [%s] to file [%s]" % (targets, target_filename))
201
202
203 def to_target_str(targets):
204 target_strings = []
205 for target in targets:
206 if target.version:
207 target_str = "%s=%s" % (target.package_name, target.version)
208 else:
209 target_str = target.package_name
210 target_strings.append(target_str)
211 return ",".join(target_strings)
212
213
214 def open_prs(ctx):
215 repo = get_repository_object(ctx, REGISTRY_REPOSITORY)
216 prs = [pr for pr in repo.get_pulls()]
217 return prs