Skip to content

Commit fc4f684

Browse files
Add/wrapper scripts (#496)
* reverting string to parse config params * config get: changed behaviour to work with dicts * reverting config get impl for non-dict, to try and fix tests * adding wrapper script variables * fix to updated settings.yml * new default: containers_base distinct from modules_base * 1st pass at wrapper_scripts for singularity * wrapper_script: added tcl for singularity * wrapper script: added script template for real (was in gitignore) * wrapper script: fix for add case * wrapper scripts now created as executable * wrapper scripts: templates for docker/podman * wrapper scripts: bin directory under modules * wrapper scripts: docker/podman support * reduced newlines in module templates * all shell settings nearby in settings.yml * utils/fileio: make executable using stat rather than subprocess methods * wrapper script templates: protecting argument passing * wrapper script templates: protecting argument passing for docker, too * container: defined wrapper_subdir attribute * modules: defined wrappertemplatefile property * wrapper scripts: relative PATH in modulefile templates * wrapper_scripts: new function _generate_wrapper_scripts in container/base.py * first shot at adding wrapper scripts this will support two kinds of wrapper scripts - global that live in the settings.yaml, and container.yaml specific that go alongside that file. We basically will then generate aliases for any executable names that were not created as wrappers. we will want to follow this up with a command to list wrappers, and also include descriptions for the interested user, and a flag to install to select on the fly warning - parsed_names (tool, registry, etc) are now prefixed with parsed_name.<variable> and the user will need to update settings * Add/wrapper scripts - minor polishing (#497) * fixed typos in module/templates/docs.md * minor polishing in settings.yml, wrappers_subdir * docs: edits to user and developer guides, for wrapper scripts * reverted a couple of shell occurrences (docker/podman) in modules/templates/docs.md * one more edit for shell in modules/templates/docs.md * Update docs/getting_started/user-guide.rst * adding new design for templates: bases and snippets! * wrapper/templates/bases: csh always needs "set" for variables * wrappers/templates: using variable moduleDir * modules/templates lua: moduleDir always replaces {{ module_dir }} * modules/templates tcl: moduleDir always replaces {{ module_dir }} * main/container: no more need to pass module_dir to template.render for modulefiles * registry/vanessa/salad: updated wrapper scripts with moduleDir var * registry/vanessa/salad: typo in singularity_fork.sh Signed-off-by: vsoch <[email protected]> Co-authored-by: Marco De La Pierre <[email protected]> Co-authored-by: vsoch <[email protected]>
1 parent 4681abc commit fc4f684

29 files changed

+835
-129
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are:
1414
The versions coincide with releases on pip. Only major versions will be released as tags on Github.
1515

1616
## [0.0.x](https://github.com/singularityhub/singularity-hpc/tree/main) (0.0.x)
17+
- Adding support for wrapper scripts for global and container.yaml (0.0.45)
1718
- Lua and tcl module file bug fixes for shell functions and aliases (0.0.44)
1819
- restoring -B for Singularity bindpaths over using envar
1920
- default containers directory should be separate from modules (0.0.43)

docs/getting_started/developer-guide.rst

Lines changed: 216 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,215 @@ For each of the above, depending on the prefix of options that you choose, it wi
198198
This means that if you design a new registry recipe, you should consider how to run it for both kinds of technology. Also note that ``docker_options`` are
199199
those that will also be used for Podman.
200200

201+
Wrapper Script
202+
--------------
203+
204+
Singularity HPC allows exposure of two kinds of wrapper scripts:
205+
206+
1. A global level wrapper intended to replace aliases. E.g., if an alias "samtools" is typically a direct container call, enabling a wrapper will generate an executable script "samtools" in a "bin" directory associated with the container, added to the path, to call instead. This is desired when MPI ("mpirun") or scheduler (e.g. "srun" with Slurm) utilities are needed to run the scripts. This global script is defined in settings.yml and described in the user guide.
207+
2. A container level wrapper that is specific to a container, described here.
208+
209+
For container specific scripts, you can add sections to a ``container.yaml`` to specify the script (and container type)
210+
and the scripts must be provided alongside the container.yaml to install.
211+
212+
.. code-block:: yaml
213+
214+
docker_scripts:
215+
fork: docker_fork.sh
216+
singularity_scripts:
217+
fork: singularity_fork.sh
218+
219+
The above says "given generation of a docker or podman container, write a script named "fork" that uses "docker_fork.sh" as a template"
220+
and the same for Singularity. And then I (the developer) would provide the custom scripts alongside container.yaml:
221+
222+
.. code-block:: console
223+
224+
registry/vanessa/salad/
225+
├── container.yaml
226+
├── docker_fork.sh
227+
└── singularity_fork.sh
228+
229+
You can look at ``registry/vanessa/salad`` for an example that includes Singularity
230+
and Docker wrapper scripts. For example, when generating for a singularity container with
231+
the global wrapped scripts enabled, we get one wrapper script for the alias "salad" and one for
232+
the custom container script "fork":
233+
234+
.. code-block:: console
235+
236+
$ tree modules/vanessa/salad/
237+
modules/vanessa/salad/
238+
└── latest
239+
├── 99-shpc.sh
240+
├── bin
241+
│   ├── fork
242+
│   └── salad
243+
└── module.lua
244+
245+
If we disable all wrapper scripts, the bin directory would not exist. If we set the default wrapper
246+
scripts for singularity and docker in settings.yml and left enable to true, we would only see "fork."
247+
248+
How to write an alias wrapper script
249+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
250+
251+
First, decide if you want a global script (to replace or wrap aliases) OR a custom container script. For an alias derived (global) script, you should:
252+
253+
1. Write the new script file into shpc/main/wrappers.
254+
2. Add an entry to shpc/main/wrappers/scripts referencing the script.
255+
256+
For these global scripts, the user can select to use it in their settings.yaml.
257+
We will eventually write a command to list global wrappers available, so if you add a new one future users will know
258+
about it. For alias wrapper scripts, the following variables are passed for rendering:
259+
260+
.. list-table:: Title
261+
:widths: 15 15 40 30
262+
:header-rows: 1
263+
264+
* - Name
265+
- Type
266+
- Description
267+
- Example
268+
* - alias
269+
- dictionary
270+
- The entire alias in question, including subfields name, command, singularity_options or docker_options, and args
271+
- ``{{ alias.name }}``
272+
* - settings
273+
- dictionary
274+
- Everything referenced in the user settings
275+
- ``{{ settings.wrapper_shell }}``
276+
* - container
277+
- dictionary
278+
- The container technology
279+
- ``{{ container.command }}`` renders to docker, singularity, or podman
280+
* - config
281+
- dictionary
282+
- The entire container config (container.yaml) structured the same
283+
- ``{{ config.docker }}``
284+
* - image
285+
- string
286+
- The name of the container binary (SIF) or unique resource identifier
287+
- ``{{ image }}``
288+
* - module_dir
289+
- string
290+
- The name of the module directory
291+
- ``{{ module_dir }}``
292+
* - features
293+
- dictionary
294+
- A dictionary of parsed features
295+
- ``{{ features.gpu }}``
296+
297+
298+
299+
How to write an container wrapper script
300+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
301+
302+
If you want to write a custom container.yaml script:
303+
304+
1. Add either (or both) of singularity_scripts/docker_scripts in the container.yaml, including an alias command and an associated script.
305+
2. Write the script with the associated name into that folder.
306+
307+
The following variables are passed for rendering.
308+
309+
.. list-table:: Title
310+
:widths: 15 15 40 30
311+
:header-rows: 1
312+
313+
* - Name
314+
- Type
315+
- Description
316+
- Example
317+
* - alias
318+
- string
319+
- The alias name defined under singularity_scripts or docker_scripts
320+
- ``{{ alias }}``
321+
* - settings
322+
- dictionary
323+
- Everything referenced in the user settings
324+
- ``{{ settings.wrapper_shell }}``
325+
* - container
326+
- dictionary
327+
- The container technology
328+
- ``{{ container.command }}`` renders to docker, singularity, or podman
329+
* - config
330+
- dictionary
331+
- The entire container config (container.yaml) structured the same
332+
- ``{{ config.docker }}``
333+
* - image
334+
- string
335+
- The name of the container binary (SIF) or unique resource identifier
336+
- ``{{ image }}``
337+
* - module_dir
338+
- string
339+
- The name of the module directory
340+
- ``{{ module_dir }}``
341+
* - features
342+
- dictionary
343+
- A dictionary of parsed features
344+
- ``{{ features.gpu }}``
345+
346+
347+
Templating for both wrapper script types
348+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
349+
350+
Note that you are free to use "snippets" and "bases" either as an inclusion or "extends" meaning you can
351+
easily re-use code. For example, if we have the following registered directories under ``shpc/main/wrappers/templates``
352+
for definition of bases and templates:
353+
354+
.. code-block:: console
355+
356+
main/wrappers/templates/
357+
358+
# These are intended for use with "extends"
359+
├── bases
360+
│   ├── __init__.py
361+
│   └── shell-script-base.sh
362+
363+
# These are top level template files, as specified in the settings.yml
364+
├── docker.sh
365+
├── singularity.sh
366+
367+
# A mostly empty directory ready for any snippets!
368+
└── snippets
369+
370+
For example, a "bases" template to define a shell and some special command that might look like this:
371+
372+
.. code-block:: console
373+
374+
#!{{ settings.wrapper_shell }}
375+
376+
script=`realpath $0`
377+
wrapper_bin=`dirname $script`
378+
{% if '/csh' in settings.wrapper_shell %}set moduleDir=`dirname $wrapper_bin`{% else %}export moduleDir=$(dirname $wrapper_bin){% endif %}
379+
380+
{% block content %}{% endblock %}
381+
382+
383+
And then to use it for any container- or global- wrapper we would do the following in the wrapper script:
384+
385+
.. code-block:: console
386+
387+
{% extends "bases/my-base-shell.sh" %}
388+
389+
# some custom wrapper before stuff here
390+
391+
{% block content %}{% endblock %}
392+
393+
# some custom wrapper after stuff here
394+
395+
396+
For snippets, which are intended to be more chunks of code you can throw in one spot
397+
on the fly, you can do this:
398+
399+
400+
.. code-block:: console
401+
402+
{% include "snippets/export-envars.sh" %}
403+
# some custom wrapper after stuff here
404+
405+
406+
Finally, if you want to add your own custom templates directory for which you
407+
can refer to templates relatively, define ``wrapper_scripts`` -> ``templates`` as a full path
408+
in your settings.
409+
201410

202411
Environment Variables
203412
---------------------
@@ -337,7 +546,7 @@ Fields include:
337546
- A list of patterns to use for adding new tags. If not defined, all are added
338547
- false
339548
* - aliases
340-
- Named entrypoints for container (dict)
549+
- Named entrypoints for container (dict) as described above
341550
- false
342551
* - url
343552
- Documentation or other url for the container uri
@@ -351,7 +560,12 @@ Fields include:
351560
* - features
352561
- Optional key, value paired set of features to enable for the container. Currently allowed keys: *gpu* *home* and *x11*.
353562
- varies
354-
563+
* - singularity_scripts
564+
- key value pairs of wrapper names (e.g., executable called by user) and local container script for Singularity
565+
- false
566+
* - docker_scripts
567+
- key value pairs of wrapper names (e.g., executable called by user) and local container script for Docker or Singularity
568+
- false
355569

356570
A complete table of features is shown here. The
357571

docs/getting_started/user-guide.rst

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,21 @@ variable replacement. A summary table of variables is included below, and then f
201201
* - test_shell
202202
- The shell used for the test.sh file
203203
- /bin/bash
204+
* - wrapper_shell
205+
- The shell used for wrapper scripts
206+
- /bin/bash
207+
* - wrapper_scripts:enabled
208+
- enable or disable generation of wrapper scripts, instead of module aliases
209+
- false
210+
* - wrapper_scripts:docker
211+
- The name of the generic wrapper script template for docker
212+
- docker.sh
213+
* - wrapper_scripts:podman
214+
- The name of the generic wrapper script template for podman
215+
- docker.sh
216+
* - wrapper_scripts:singularity
217+
- The name of the generic wrapper script template for singularity
218+
- singularity.sh
204219
* - namespace
205220
- Set a default module namespace that you want to install from.
206221
- null
@@ -446,6 +461,95 @@ If you would like support for a different container technology that has not been
446461
mentioned, please also `open an issue <https://github.com/singularityhub/singularity-hpc>`_ and
447462
provide description and links to what you have in mind.
448463

464+
Wrapper Scripts
465+
---------------
466+
467+
Singularity HPC allows for global definition of wrapper scripts, meaning that instead of writing a module alias to run a container for some given alias,
468+
we generate a wrapper script of the same name instead. Since the settings.yml is global, all wrapper scripts defined here are specific to replacing aliases.
469+
Container-specific scripts you'll want to include in the container.yaml are described in the developer docs. Let's take a look at the settings:
470+
471+
472+
.. code-block:: yaml
473+
474+
wrapper_scripts:
475+
476+
# Enable wrapper scripts, period. If enabled, generate scripts for aliases instead of commands
477+
# if enabled, we also allow container-specific wrapper scripts.
478+
enabled: false
479+
480+
# use for docker aliases
481+
docker: docker.sh
482+
483+
# use for podman aliases
484+
podman: docker.sh
485+
486+
# use for singularity aliases
487+
singularity: singularity.sh
488+
489+
490+
Since different container technologies might expose different environment variables (e.g., ``SINGULARITY_OPTS`` vs ``PODMAN_OPTS``)
491+
they are organized above based on the container technology. If you want to customize the wrapper script, simply replace the relative paths
492+
above (e.g., ``singularity.sh``) with an absolute path to a file that will be used instead. For global alias scripts such as these,
493+
Singularity HPC will look for:
494+
495+
1. An absolute path first, if found is used first.
496+
2. Then a script name in the shpc/main/wrappers directory
497+
498+
Here is an example of using wrapper scripts for the "python" container, which doesn't have container specific wrappers. What you see
499+
is the one entrypoint, `python`, being placed in a "bin" subdirectory that the module will see instead of defining the alias.
500+
501+
502+
.. code-block:: console
503+
504+
modules/python/
505+
└── 3.9.10
506+
├── 99-shpc.sh
507+
├── bin
508+
│   └── python
509+
└── module.lua
510+
511+
For container specific scripts, you can add sections to a ``container.yaml`` to specify the script (and container type)
512+
and the scripts must be provided alongside the container.yaml to install.
513+
514+
.. code-block:: yaml
515+
516+
docker_scripts:
517+
fork: docker_fork.sh
518+
singularity_scripts:
519+
fork: singularity_fork.sh
520+
521+
The above says "given generation of a docker or podman container, write a script named "fork" that uses "docker_fork.sh" as a template"
522+
and the same for Singularity. And then I (the developer) would provide the custom scripts alongside container.yaml:
523+
524+
.. code-block:: console
525+
526+
registry/vanessa/salad/
527+
├── container.yaml
528+
├── docker_fork.sh
529+
└── singularity_fork.sh
530+
531+
And here is what those scripts look like installed. Since we are installing for just one container technology, we are seeing the alias wrapper for salad as "salad" and the container-specific wrapper for fork as "fork."
532+
533+
534+
.. code-block:: console
535+
536+
modules/vanessa/salad/
537+
└── latest
538+
├── 99-shpc.sh
539+
├── bin
540+
│   ├── fork
541+
│   └── salad
542+
└── module.lua
543+
544+
545+
We currently don't have a global argument to enable alias wrappers but not container wrappers. If you see a need for this please let us know.
546+
547+
Where are wrapper scripts stored?
548+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
549+
550+
Since we don't allow overlap
551+
of the name of an alias wrapper script (e.g., ``bin/python`` as a wrapper to a python entrypoint) from a custom container wrapper script (e.g., a wrapper script with name "python" under a container.yaml) we can keep them both in the modules directory. If you see a need to put them elsewhere please let us know.
552+
449553
.. _getting_started-commands:
450554

451555

registry/vanessa/salad/container.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,13 @@ tags:
88
latest: sha256:e8302da47e3200915c1d3a9406d9446f04da7244e4995b7135afd2b79d4f63db
99
aliases:
1010
salad: /code/salad
11+
12+
# An example of a custom wrapper script, stored here, will generate "fork" alias
13+
# for each of a docker and singularity container
14+
docker_scripts:
15+
fork: docker_fork.sh
16+
singularity_scripts:
17+
fork: singularity_fork.sh
18+
1119
env:
1220
maintainer: vsoch
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "bases/shell-script-base.sh" %}
2+
3+
# See shpc/main/wrappers/snippets for what is added via this extend
4+
5+
# This is an example of a custom wrapper script for a docker container
6+
# Note that we are showing an example of all the default args provided to the script!
7+
# You don't need to use any of this syntax
8+
9+
{% block content %}{{ container.command }} ${PODMAN_OPTS} run ${PODMAN_COMMAND_OPTS} -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file $moduleDir/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} --entrypoint /code/salad {{ image }} fork {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %}{% endblock %}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "bases/shell-script-base.sh" %}
2+
3+
# See shpc/main/wrappers/snippets for what is added via this extend
4+
5+
# This is an example of a singularity wrapper script, under singularity_scripts
6+
# Note that we are showing an example of all the default args provided to the script!
7+
# You don't need to use any of this syntax
8+
9+
{% block content %}singularity ${SINGULARITY_OPTS} exec ${SINGULARITY_COMMAND_OPTS} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home }} --home {{ features.home }} {% endif %}{% if features.x11 %}-B {{ features.x11 }} {% endif %}{% if settings.environment_file %}-B $moduleDir/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} {{ image }} /code/salad fork {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %}{% endblock %}

0 commit comments

Comments
 (0)