Skip to content

Commit 8c3feaa

Browse files
committed
Overhaul Galaxy job config handling.
- More options from the command-line - multiple runners, TPV shared database, singularity, etc... - Switch from job conf via XML to job conf via YAML. - Bring in dependency on galaxy-job-config-init to handle a lot of that logic. - Commands to assign binding a job config to a created Planemo profile.
1 parent 1857d05 commit 8c3feaa

12 files changed

Lines changed: 324 additions & 80 deletions

planemo/commands/cmd_config_init.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from planemo.cli import command_function
1313
from planemo.io import (
1414
info,
15+
launch_if_open_flagged,
1516
warn,
1617
)
1718

@@ -44,6 +45,7 @@
4445

4546
@click.command("config_init")
4647
@options.optional_project_arg(exists=None)
48+
@options.open_file_option()
4749
@click.option("--template", default=None)
4850
@command_function
4951
def cli(ctx, path, template=None, **kwds):
@@ -59,3 +61,4 @@ def cli(ctx, path, template=None, **kwds):
5961
with open(config_path, "w") as f:
6062
f.write(CONFIG_TEMPLATE)
6163
info(SUCCESS_MESSAGE % config_path)
64+
launch_if_open_flagged(config_path, **kwds)

planemo/commands/cmd_dockstore_init.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from planemo import options
88
from planemo.cli import command_function
9+
from planemo.io import launch_if_open_flagged
910
from planemo.workflow_lint import (
1011
DOCKSTORE_REGISTRY_CONF,
1112
generate_dockstore_yaml,
@@ -15,6 +16,7 @@
1516
@click.command("dockstore_init")
1617
@options.optional_project_arg()
1718
@options.publish_dockstore_option()
19+
@options.open_file_option()
1820
@command_function
1921
def cli(ctx, path=".", **kwds):
2022
"""Initialize a .dockstore.yml configuration file for workflows in directory.
@@ -32,3 +34,4 @@ def cli(ctx, path=".", **kwds):
3234
contents = generate_dockstore_yaml(path, kwds["publish"])
3335
with open(dockstore_path, "w") as f:
3436
f.write(contents)
37+
launch_if_open_flagged(dockstore_path, **kwds)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Module describing the planemo ``project_init`` command."""
2+
3+
import os
4+
import sys
5+
6+
import click
7+
from gxjobconfinit import build_job_config, ConfigArgs
8+
from gxjobconfinit.generate import DevelopmentContext
9+
10+
from planemo import options
11+
from planemo.galaxy.config import get_all_tool_path_from_kwds
12+
from planemo.cli import command_function
13+
from planemo.io import (
14+
info,
15+
launch_if_open_flagged,
16+
warn,
17+
)
18+
from planemo.runnable import for_paths
19+
from planemo.tools import uris_to_paths
20+
21+
SUCCESS_MESSAGE = "Wrote configuration template to %s, please open with editor to customize if needed."
22+
23+
24+
@click.command("job_config_init")
25+
@options.optional_tools_arg(multiple=True, allow_uris=True)
26+
@options.open_file_option()
27+
@options.job_config_init_options()
28+
@command_function
29+
def cli(ctx, uris, **kwds):
30+
"""Initialize an small Galaxy job config file for hosted workflow runs."""
31+
config_path = "job_conf.yml"
32+
if os.path.exists(config_path):
33+
warn(f"File '{config_path}' already exists, exiting.")
34+
sys.exit(1)
35+
36+
paths = uris_to_paths(ctx, uris)
37+
runnables = for_paths(paths)
38+
tool_paths = get_all_tool_path_from_kwds(runnables, **kwds)
39+
dev_context = DevelopmentContext(
40+
kwds.get("test_data", None),
41+
tool_paths,
42+
)
43+
init_config = ConfigArgs.from_dict(**kwds)
44+
job_config = build_job_config(init_config, dev_context)
45+
with open(config_path, "w") as f:
46+
f.write(job_config)
47+
info(SUCCESS_MESSAGE % config_path)
48+
launch_if_open_flagged(config_path, **kwds)

planemo/commands/cmd_open.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from planemo.cli import command_function
66

77

8-
@click.command("docs")
8+
@click.command("open")
99
@click.argument(
1010
"path",
1111
metavar="PATH",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Module describing the planemo ``project_init`` command."""
2+
3+
import click
4+
5+
from planemo import options
6+
from planemo.galaxy import profiles
7+
from planemo.cli import command_function
8+
from planemo.io import (
9+
info,
10+
launch_if_open_flagged,
11+
warn,
12+
)
13+
14+
SUCCESS_MESSAGE = "Wrote configuration template to %s for your profile, please open with editor to customize if needed."
15+
16+
17+
@click.command("profile_job_config_init")
18+
@options.profile_name_argument()
19+
@options.open_file_option()
20+
@options.job_config_init_options()
21+
@command_function
22+
def cli(ctx, profile_name, **kwds):
23+
"""Initialize a Galaxy job configuration for specified profile."""
24+
if not profiles.profile_exists(ctx, profile_name, **kwds):
25+
warn(f"Profile '{profile_name}' does not exist, exiting.")
26+
pass
27+
28+
config_path = profiles.initialize_job_config(ctx, profile_name, **kwds)
29+
info(SUCCESS_MESSAGE % config_path)
30+
launch_if_open_flagged(config_path, **kwds)

planemo/galaxy/config.py

Lines changed: 20 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@
1919
from typing import (
2020
Any,
2121
Dict,
22-
Iterable,
2322
List,
2423
Optional,
2524
Set,
2625
TYPE_CHECKING,
2726
)
2827

28+
from gxjobconfinit.generate import (
29+
build_job_config,
30+
ConfigArgs,
31+
create_docker_volumes,
32+
)
33+
from gxjobconfinit.generate import DevelopmentContext
34+
2935
from galaxy.tool_util.deps import docker_util
30-
from galaxy.tool_util.deps.container_volumes import DockerVolume
3136
from galaxy.util.commands import argv_to_str
3237
from packaging.version import parse as parse_version
3338

@@ -122,33 +127,6 @@
122127
</tool_sheds>
123128
"""
124129

125-
JOB_CONFIG_LOCAL = """<job_conf>
126-
<plugins>
127-
<plugin id="planemo_runner" type="runner" load="galaxy.jobs.runners.local:LocalJobRunner" workers="4"/>
128-
</plugins>
129-
<handlers>
130-
</handlers>
131-
<destinations default="planemo_dest">
132-
<destination id="planemo_dest" runner="planemo_runner">
133-
<param id="require_container">${require_container}</param>
134-
<param id="docker_enabled">${docker_enable}</param>
135-
<param id="docker_sudo">${docker_sudo}</param>
136-
<param id="docker_sudo_cmd">${docker_sudo_cmd}</param>
137-
<param id="docker_cmd">${docker_cmd}</param>
138-
<param id="docker_volumes">${docker_volumes}</param>
139-
<param id="docker_run_extra_arguments"><![CDATA[${docker_run_extra_arguments}]]></param>
140-
${docker_host_param}
141-
</destination>
142-
<destination id="upload_dest" runner="planemo_runner">
143-
<param id="docker_enabled">false</param>
144-
</destination>
145-
</destinations>
146-
<tools>
147-
<tool id="upload1" destination="upload_dest" />
148-
</tools>
149-
</job_conf>
150-
"""
151-
152130
REFGENIE_CONFIG_TEMPLATE = """
153131
config_version: %s
154132
genome_folder: '%s'
@@ -216,22 +194,6 @@ def read_log(ctx, log_path, e: threading.Event):
216194
log_fh.close()
217195

218196

219-
def create_docker_volumes(paths: Iterable[str]) -> Iterable[DockerVolume]:
220-
"""
221-
Creates string of the format "host_path:target_path:mode" and deduplicates overlapping mounts.
222-
"""
223-
docker_volumes: Dict[str, DockerVolume] = {}
224-
for path in paths:
225-
docker_volume = DockerVolume.from_str(path)
226-
if docker_volume.path in docker_volumes:
227-
# volume has been specified already, make sure we use "rw" if any of the modes are "rw"
228-
if docker_volume.mode == "rw" or docker_volumes[docker_volume.path].mode == "rw":
229-
docker_volumes[docker_volume.path].mode = "rw"
230-
else:
231-
docker_volumes[docker_volume.path] = docker_volume
232-
return docker_volumes.values()
233-
234-
235197
@contextlib.contextmanager
236198
def docker_galaxy_config(ctx, runnables, for_tests=False, **kwds):
237199
"""Set up a ``GalaxyConfig`` for Docker container."""
@@ -572,6 +534,12 @@ def get_refgenie_config(galaxy_root, refgenie_dir):
572534
return REFGENIE_CONFIG_TEMPLATE % (config_version, refgenie_dir)
573535

574536

537+
def get_all_tool_path_from_kwds(runnables: List["Runnable"], **kwds) -> Set[str]:
538+
galaxy_root = kwds.get("galaxy_root")
539+
extra_tools = kwds.get("extra_tools")
540+
return _all_tool_paths(runnables, galaxy_root, extra_tools)
541+
542+
575543
def _all_tool_paths(
576544
runnables: List["Runnable"], galaxy_root: Optional[str] = None, extra_tools: Optional[List[str]] = None
577545
) -> Set[str]:
@@ -1378,40 +1346,15 @@ def _handle_job_config_file(
13781346
):
13791347
job_config_file = kwds.get("job_config_file", None)
13801348
if not job_config_file:
1381-
template_str = JOB_CONFIG_LOCAL
1349+
dev_context = DevelopmentContext(
1350+
test_data_dir,
1351+
all_tool_paths,
1352+
)
1353+
init_config = ConfigArgs.from_dict(**kwds)
1354+
conf_contents = build_job_config(init_config, dev_context)
13821355
job_config_file = os.path.join(
13831356
config_directory,
1384-
"job_conf.xml",
1385-
)
1386-
docker_enable = str(kwds.get("docker", False))
1387-
docker_host = kwds.get("docker_host", docker_util.DEFAULT_HOST)
1388-
docker_host_param = ""
1389-
if docker_host:
1390-
docker_host_param = f"""<param id="docker_host">{docker_host}</param>"""
1391-
1392-
volumes = list(kwds.get("docker_extra_volume") or [])
1393-
if test_data_dir:
1394-
volumes.append(f"{test_data_dir}:ro")
1395-
1396-
docker_volumes_str = "$defaults"
1397-
if volumes:
1398-
# exclude tool directories, these are mounted :ro by $defaults
1399-
all_tool_dirs = {os.path.dirname(tool_path) for tool_path in all_tool_paths}
1400-
extra_volumes_str = ",".join(str(v) for v in create_docker_volumes(volumes) if v.path not in all_tool_dirs)
1401-
docker_volumes_str = f"{docker_volumes_str},{extra_volumes_str}"
1402-
1403-
conf_contents = Template(template_str).safe_substitute(
1404-
{
1405-
"server_name": server_name,
1406-
"docker_enable": docker_enable,
1407-
"require_container": "false",
1408-
"docker_sudo": str(kwds.get("docker_sudo", False)),
1409-
"docker_sudo_cmd": str(kwds.get("docker_sudo_cmd", docker_util.DEFAULT_SUDO_COMMAND)),
1410-
"docker_cmd": str(kwds.get("docker_cmd", docker_util.DEFAULT_DOCKER_COMMAND)),
1411-
"docker_host_param": docker_host_param,
1412-
"docker_volumes": docker_volumes_str,
1413-
"docker_run_extra_arguments": kwds.get("docker_run_extra_arguments", ""),
1414-
}
1357+
"job_conf.yml",
14151358
)
14161359
write_file(job_config_file, conf_contents)
14171360
kwds["job_config_file"] = job_config_file

planemo/galaxy/profiles.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import shutil
1010

1111
from galaxy.util.commands import which
12+
from gxjobconfinit import build_job_config, ConfigArgs
1213

1314
from planemo.database import create_database_source
1415
from planemo.galaxy.api import test_credentials_valid
@@ -226,6 +227,24 @@ def _profile_directory(ctx, profile_name):
226227
return os.path.join(ctx.galaxy_profiles_directory, profile_name)
227228

228229

230+
def initialize_job_config(ctx, profile_name, **kwds):
231+
profile_directory = _profile_directory(ctx, profile_name)
232+
job_config_path = os.path.join(profile_directory, "job_conf.yml")
233+
if os.path.exists(job_config_path):
234+
raise Exception(f"File '{job_config_path}' already exists, exiting.")
235+
236+
init_config = ConfigArgs.from_dict(**kwds)
237+
job_config = build_job_config(init_config)
238+
with open(job_config_path, "w") as f:
239+
f.write(job_config)
240+
241+
config, profile_config_path = _load_profile_to_json(ctx, profile_name)
242+
config["job_config_file"] = os.path.abspath(profile_config_path)
243+
with open(profile_config_path, "w") as f:
244+
json.dump(config, f)
245+
return job_config_path
246+
247+
229248
__all__ = (
230249
"create_profile",
231250
"delete_profile",

planemo/io.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,8 @@ def coalesce_return_codes(ret_codes, assert_at_least_one=False):
425425
coalesced_return_code = 255 + coalesced_return_code
426426

427427
return coalesced_return_code
428+
429+
430+
def launch_if_open_flagged(file, **kwd):
431+
if kwd.get("open"):
432+
click.launch(file)

0 commit comments

Comments
 (0)