diff --git a/README.md b/README.md index b769290..0a86bd3 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,44 @@ The handler should "listen" to the topic `"restart galaxy"`. [gravity]: https://github.com/galaxyproject/gravity +From release 22.01 Galaxy can serve different static content per host (e.g. subdomain) and you can set [themes][themes] per host. + +By setting `galaxy_manage_subdomain_static: yes` you enable the creation of static directories and configuration per host and by setting `galaxy_manage_themes: yes` the role will append your themes_config.yml file specified under `galaxy_themes_conf_path` to your themes files after coping them over to your galaxy server and create the respective configuration. + +In order to use this feature, you need to create the following directory structure under files/ (customizable with the `galaxy_themes_ansible_file_path` variable): + +~~~bash +files/galaxy/static +├── +│   ├── static +│   │   ├── dist (optional) +│   │   │   └── some-image.png +│   │   ├── images (optional) +│   │   │   └── more-content.jpg +│   │   └── welcome.html (optional, galaxyproject.org will be displayed otherwise.) +│   └── themes +│   └── .yml +├── +│   ├── static +│   │   ├── dist (optional) +│   │   │   ├── another-static-image.svg +│   │   │   └── more-static-content-2.svg +│   │   └── welcome.html (optional) +│   └── themes +│   └── .yml +... (and many more subdomains) +~~~ + +Where the should exactly match your subdomain's name. The subdirectories `static` and `themes` are mandatory, as well as the correctly named theme file (if you enabled `galaxy_manage_themes`), while all subdirectories in `static` are optional. +Which subdirectories and files are copied is managed by the `static_galaxy_themes_keys` variable. + +Also make sure that you set `galaxy_themes_welcome_url_prefix`, so your welcome pages are templated correctly. + +It is mandatory to set the variables under `galaxy_themes_subdomains` as shown in the example in [defaults/main.yml](defaults/main.yml). If you enabled the `galaxy_manage_host_filters` variable, you can also specify the tool sections that should be shown for each individual subdomain. + + + +[themes]: https://training.galaxyproject.org/training-material/topics/admin/tutorials/customization/tutorial.html **New options for Galaxy 18.01 and later** - `galaxy_config_style` (default: `yaml`): The type of Galaxy configuration file to write, `yaml` for the YAML format supported by uWSGI or `ini-paste` for the traditional PasteDeploy-style INI file @@ -503,3 +541,4 @@ This role was written and contributed to by the following people: - [John Chilton](https://github.com/jmchilton) - [Nate Coraor](https://github.com/natefoo) - [Helena Rasche](https://github.com/hexylena) +- [Mira Kuntz](https://github.com/mira-miracoli) \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml index b1c88c2..1489c48 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -23,6 +23,10 @@ galaxy_manage_gravity: "{{ false if __galaxy_major_version is version('22.05', ' galaxy_manage_systemd: no # For Galaxy galaxy_manage_systemd_reports: no # For Reports galaxy_manage_cleanup: no +galaxy_manage_themes: no +galaxy_manage_subdomain_static: no +galaxy_manage_host_filters: no +galaxy_auto_brand: no # automatically sets the subdomain name as brand # Control whether to output verbose task diffs (e.g. when performing a git update) when running Ansible with --diff. @@ -295,6 +299,63 @@ galaxy_app_config_default: # Everything else visualization_plugins_directory: "config/plugins/visualizations" + # Static and themes configuration, will only be added if galaxy_manage_themes + static_enabled: "{{ galaxy_manage_subdomain_static }}" + static_dir_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static/', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/', + {% endfor %} + {% endif %} } + static_images_dir_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static/images', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/images', + {% endfor %} + {% endif %} } + static_welcome_html_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static/welcome.html', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/welcome.html', + {% endfor %} + {% endif %} } + static_scripts_dir_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static/scripts', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/scripts', + {% endfor %} + {% endif %} } + static_favicon_dir_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}', + {% endfor %} + {% endif %} } + static_robots_txt_by_host: > + { {% if galaxy_manage_subdomain_static %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_static_path }}/static', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}', + {% endfor %} + {% endif %} } + themes_config_file_by_host: > + { {% if galaxy_manage_themes %} + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ subdomain.name }}.yml', + {% endfor %} + {% endif %} } + brand_by_host: > + { {% if galaxy_auto_brand %} + '{{ galaxy_themes_instance_domain }}': '{{ galaxy_themes_instance_domain }}', + {% for subdomain in galaxy_themes_subdomains %} + '{{ subdomain.name }}.{{ galaxy_themes_instance_domain}}': '{{ subdomain.name[0]|upper }}{{ subdomain.name[1:] }}', + {% endfor %} + {% endif %} } # Need to set galaxy_config_default[galaxy_app_config_section] dynamically but Ansible/Jinja2 does not make this easy galaxy_config_default: "{{ {} | combine({galaxy_app_config_section: galaxy_app_config_default}) }}" galaxy_config_merged: "{{ galaxy_config_default | combine(galaxy_config | default({}), recursive=True) }}" @@ -425,3 +486,64 @@ galaxy_systemd_env: [] # A list of additional python packages to install into galaxy's virtual environment galaxy_additional_venv_packages: [] + +# galaxy themes variables +galaxy_themes_subdomains: {} +# - name: assembly +# tool_sections: +# - "hicexplorer" +# - "graph_display_data" +# - "peak_calling" +# - "assembly" +# - "annotation" +# - "genome_diversity" +# - "multiple_alignments" +# extra_tool_labels: +# - "proteomics" +galaxy_themes_static_keys: + static_dir: "" + static_images_dir: "images/" + static_scripts_dir: "scripts/" + static_welcome_html: "welcome.html/" + static_favicon_dir: "favicon.ico" + static_robots_txt: "robots.txt" +galaxy_themes_conf_path: files/galaxy/config/themes_conf.yml +galaxy_themes_static_path: "{{ galaxy_root }}/server" +galaxy_themes_static_dir: "{{ galaxy_root }}/server/static" +galaxy_themes_welcome_url_prefix: https://usegalaxy-eu.github.io/index- +galaxy_themes_default_welcome: https://galaxyproject.org +galaxy_themes_ansible_file_path: files/galaxy/static +galaxy_themes_instance_domain: usegalaxy.eu +galaxy_themes_global_host_filters_path: "{{ galaxy_root }}/server/lib/galaxy/tool_util/toolbox/filters/global_host_filters.py" +galaxy_themes_tool_base_labels: + - "file_and_meta_tools" + - "general_text_tools" + - "genomic_file_manipulation" + - "gff" + - "common_genomics" +galaxy_themes_tool_ngs_labels: + - "specific_genomics" + - "genomics_toolkits" + +# These sections will be displayed in the tool panel for every subdomain +galaxy_themes_tool_base_sections: + - "getext" + - "send" + - "collection_operations" + - "textutil" + - "convert" + - "filter" + - "group" + - "expression_tools" + +# There sections are a collection of general NGS tools and will be used in most but not all subdomains +galaxy_themes_tool_ngs_sections: + - "deeptools" + - "bed" + - "sambam" + - "bxops" + - "fastafastq" + - "fastq_quality_control" + - "picard" + - "mapping" + - "sambam" diff --git a/tasks/copy_static_files.yml b/tasks/copy_static_files.yml new file mode 100644 index 0000000..0ccc9a4 --- /dev/null +++ b/tasks/copy_static_files.yml @@ -0,0 +1,37 @@ +--- +- name: Copy subdomain static files + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/{{ object.value }}" + mode: '0644' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + with_fileglob: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/static/{{ object.value }}*" + +- name: Copy files from dist + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/dist/" + mode: '0644' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + with_fileglob: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/static/dist/*" + +- name: Copy files from dist + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/dist/" + mode: '0644' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + with_fileglob: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/static/dist/*" + +- name: Copy custom welcome.html + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/welcome.html/index.html" + mode: '0644' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + with_fileglob: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/static/welcome.html" + when: custom_welcome.stat.exists diff --git a/tasks/main.yml b/tasks/main.yml index dfb47b3..0795ca5 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -160,3 +160,27 @@ when: galaxy_manage_cleanup tags: - galaxy_manage_cleanup + +- name: Inlcude static directory setup + ansible.builtin.include_tasks: static_dirs.yml + when: galaxy_manage_subdomain_static + tags: + - galaxy_manage_subdomain_static + +- name: Include copy themes files + ansible.builtin.include_tasks: themes.yml + # fail only if enabled but dictionary is empty + loop: "{{ galaxy_themes_subdomains if galaxy_themes_subdomains|length or \ + galaxy_manage_themes else [] }}" + loop_control: + loop_var: subdomain + when: galaxy_manage_themes + tags: + - galaxy_manage_themes + +- name: Set global host filters + ansible.builtin.template: + src: "global_host_filters.py.j2" + dest: "{{ galaxy_themes_global_host_filters_path }}" + mode: 0644 + when: galaxy_manage_host_filters diff --git a/tasks/static_dirs.yml b/tasks/static_dirs.yml new file mode 100644 index 0000000..c59ab2c --- /dev/null +++ b/tasks/static_dirs.yml @@ -0,0 +1,23 @@ +--- +- name: Create welcome.html directory for basedomain + ansible.builtin.file: + state: directory + mode: '0755' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + path: "{{ galaxy_themes_static_path }}/static/welcome.html" + +- name: Template welcome.html for basedomain + ansible.builtin.template: + src: welcome.html.j2 + dest: "{{ galaxy_themes_static_path }}/static/welcome.html/index.html" + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + mode: '0644' + +- name: Include create subdomain static dirs and copy static files + ansible.builtin.include_tasks: static_subdomain_dirs.yml + loop: "{{ galaxy_themes_subdomains if galaxy_themes_subdomains | length or \ + galaxy_manage_static else [] }}" + loop_control: + loop_var: subdomain diff --git a/tasks/static_subdomain_dirs.yml b/tasks/static_subdomain_dirs.yml new file mode 100644 index 0000000..3a305d2 --- /dev/null +++ b/tasks/static_subdomain_dirs.yml @@ -0,0 +1,51 @@ +--- +- name: Create subdomain static dirs + ansible.builtin.file: + state: directory + mode: '0755' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + path: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}" + +- name: Synchronize contents from static to static-"{{ subdomain.name }}" + ansible.posix.synchronize: + src: "{{ galaxy_themes_static_path }}/static/" + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}" + rsync_opts: ["--ignore-existing"] + delegate_to: "{{ inventory_hostname }}" + +- name: Check if welcome.html is present in files + ansible.builtin.stat: + path: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/static/welcome.html" + register: custom_welcome + +- name: Create welcome.html directory + ansible.builtin.file: + state: directory + mode: '0755' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + path: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/welcome.html" + +- name: Check if iframe for subdomain exists + ansible.builtin.uri: + url: "{{ galaxy_themes_welcome_url_prefix }}{{ subdomain.name }}.html" + return_content: true + register: galaxy_themes_use_iframe + failed_when: false + when: not custom_welcome.stat.exists + +- name: Template welcome.html for subdomains + ansible.builtin.template: + src: welcome.html.j2 + dest: "{{ galaxy_themes_static_path }}/static-{{ subdomain.name }}/welcome.html/index.html" + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + mode: '0644' + when: not custom_welcome.stat.exists + +- name: Include copy_static_files.yml + ansible.builtin.include_tasks: copy_static_files.yml + loop: "{{ galaxy_themes_static_keys | dict2items }}" + loop_control: + loop_var: object diff --git a/tasks/themes.yml b/tasks/themes.yml new file mode 100644 index 0000000..311d201 --- /dev/null +++ b/tasks/themes.yml @@ -0,0 +1,14 @@ +--- +- name: Append themes_conf.yml to all files in galaxy/config/themes + ansible.builtin.blockinfile: + block: "{{ lookup('ansible.builtin.file', galaxy_themes_conf_path) }}" + path: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/themes/{{ subdomain.name }}.yml" + +- name: Copy themes files + ansible.builtin.copy: + src: "{{ galaxy_themes_ansible_file_path }}/{{ subdomain.name }}/themes/{{ subdomain.name }}.yml" + dest: "{{ galaxy_config_dir }}" + mode: '0644' + owner: "{{ __galaxy_privsep_user_name }}" + group: "{{ __galaxy_privsep_user_group }}" + diff --git a/templates/global_host_filters.py.j2 b/templates/global_host_filters.py.j2 new file mode 100644 index 0000000..41ec121 --- /dev/null +++ b/templates/global_host_filters.py.j2 @@ -0,0 +1,67 @@ +import logging +log = logging.getLogger( __name__ ) + +def per_host_tool_labels( context, label ): + """ + This tool section filter results in different labels being displayed based on + the fqdn the user is making the request to. This could allow a single Galaxy instance + to seem like several different instances hosting different tools based on the fqdn used + to access the Galxy. This can be enabled by renaming this file to examples.py and adding + the following to the ``app:main`` section of ``galaxy.yml``: + + tool_label_filters = examples:per_host_tool_labels + """ + host = context.trans.request.host + subdomain = host.replace('.{{ galaxy_themes_instance_domain }}', '') + # Core tools used by all virtual hosts. + valid_labels = {{ galaxy_themes_tool_base_labels }} + general_ngs_labels = {{ galaxy_themes_tool_ngs_labels }} +{% if galaxy_themes_subdomains is defined %} +{% for subsite in galaxy_themes_subdomains %} + if "{{ subsite.name }}.{{ galaxy_themes_instance_domain }}" in host: + valid_labels += general_ngs_labels +{% if subsite.extra_tool_labels is defined %} + valid_labels += {{ subsite.extra_tool_labels }} +{% endif %} + return label.id in valid_labels +{% endfor %} +{% endif %} + return True + +BASE_SECTIONS = {{ galaxy_themes_tool_base_sections }} + +GENERAL_NGS_SECTIONS = {{ galaxy_themes_tool_ngs_sections }} + +DOMAIN_SECTIONS = { +{% if galaxy_themes_subdomains is defined %} +{% for subdomain in galaxy_themes_subdomains %} +{% if subdomain.tool_sections is defined %} + '{{ subdomain.name }}': GENERAL_NGS_SECTIONS + {{ subdomain.tool_sections }}, +{% endif %} +{% endfor %} +{% endif %} +} + + +def per_host_tool_sections( context, section ): + """ + This tool section filter results in different sections being displayed based on + the fqdn the user is making the request to. This could allow a single Galaxy instance + to seem like several different instances hosting different tools based on the fqdn used + to access the Galxy. This can be enabled by renaming this file to examples.py and adding + the following to the ``app:main`` section of ``galaxy.yml``: + + tool_section_filters = examples:per_host_tool_sections + """ + host = context.trans.request.host + subdomain = host.replace('.{{ galaxy_themes_instance_domain }}', '') + + # Core tools used by all virtual hosts. + # HiCtools mode: published in NAR 2018 + if host == "{{ galaxy_themes_instance_domain }}": + return True + + if subdomain in DOMAIN_SECTIONS: + return section.id in DOMAIN_SECTIONS[subdomain] or section.id in BASE_SECTIONS + else: + return True \ No newline at end of file diff --git a/templates/welcome.html.j2 b/templates/welcome.html.j2 new file mode 100644 index 0000000..0aa2a79 --- /dev/null +++ b/templates/welcome.html.j2 @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + \ No newline at end of file