From f88d6cc50aea5cb6e65c3cb83a68002de28438d6 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Mon, 9 Sep 2019 14:07:56 -0400 Subject: [PATCH 1/6] Allow InteractiveTools to use path-based proxying when requires_domain=False To the right, to the right, to the right, to the right --- config/galaxy.yml.interactivetools | 22 +++++++++--- lib/galaxy/managers/interactivetool.py | 20 ++++++----- lib/galaxy/model/__init__.py | 3 +- lib/galaxy/model/mapping.py | 1 + .../0159_it_entrypoint_requires_domain.py | 36 +++++++++++++++++++ lib/galaxy/tools/__init__.py | 2 -- lib/galaxy/tools/evaluation.py | 4 +-- 7 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py diff --git a/config/galaxy.yml.interactivetools b/config/galaxy.yml.interactivetools index fe2d5892086f..48909a654c2c 100644 --- a/config/galaxy.yml.interactivetools +++ b/config/galaxy.yml.interactivetools @@ -14,13 +14,27 @@ uwsgi: interactivetools_map: database/interactivetools_map.sqlite python-raw: scripts/interactivetools/key_type_token_mapping.py - route-host: ^([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)-([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.(interactivetool\.localhost:8080)$ goto:interactivetool - route-run: goto:endendend - route-label: interactivetool + route-host: ^([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)-([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.(interactivetool\.localhost:8080)$ goto:itdomain + route-run: goto:itdomainend + route-label: itdomain route-host: ^([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)-([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\.(interactivetool\.localhost:8080)$ rpcvar:TARGET_HOST rtt_key_type_token_mapper_cached $1 $3 $2 $4 $0 5 route-if-not: empty:${TARGET_HOST} httpdumb:${TARGET_HOST} route: .* break:404 Not Found - route-label: endendend + route-label: itdomainend + # Path-based is currently less functional and less tested than domain-based + route: ^(/interactivetool/access/)([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)(()|(/.*)*)$ goto:itpath + route-run: goto:itpathend + route-label: itpath + route: ^/interactivetool/access/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)(()|/.*)$ rpcvar:TARGET_HOST rtt_key_type_token_mapper_cached $2 $1 $3 $4 $0 5 + route-if: empty:${TARGET_HOST} goto:itpathfail + route: ^/interactivetool/access/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)/([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)((()|/.*)*)$ rewrite:$4 + route-if: empty:${PATH_INFO} addvar:PATH_INFO=/ + route-run: seturi:${PATH_INFO} + route-if-not: empty:${QUERY_STRING} seturi:${PATH_INFO}?${QUERY_STRING} + route-if-not: empty:${TARGET_HOST} httpdumb:${TARGET_HOST} + route-label: itpathfail + route: .* break:404 Not Found + route-label: itpathend galaxy: diff --git a/lib/galaxy/managers/interactivetool.py b/lib/galaxy/managers/interactivetool.py index 4596f2b56918..19ce3f4c7cd5 100644 --- a/lib/galaxy/managers/interactivetool.py +++ b/lib/galaxy/managers/interactivetool.py @@ -143,7 +143,7 @@ def __init__(self, app): def create_entry_points(self, job, tool, entry_points=None, flush=True): entry_points = entry_points or tool.ports for entry in entry_points: - ep = self.model.InteractiveToolEntryPoint(job=job, tool_port=entry['port'], entry_url=entry['url'], name=entry['name']) + ep = self.model.InteractiveToolEntryPoint(job=job, tool_port=entry['port'], entry_url=entry['url'], name=entry['name'], requires_domain=entry['requires_domain']) self.sa_session.add(ep) if flush: self.sa_session.flush() @@ -254,13 +254,17 @@ def remove_entry_point(self, entry_point, flush=True): def target_if_active(self, trans, entry_point): if entry_point.active and not entry_point.deleted: request_host = trans.request.host - protocol = trans.request.host_url.split('//', 1)[0] - entry_point_encoded_id = trans.security.encode_id(entry_point.id) - entry_point_class = entry_point.__class__.__name__.lower() - entry_point_prefix = self.app.config.interactivetools_prefix - interactivetools_proxy_host = self.app.config.interactivetools_proxy_host or request_host - rval = '{}//{}-{}.{}.{}.{}/'.format(protocol, entry_point_encoded_id, - entry_point.token, entry_point_class, entry_point_prefix, interactivetools_proxy_host) + if entry_point.requires_domain: + rval = '{}//{}-{}.{}.{}.{}/'.format(trans.request.host_url.split('//', 1)[0], + trans.security.encode_id(entry_point.id), + entry_point.token, + entry_point.__class__.__name__.lower(), + self.app.config.interactivetool_prefix, request_host) + else: + rval = self.app.url_for('{}/access/{}/{}/{}/'.format(trans.app.config.interactivetool_prefix, + entry_point.__class__.__name__.lower(), + trans.security.encode_id(entry_point.id), + entry_point.token)) if entry_point.entry_url: rval = '{}/{}'.format(rval.rstrip('/'), entry_point.entry_url.lstrip('/')) return rval diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 502c7eff22de..de1907a67756 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1675,7 +1675,7 @@ class InteractiveToolEntryPoint(Dictifiable, RepresentById): dict_element_visible_keys = ['id', 'name', 'active', 'created_time', 'modified_time'] def __init__(self, job=None, name=None, token=None, tool_port=None, host=None, port=None, protocol=None, - entry_url=None, info=None, configured=False, deleted=False): + entry_url=None, requires_domain=True, info=None, configured=False, deleted=False): self.job = job self.name = name if not token: @@ -1686,6 +1686,7 @@ def __init__(self, job=None, name=None, token=None, tool_port=None, host=None, p self.port = port self.protocol = protocol self.entry_url = entry_url + self.requires_domain = requires_domain self.info = info or {} self.configured = configured self.deleted = deleted diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index af9ded84c70e..52c933e2e7f5 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -811,6 +811,7 @@ Column("port", Integer), Column("protocol", TEXT), Column("entry_url", TEXT), + Column("requires_domain", Boolean, default=True), Column("info", JSONType, nullable=True), Column("configured", Boolean, default=False), Column("deleted", Boolean, default=False), diff --git a/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py b/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py new file mode 100644 index 000000000000..11a61cbb675e --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py @@ -0,0 +1,36 @@ +""" +Adds requires_domain column to InteractiveTools Entry Point (interactivetool_entry_point). +""" +import datetime +import logging + +from sqlalchemy import ( + Column, + MetaData, + Boolean +) + +from galaxy.model.migrate.versions.util import ( + add_column, + drop_column +) + +log = logging.getLogger(__name__) +now = datetime.datetime.utcnow +metadata = MetaData() + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + requires_domain = Column("requires_domain", Boolean, default=None) + add_column(requires_domain, 'interactivetool_entry_point', metadata) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + drop_column('requires_domain', 'interactivetool_entry_point', metadata) diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index c6d59210488f..0dec242d5533 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -2656,8 +2656,6 @@ class InteractiveTool(Tool): def __init__(self, config_file, tool_source, app, **kwd): assert app.config.interactivetools_enable, ValueError('Trying to load an InteractiveTool, but InteractiveTools are not enabled.') super().__init__(config_file, tool_source, app, **kwd) - for port in self.ports: - assert port.get('requires_domain', None), ValueError('InteractiveTools currently only work when requires_domain is True for each entry_point.') def __remove_interactivetool_by_job(self, job): if job: diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index f9fb3ca80aa6..be23579328d0 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -399,9 +399,9 @@ def __populate_interactivetools(self, param_dict): it = [] for ep in getattr(self.tool, 'ports', []): ep_dict = {} - for key in 'port', 'name', 'url': + for key in 'port', 'name', 'url', 'requires_domain': val = ep.get(key, None) - if val is not None: + if val is not None and not isinstance(val, bool): val = fill_template(val, context=param_dict, python_template_version=self.tool.python_template_version) clean_val = [] for line in val.split('\n'): From 97aa955901a31441e7b49258f39ce2bd6309c051 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Mon, 9 Sep 2019 14:58:25 -0400 Subject: [PATCH 2/6] import order lint --- .../migrate/versions/0159_it_entrypoint_requires_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py b/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py index 11a61cbb675e..bbc2a402addf 100644 --- a/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py +++ b/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py @@ -5,9 +5,9 @@ import logging from sqlalchemy import ( + Boolean, Column, MetaData, - Boolean ) from galaxy.model.migrate.versions.util import ( From ba890399cfe7d50ab63219e150cbeeefc2d20778 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 16 Jul 2020 13:43:57 -0400 Subject: [PATCH 3/6] update migration version --- ...t_requires_domain.py => 0168_it_entrypoint_requires_domain.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/galaxy/model/migrate/versions/{0159_it_entrypoint_requires_domain.py => 0168_it_entrypoint_requires_domain.py} (100%) diff --git a/lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py b/lib/galaxy/model/migrate/versions/0168_it_entrypoint_requires_domain.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0159_it_entrypoint_requires_domain.py rename to lib/galaxy/model/migrate/versions/0168_it_entrypoint_requires_domain.py From 0ee0090bafff86285279b39f029e0cb31757d5e3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 16 Jul 2020 15:46:24 -0400 Subject: [PATCH 4/6] fix for url generation --- lib/galaxy/managers/interactivetool.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/managers/interactivetool.py b/lib/galaxy/managers/interactivetool.py index 19ce3f4c7cd5..03d16c8bae91 100644 --- a/lib/galaxy/managers/interactivetool.py +++ b/lib/galaxy/managers/interactivetool.py @@ -261,9 +261,9 @@ def target_if_active(self, trans, entry_point): entry_point.__class__.__name__.lower(), self.app.config.interactivetool_prefix, request_host) else: - rval = self.app.url_for('{}/access/{}/{}/{}/'.format(trans.app.config.interactivetool_prefix, - entry_point.__class__.__name__.lower(), - trans.security.encode_id(entry_point.id), + rval = self.app.url_for('/{}/access/{}/{}/{}/'.format(entry_point_prefix, + entry_point_class, + entry_point_encoded_id, entry_point.token)) if entry_point.entry_url: rval = '{}/{}'.format(rval.rstrip('/'), entry_point.entry_url.lstrip('/')) From 89b0696ee8f73a24b06484ad4c29fce7232f4554 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 7 Jan 2021 16:47:15 +0100 Subject: [PATCH 5/6] Update migration number --- ...t_requires_domain.py => 0172_it_entrypoint_requires_domain.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/galaxy/model/migrate/versions/{0168_it_entrypoint_requires_domain.py => 0172_it_entrypoint_requires_domain.py} (100%) diff --git a/lib/galaxy/model/migrate/versions/0168_it_entrypoint_requires_domain.py b/lib/galaxy/model/migrate/versions/0172_it_entrypoint_requires_domain.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0168_it_entrypoint_requires_domain.py rename to lib/galaxy/model/migrate/versions/0172_it_entrypoint_requires_domain.py From 4ac02b027b6715c18f10bb6ccc9adbaed2386325 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 7 Jan 2021 16:59:56 +0100 Subject: [PATCH 6/6] Fix variables, use f-strings --- lib/galaxy/managers/interactivetool.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/managers/interactivetool.py b/lib/galaxy/managers/interactivetool.py index 03d16c8bae91..0bf01d0ae4a6 100644 --- a/lib/galaxy/managers/interactivetool.py +++ b/lib/galaxy/managers/interactivetool.py @@ -254,17 +254,14 @@ def remove_entry_point(self, entry_point, flush=True): def target_if_active(self, trans, entry_point): if entry_point.active and not entry_point.deleted: request_host = trans.request.host + protocol = trans.request.host_url.split('//', 1)[0] + entry_point_encoded_id = trans.security.encode_id(entry_point.id) + entry_point_class = entry_point.__class__.__name__.lower() + entry_point_prefix = self.app.config.interactivetools_prefix if entry_point.requires_domain: - rval = '{}//{}-{}.{}.{}.{}/'.format(trans.request.host_url.split('//', 1)[0], - trans.security.encode_id(entry_point.id), - entry_point.token, - entry_point.__class__.__name__.lower(), - self.app.config.interactivetool_prefix, request_host) + rval = f'{protocol}//{entry_point_encoded_id}-{entry_point.token}.{entry_point_class}.{entry_point_prefix}.{request_host}/' else: - rval = self.app.url_for('/{}/access/{}/{}/{}/'.format(entry_point_prefix, - entry_point_class, - entry_point_encoded_id, - entry_point.token)) + rval = self.app.url_for(f'/{entry_point_prefix}/access/{entry_point_class}/{entry_point_encoded_id}/{entry_point.token}/') if entry_point.entry_url: rval = '{}/{}'.format(rval.rstrip('/'), entry_point.entry_url.lstrip('/')) return rval