diff --git a/.github/dev-requirements.txt b/.github/dev-requirements.txt index 0b29a8891..6116de386 100644 --- a/.github/dev-requirements.txt +++ b/.github/dev-requirements.txt @@ -1,4 +1,4 @@ pre-commit -black +black==23.3.0 isort flake8 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 414eb3704..038c25258 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,6 +97,8 @@ jobs: printf "\n\nmodule help ============================================\n" module help python/3.9.5-alpine + script_path=$(which python-exec) + cat $script_path set -x python-exec echo donuts >test_output cat test_output diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b24f3334..b9c23ac4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are: The versions coincide with releases on pip. Only major versions will be released as tags on Github. ## [0.0.x](https://github.com/singularityhub/singularity-hpc/tree/main) (0.0.x) + - Allow custom location for wrapper scripts (0.1.24) - Labels with newlines need additional parsing (0.1.23) - Do not write directly to output with shpc show (0.1.22) - Podman template bug (0.1.21) diff --git a/docs/getting_started/user-guide.rst b/docs/getting_started/user-guide.rst index 9b935a54f..49cf8cd3c 100644 --- a/docs/getting_started/user-guide.rst +++ b/docs/getting_started/user-guide.rst @@ -176,6 +176,9 @@ variable replacement. A summary table of variables is included below, and then f * - module_base - The install directory for modules - $root_dir/modules + * - wrapper_base + - The install directory for script wrappers + - $root_dir/modules * - container_base - Where to install containers. If not defined, they are installed in "containers" in the install root - $root_dir/containers @@ -347,6 +350,20 @@ Singularity Registry HPC uses this simple directory structure to ensure a unique namespace. +Wrapper Base +------------ + +By default, if you do not set a wrapper script base they will be stored alongside +modules. However, for large installations, we recommend you customize this path +to be somewhere else. This way, you can avoid warnings from your module software +about having too many files. + +.. code-block:: console + + # an absolute path + $ shpc config set wrapper_base /opt/lmod/wrappers + + Container Images Folder ----------------------- diff --git a/shpc/main/container/base.py b/shpc/main/container/base.py index afabadff0..f7d383886 100644 --- a/shpc/main/container/base.py +++ b/shpc/main/container/base.py @@ -66,13 +66,16 @@ def add(self, sif, module_name, modulefile, template, **kwargs): """ logger.warning("Add is not supported for %s" % self) - def add_environment(self, module_dir, envars, environment_file): + def add_environment(self, env_dir, envars, environment_file): """ Given one or more environment variables in a dictionary, write to file. + + The environment file goes in the wrapper directory, which can default + to the module directory if the value uses the default or is unset. """ # Podman envars are written directly to the module file out = Template(shpc.main.templates.environment_file).render(envars=envars) - env_file = os.path.join(module_dir, environment_file) + env_file = os.path.join(env_dir, environment_file) shpc.utils.write_file(env_file, out) def delete(self, image): diff --git a/shpc/main/container/docker.py b/shpc/main/container/docker.py index 7130b5d33..a55fc1bfb 100644 --- a/shpc/main/container/docker.py +++ b/shpc/main/container/docker.py @@ -218,7 +218,7 @@ def install(self, module_path, template, module, features=None): if self.settings.wrapper_scripts["enabled"] is True: wrapper_scripts = shpc.main.wrappers.generate( aliases=aliases, - module_dir=module.module_dir, + wrapper_dir=module.wrapper_dir, features=features, container=self, image=module.container_path, diff --git a/shpc/main/container/singularity.py b/shpc/main/container/singularity.py index 46fb60adb..1d5a5d428 100644 --- a/shpc/main/container/singularity.py +++ b/shpc/main/container/singularity.py @@ -211,9 +211,9 @@ def install(self, module_path, template, module, features=None): if self.settings.wrapper_scripts["enabled"] is True: wrapper_scripts = shpc.main.wrappers.generate( aliases=aliases, - module_dir=module.module_dir, features=features, container=self, + wrapper_dir=module.wrapper_dir, image=module.container_path, config=module.config, ) diff --git a/shpc/main/container/update/versions.py b/shpc/main/container/update/versions.py index 20e69dba7..39ac93097 100644 --- a/shpc/main/container/update/versions.py +++ b/shpc/main/container/update/versions.py @@ -155,7 +155,7 @@ def _cmp(self, other): this_version = self.version[i] other_version = other.version[i] - if type(this_version) != type(other_version): + if type(this_version) is not type(other_version): continue if this_version == other_version: diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 8b0058c0d..52908c380 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -130,6 +130,14 @@ def uninstall(self, name, force=False): "$module_base/%s" % module.name, ) + # If we have a wrapper + if module.wrapper_dir != module.module_dir: + self._uninstall( + module.wrapper_dir, + self.settings.wrapper_base, + "$wrapper_base/%s" % module.name, + ) + # If uninstalling the entire module, clean up symbolic links in all views for view_name in views_with_module: self.views[view_name].uninstall(module.module_dir) diff --git a/shpc/main/modules/module.py b/shpc/main/modules/module.py index d95bf9a17..1a16b671b 100644 --- a/shpc/main/modules/module.py +++ b/shpc/main/modules/module.py @@ -36,7 +36,7 @@ def add_environment(self): Write the environment to the module directory. """ self.container.add_environment( - self.module_dir, + self.wrapper_dir, envars=self.config.get_envars(), environment_file=self.settings.environment_file, ) @@ -165,6 +165,14 @@ def uri(self): """ return self._uri + @property + def wrapper_dir(self): + """ + Full path to the wrapper directory + """ + wrapper_dir = self.settings.wrapper_base or self.settings.module_base + return os.path.join(wrapper_dir, self.module_basepath) + @property def module_dir(self): """ diff --git a/shpc/main/modules/templates/docker.lua b/shpc/main/modules/templates/docker.lua index 575cb6347..845ac1bf9 100644 --- a/shpc/main/modules/templates/docker.lua +++ b/shpc/main/modules/templates/docker.lua @@ -16,18 +16,18 @@ Container: Commands include: - {|module_name|}-run: - {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}-v ${PWD} -w ${PWD} "$@" + {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}-v ${PWD} -w ${PWD} "$@" - {|module_name|}-shell: - {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}--entrypoint {{ shell }} -v ${PWD} -w ${PWD} + {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}--entrypoint {{ shell }} -v ${PWD} -w ${PWD} - {|module_name|}-exec: - {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint "" {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} "$@" + {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint "" {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} "$@" - {|module_name|}-inspect: {{ command }} inspect - {|module_name|}-container: echo "$PODMAN_CONTAINER" {% if aliases %}{% for alias in aliases %} - {{ alias.name }}: - {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint {{ alias.entrypoint }} {% if settings.environment_file %}--env-file /{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}{% if alias.docker_options %}{{ alias.docker_options }} {% endif %} -v ${PWD} -w ${PWD} "{{ alias.args }}" "$@" + {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint {{ alias.entrypoint }} {% if settings.environment_file %}--env-file /{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}{% if alias.docker_options %}{{ alias.docker_options }} {% endif %} -v ${PWD} -w ${PWD} "{{ alias.args }}" "$@" {% endfor %}{% endif %} For each of the above, you can export: @@ -48,23 +48,26 @@ if not os.getenv("PODMAN_COMMAND_OPTS") then setenv ("PODMAN_COMMAND_OPTS", "") -- directory containing this modulefile, once symlinks resolved (dynamically defined) local moduleDir = subprocess("realpath " .. myFileName()):match("(.*[/])") or "." +-- If we have wrapper base set, honor it, otherwise we use the moduleDir +{% if settings.wrapper_base %}local wrapperDir = "{{ module.wrapper_dir }}"{% else %}local wrapperDir = moduleDir{% endif %} + -- interactive shell to any container, plus exec for aliases local containerPath = '{{ module.container_path }}' -- service environment variable to access docker URI setenv("PODMAN_CONTAINER", containerPath) -local shellCmd = "{{ command }} ${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} ${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm --entrypoint {{ shell }} {% 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} " .. containerPath +local shellCmd = "{{ command }} ${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} ${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm --entrypoint {{ shell }} {% if settings.environment_file %}--env-file " .. wrapperDir .. "/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} " .. containerPath -- execCmd needs entrypoint to be the executor -local execCmd = "{{ 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} " -local runCmd = "{{ 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} " .. containerPath +local execCmd = "{{ 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 " .. wrapperDir .. "/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} " +local runCmd = "{{ 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 " .. wrapperDir .. "/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} " .. containerPath local inspectCmd = "{{ command }} ${PODMAN_OPTS} inspect ${PODMAN_COMMAND_OPTS} " .. containerPath -- conflict with modules with the same name conflict("{{ parsed_name.tool }}"{% if name != parsed_name.tool %},"{{ module.name }}"{% endif %}{% if aliases %}{% for alias in aliases %}{% if alias.name != parsed_name.tool %},"{{ alias.name }}"{% endif %}{% endfor %}{% endif %}) -- if we have any wrapper scripts, add the bin directory -{% if wrapper_scripts %}prepend_path("PATH", pathJoin(moduleDir, "bin")){% endif %} +{% if wrapper_scripts %}prepend_path("PATH", pathJoin(wrapperDir, "bin")){% endif %} -- "aliases" to module commands - generate only if not a wrapper script already generated {% if aliases %}{% for alias in aliases %}{% if alias.name not in wrapper_scripts %}set_shell_function("{{ alias.name }}", execCmd .. {% if alias.docker_options %} "{{ alias.docker_options }} " .. {% endif %} " --entrypoint {{ alias.entrypoint }} " .. containerPath .. " {{ alias.args }} \"$@\"", execCmd .. {% if alias.docker_options %} "{{ alias.docker_options }} " .. {% endif %} " --entrypoint {{ alias.entrypoint }} " .. containerPath .. " {{ alias.args }}"){% endif %} diff --git a/shpc/main/modules/templates/docker.tcl b/shpc/main/modules/templates/docker.tcl index d8f1e663b..531d801b6 100644 --- a/shpc/main/modules/templates/docker.tcl +++ b/shpc/main/modules/templates/docker.tcl @@ -15,18 +15,18 @@ proc ModulesHelp { } { puts stderr " - {{ module.container_path }}" puts stderr "Commands include:" puts stderr " - {|module_name|}-run:" - puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . \"\$@\"" + puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . \"\$@\"" puts stderr " - {|module_name|}-shell:" - puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint {{ shell }} {% if settings.environment_file %} --env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . " + puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint {{ shell }} {% if settings.environment_file %} --env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . " puts stderr " - {|module_name|}-exec:" - puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint \"\" {% if settings.environment_file %} --env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . \"\$@\"" + puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} -u `id -u`:`id -g` --rm --entrypoint \"\" {% if settings.environment_file %} --env-file /{{ settings.environment_file }} {% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v . -w . \"\$@\"" puts stderr " - {|module_name|}-inspect:" puts stderr " {{ command }} inspect " puts stderr " - {|module_name|}-container:" puts stderr " echo \"\$PODMAN_CONTAINER\"" puts stderr "" {% if aliases %}{% for alias in aliases %} puts stderr " - {{ alias.name }}:" - puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} --rm -u `id -u`:`id -g` --entrypoint {{ alias.entrypoint | replace("$", "\$") }} {% if settings.environment_file %}--settings.environment_file /{{ settings.environment_file }} {% endif %}{% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}{% if alias.docker_options %}{{ alias.docker_options | replace("$", "\$") }} {% endif %} -v . -w . {{ alias.args | replace("$", "\$") }} \"\$@\"" + puts stderr " {{ command }} run -i{% if settings.enable_tty %}t{% endif %} --rm -u `id -u`:`id -g` --entrypoint {{ alias.entrypoint | replace("$", "\$") }} {% if settings.environment_file %}--settings.environment_file /{{ settings.environment_file }} {% endif %}{% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %}{% if alias.docker_options %}{{ alias.docker_options | replace("$", "\$") }} {% endif %} -v . -w . {{ alias.args | replace("$", "\$") }} \"\$@\"" {% endfor %}{% endif %} puts stderr "" puts stderr "For each of the above, you can export:" @@ -62,6 +62,9 @@ set helpcommand "This module is a {{ docker }} container wrapper for {{ module.n # directory containing this modulefile, once symlinks resolved (dynamically defined) set moduleDir [file dirname [expr { [string equal [file type ${ModulesCurrentModulefile}] "link"] ? [file readlink ${ModulesCurrentModulefile}] : ${ModulesCurrentModulefile} }]] +# If we have wrapper base set, honor it, otherwise we use the moduleDir +{% if settings.wrapper_base %}set wrapperDir "{{ module.wrapper_dir }}"{% else %}set wrapperDir "${moduleDir}"{% endif %} + # conflict with modules with the same alias name conflict {{ parsed_name.tool }} {% if name != parsed_name.tool %}conflict {{ module.name }}{% endif %} @@ -72,15 +75,15 @@ conflict {{ parsed_name.tool }} setenv PODMAN_CONTAINER "${containerPath}" # interactive shell to any container, plus exec for aliases -set shellCmd "{{ command }} \${PODMAN_OPTS} run \${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm -i{% if settings.enable_tty %}t{% endif %} --entrypoint {{ shell }} {% 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 $workdir -w $workdir ${containerPath}" +set shellCmd "{{ command }} \${PODMAN_OPTS} run \${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm -i{% if settings.enable_tty %}t{% endif %} --entrypoint {{ shell }} {% if settings.environment_file %}--env-file ${wrapperDir}/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v $workdir -w $workdir ${containerPath}" # execCmd needs entrypoint to be the executor -set execCmd "{{ command }} \${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} \${PODMAN_COMMAND_OPTS} -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 $workdir -w $workdir" -set runCmd "{{ command }} \${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} \${PODMAN_COMMAND_OPTS} -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 $workdir -w $workdir ${containerPath}" +set execCmd "{{ command }} \${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} \${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm {% if settings.environment_file %} --env-file ${wrapperDir}/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }}{% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v $workdir -w $workdir" +set runCmd "{{ command }} \${PODMAN_OPTS} run -i{% if settings.enable_tty %}t{% endif %} \${PODMAN_COMMAND_OPTS} -u `id -u`:`id -g` --rm {% if settings.environment_file %}--env-file ${wrapperDir}/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v $workdir -w $workdir ${containerPath}" set inspectCmd "{{ command }} \${PODMAN_OPTS} inspect ${containerPath}" # wrapper scripts? Add bin to path -{% if wrapper_scripts %}prepend-path PATH "${moduleDir}/bin"{% endif %} +{% if wrapper_scripts %}prepend-path PATH ${wrapperDir}/bin{% endif %} # "aliases" to module commands {% if aliases %}if { [ module-info shell bash ] } { diff --git a/shpc/main/modules/templates/singularity.lua b/shpc/main/modules/templates/singularity.lua index 97ec76395..ccade2938 100644 --- a/shpc/main/modules/templates/singularity.lua +++ b/shpc/main/modules/templates/singularity.lua @@ -16,11 +16,11 @@ Container (available through variable SINGULARITY_CONTAINER): Commands include: - {|module_name|}-run: - singularity run {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} "$@" + singularity run {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} "$@" - {|module_name|}-shell: - singularity shell -s {{ settings.singularity_shell }} {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} + singularity shell -s {{ settings.singularity_shell }} {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} - {|module_name|}-exec: - singularity exec {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} "$@" + singularity exec {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %} "$@" - {|module_name|}-inspect-runscript: singularity inspect -r - {|module_name|}-inspect-deffile: @@ -29,7 +29,7 @@ Commands include: echo "$SINGULARITY_CONTAINER" {% if aliases %}{% for alias in aliases %} - {{ alias.name }}: - singularity exec {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %}{% if alias.singularity_options %}{{ alias.singularity_options }} {% endif %} {{ alias.command }} "$@" + singularity exec {% 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 /{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }} {% endif %}{% if alias.singularity_options %}{{ alias.singularity_options }} {% endif %} {{ alias.command }} "$@" {% endfor %}{% endif %} For each of the above, you can export: @@ -46,6 +46,9 @@ For each of the above, you can export: -- directory containing this modulefile, once symlinks resolved (dynamically defined) local moduleDir = subprocess("realpath " .. myFileName()):match("(.*[/])") or "." +-- If we have wrapper base set, honor it, otherwise we use the moduleDir +{% if settings.wrapper_base %}local wrapperDir = "{{ module.wrapper_dir }}"{% else %}local wrapperDir = moduleDir{% endif %} + -- singularity environment variable to set shell setenv("SINGULARITY_SHELL", "{{ settings.singularity_shell }}") @@ -58,16 +61,16 @@ local containerPath = '{{ module.container_path }}' setenv("SINGULARITY_CONTAINER", containerPath) -- interactive shell to any container, plus exec for aliases -local shellCmd = "singularity ${SINGULARITY_OPTS} shell ${SINGULARITY_COMMAND_OPTS} -s {{ settings.singularity_shell }} {% 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 %} " .. containerPath -local execCmd = "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 %} " -local runCmd = "singularity ${SINGULARITY_OPTS} run ${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 %} " .. containerPath +local shellCmd = "singularity ${SINGULARITY_OPTS} shell ${SINGULARITY_COMMAND_OPTS} -s {{ settings.singularity_shell }} {% 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 " .. wrapperDir .. "/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} " .. containerPath +local execCmd = "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 " .. wrapperDir .. "/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} " +local runCmd = "singularity ${SINGULARITY_OPTS} run ${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 " .. wrapperDir .. "/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} " .. containerPath local inspectCmd = "singularity ${SINGULARITY_OPTS} inspect ${SINGULARITY_COMMAND_OPTS} " -- conflict with modules with the same name conflict("{{ parsed_name.tool }}"{% if name != parsed_name.tool %},"{{ module.name }}"{% endif %}{% if aliases %}{% for alias in aliases %}{% if alias.name != parsed_name.tool %},"{{ alias.name }}"{% endif %}{% endfor %}{% endif %}) -- if we have any wrapper scripts, add bin to path -{% if wrapper_scripts %}prepend_path("PATH", pathJoin(moduleDir, "bin")){% endif %} +{% if wrapper_scripts %}prepend_path("PATH", pathJoin(wrapperDir, "bin")){% endif %} -- "aliases" to module commands {% if aliases %}{% for alias in aliases %}{% if alias.name not in wrapper_scripts %}set_shell_function("{{ alias.name }}", execCmd .. {% if alias.singularity_options %} "{{ alias.singularity_options }} " .. {% endif %} containerPath .. " {{ alias.command }} \"$@\"", execCmd .. {% if alias.singularity_options %} "{{ alias.singularity_options }} " .. {% endif %} containerPath .. " {{ alias.command }}"){% endif %} diff --git a/shpc/main/modules/templates/singularity.tcl b/shpc/main/modules/templates/singularity.tcl index bcf45ac07..f21ccb3ad 100644 --- a/shpc/main/modules/templates/singularity.tcl +++ b/shpc/main/modules/templates/singularity.tcl @@ -67,6 +67,9 @@ set helpcommand "This module is a singularity container wrapper for {{ module.na # directory containing this modulefile, once symlinks resolved (dynamically defined) set moduleDir [file dirname [expr { [string equal [file type ${ModulesCurrentModulefile}] "link"] ? [file readlink ${ModulesCurrentModulefile}] : ${ModulesCurrentModulefile} }]] +# If we have wrapper base set, honor it, otherwise we use the moduleDir +{% if settings.wrapper_base %}set wrapperDir "{{ module.wrapper_dir }}"{% else %}set wrapperDir "${moduleDir}"{% endif %} + # conflict with modules with the same alias name conflict {{ parsed_name.tool }} {% if name != parsed_name.tool %}conflict {{ module.name }}{% endif %} @@ -80,13 +83,13 @@ setenv SINGULARITY_SHELL {{ settings.singularity_shell }} setenv SINGULARITY_CONTAINER "${containerPath}" # interactive shell to any container, plus exec for aliases -set shellCmd "singularity \${SINGULARITY_OPTS} shell \${SINGULARITY_COMMAND_OPTS} -s {{ settings.singularity_shell }} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${moduleDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} ${containerPath}" -set execCmd "singularity \${SINGULARITY_OPTS} exec \${SINGULARITY_COMMAND_OPTS} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${moduleDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} " -set runCmd "singularity \${SINGULARITY_OPTS} run \${SINGULARITY_COMMAND_OPTS} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${moduleDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} ${containerPath}" +set shellCmd "singularity \${SINGULARITY_OPTS} shell \${SINGULARITY_COMMAND_OPTS} -s {{ settings.singularity_shell }} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${wrapperDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} ${containerPath}" +set execCmd "singularity \${SINGULARITY_OPTS} exec \${SINGULARITY_COMMAND_OPTS} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${wrapperDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} " +set runCmd "singularity \${SINGULARITY_OPTS} run \${SINGULARITY_COMMAND_OPTS} {% if features.gpu %}{{ features.gpu }} {% endif %}{% if features.home %}-B {{ features.home | replace("$", "\$") }} --home {{ features.home | replace("$", "\$") }} {% endif %}{% if features.x11 %}-B {{ features.x11 | replace("$", "\$") }} {% endif %}{% if settings.environment_file %}-B ${wrapperDir}/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} ${containerPath}" set inspectCmd "singularity \${SINGULARITY_OPTS} inspect \${SINGULARITY_COMMAND_OPTS} " # if we have any wrapper scripts, add bin to path -{% if wrapper_scripts %}prepend-path PATH "${moduleDir}/bin"{% endif %} +{% if wrapper_scripts %}prepend-path PATH ${wrapperDir}/bin{% endif %} # "aliases" to module commands {% if aliases %}if { [ module-info shell bash ] } { diff --git a/shpc/main/schemas.py b/shpc/main/schemas.py index 1efdc2381..cc234550b 100644 --- a/shpc/main/schemas.py +++ b/shpc/main/schemas.py @@ -137,6 +137,7 @@ settingsProperties = { "registry": {"type": "array", "items": {"type": "string"}}, "sync_registry": {"type": "string"}, + "wrapper_base": {"type": ["string", "null"]}, "module_base": {"type": "string"}, "container_base": {"type": ["string", "null"]}, "namespace": {"type": ["string", "null"]}, diff --git a/shpc/main/wrappers/base.py b/shpc/main/wrappers/base.py index fdf978468..98e1e0414 100644 --- a/shpc/main/wrappers/base.py +++ b/shpc/main/wrappers/base.py @@ -37,11 +37,13 @@ def container_dest_dir(self): return os.path.dirname(self.image) @property - def module_dir(self): + def wrapper_dir(self): """ - Get the module directory (should error if not provided in kwargs) + Get the wrapper directory (should error if not provided in kwargs) + + This can be the module directory or a custom wrapper directory. """ - return self.kwargs["module_dir"] + return self.kwargs["wrapper_dir"] def get_template_paths(self): """ @@ -121,8 +123,8 @@ def generate(self, wrapper_name, alias_definition=None): interaction wrappers (e.g., exec, shell, etc.) """ - # Write scripts into container directory - wrapper_dir = os.path.join(self.module_dir, "bin") + # Write scripts into custom module directory + wrapper_dir = os.path.join(self.wrapper_dir, "bin") shpc.utils.mkdirp([wrapper_dir]) wrapper_path = os.path.join(wrapper_dir, wrapper_name) @@ -132,7 +134,7 @@ def generate(self, wrapper_name, alias_definition=None): settings=self.settings, image=self.image, config=self.config, - # includes module_dir, features, etc + # includes wrapper_dir, features, etc **self.kwargs ) shpc.utils.write_file(wrapper_path, out, exec=True) diff --git a/shpc/main/wrappers/templates/bases/shell-script-base.sh b/shpc/main/wrappers/templates/bases/shell-script-base.sh index 111f736c4..5d335656b 100755 --- a/shpc/main/wrappers/templates/bases/shell-script-base.sh +++ b/shpc/main/wrappers/templates/bases/shell-script-base.sh @@ -1,7 +1,6 @@ #!{{ settings.wrapper_shell }} {% if '/csh' in settings.wrapper_shell %}set {% endif %}script=`realpath $0` -{% if '/csh' in settings.wrapper_shell %}set {% endif %}wrapper_bin=`dirname $script` -{% if '/csh' in settings.wrapper_shell %}set {% endif %}moduleDir=`dirname $wrapper_bin` +{% if '/csh' in settings.wrapper_shell %}set {% endif %}wrapperDir=`dirname $script`/.. {% block content %}{% endblock %} diff --git a/shpc/main/wrappers/templates/docker.sh b/shpc/main/wrappers/templates/docker.sh index eda8b65f1..2e92bd1df 100755 --- a/shpc/main/wrappers/templates/docker.sh +++ b/shpc/main/wrappers/templates/docker.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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} {% if alias.docker_options %} {{ alias.docker_options }} {% endif %} --entrypoint {{ alias.entrypoint }} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} {% if alias.docker_options %} {{ alias.docker_options }} {% endif %} --entrypoint {{ alias.entrypoint }} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/docker/exec.sh b/shpc/main/wrappers/templates/docker/exec.sh index 9de392494..b15194adb 100755 --- a/shpc/main/wrappers/templates/docker/exec.sh +++ b/shpc/main/wrappers/templates/docker/exec.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/docker/run.sh b/shpc/main/wrappers/templates/docker/run.sh index 9de392494..b15194adb 100755 --- a/shpc/main/wrappers/templates/docker/run.sh +++ b/shpc/main/wrappers/templates/docker/run.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} {{ image }} {{ alias.args }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/docker/shell.sh b/shpc/main/wrappers/templates/docker/shell.sh index 4081f6ff6..00e26d64e 100755 --- a/shpc/main/wrappers/templates/docker/shell.sh +++ b/shpc/main/wrappers/templates/docker/shell.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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 {{ settings.wrapper_shell }} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-v {{ settings.bindpaths }} {% endif %}{% if features.home %}-v {{ features.home }} {% endif %} -v ${PWD} -w ${PWD} --entrypoint {{ settings.wrapper_shell }} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/singularity.sh b/shpc/main/wrappers/templates/singularity.sh index 98ab377c8..7c7f2aa3f 100755 --- a/shpc/main/wrappers/templates/singularity.sh +++ b/shpc/main/wrappers/templates/singularity.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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 %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {{ alias.command }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {{ alias.command }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/singularity/exec.sh b/shpc/main/wrappers/templates/singularity/exec.sh index d2b70eaf1..3040e3c23 100644 --- a/shpc/main/wrappers/templates/singularity/exec.sh +++ b/shpc/main/wrappers/templates/singularity/exec.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% 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 %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% 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 $wrapperDir/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/singularity/run.sh b/shpc/main/wrappers/templates/singularity/run.sh index 5047be356..7da8a9da0 100644 --- a/shpc/main/wrappers/templates/singularity/run.sh +++ b/shpc/main/wrappers/templates/singularity/run.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% block content %}singularity ${SINGULARITY_OPTS} run ${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 %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} +{% block content %}singularity ${SINGULARITY_OPTS} run ${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 $wrapperDir/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} {% if alias.singularity_options %} {{ alias.singularity_options }} {% endif %} {{ image }} {% if '/sh' in settings.wrapper_shell or '/bash' in settings.wrapper_shell %}"$@"{% elif '/csh' in settings.wrapper_shell %}$argv:q{% endif %} {% endblock %} diff --git a/shpc/main/wrappers/templates/singularity/shell.sh b/shpc/main/wrappers/templates/singularity/shell.sh index f1638686d..aac4116fa 100644 --- a/shpc/main/wrappers/templates/singularity/shell.sh +++ b/shpc/main/wrappers/templates/singularity/shell.sh @@ -1,4 +1,4 @@ {% extends "bases/shell-script-base.sh" %} -{% block content %}singularity ${SINGULARITY_OPTS} shell ${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 %} -s {{ settings.wrapper_shell }} {{ image }} +{% block content %}singularity ${SINGULARITY_OPTS} shell ${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 $wrapperDir/{{ settings.environment_file }}:/.singularity.d/env/{{ settings.environment_file }}{% endif %} {% if settings.bindpaths %}-B {{ settings.bindpaths }}{% endif %} -s {{ settings.wrapper_shell }} {{ image }} {% endblock %} diff --git a/shpc/settings.yml b/shpc/settings.yml index e9d9f76eb..d74743824 100644 --- a/shpc/settings.yml +++ b/shpc/settings.yml @@ -45,6 +45,8 @@ container_base: $root_dir/containers # When parsing labels, replace newlines with this string label_separator: ', ' +wrapper_base: $root_dir/modules + # Default root directory to create views views_base: $root_dir/views diff --git a/shpc/tests/test_client.py b/shpc/tests/test_client.py index b920d8db7..6cdfc85cf 100644 --- a/shpc/tests/test_client.py +++ b/shpc/tests/test_client.py @@ -44,10 +44,14 @@ def test_install_get(tmp_path, module_sys, module_file, container_tech, remote): assert os.path.exists(client.settings.module_base) module_dir = os.path.join(client.settings.module_base, "python", "3.9.2-alpine") + wrapper_dir = os.path.join(client.settings.wrapper_base, "python", "3.9.2-alpine") + assert os.path.exists(module_dir) module_file = os.path.join(module_dir, module_file) assert os.path.exists(module_file) - env_file = os.path.join(module_dir, client.settings.environment_file) + + # Environment file is in wrapper directory + env_file = os.path.join(wrapper_dir, client.settings.environment_file) assert os.path.exists(env_file) assert client.get("python:3.9.2-alpine") diff --git a/shpc/tests/test_wrappers.py b/shpc/tests/test_wrappers.py index 4fe6cecb3..0bb421fc0 100644 --- a/shpc/tests/test_wrappers.py +++ b/shpc/tests/test_wrappers.py @@ -13,6 +13,7 @@ import shpc.main.container as container import shpc.main.registry as registry import shpc.main.wrappers.base as wrappers_base +import shpc.utils as utils from .helpers import here, init_client @@ -108,10 +109,13 @@ def test_wrapper_install(tmp_path, module_sys, module_file, container_tech): # Install known tag client.install("python:3.9.2-alpine") module_dir = os.path.join(client.settings.module_base, "python", "3.9.2-alpine") + wrapper_dir = os.path.join(client.settings.wrapper_base, "python", "3.9.2-alpine") assert os.path.exists(module_dir) - assert "bin" in os.listdir(module_dir) - module_bin = os.path.join(module_dir, "bin") - assert os.path.exists(module_bin) + + # wrappers are installed to wrapper directory + assert "bin" in os.listdir(wrapper_dir) + wrapper_bin = os.path.join(wrapper_dir, "bin") + assert os.path.exists(wrapper_bin) requireds = [ "python-container", @@ -124,7 +128,7 @@ def test_wrapper_install(tmp_path, module_sys, module_file, container_tech): requireds += ["python-inspect-runscript", "python-inspect-deffile"] else: requireds += ["python-inspect"] - binaries = os.listdir(module_bin) + binaries = os.listdir(wrapper_bin) for required in requireds: assert required in binaries @@ -154,6 +158,103 @@ def test_disabled_wrapper_install(tmp_path, module_sys, module_file, container_t assert "bin" not in os.listdir(module_dir) +@pytest.mark.parametrize( + "module_sys,module_file,container_tech,wrapper_type", + [ + ("lmod", "module.lua", "singularity", "custom"), + ("lmod", "module.lua", "singularity", "default"), + ("lmod", "module.lua", "singularity", "unset"), + ("lmod", "module.lua", "singularity", "disabled"), + ("lmod", "module.lua", "podman", "custom"), + ("lmod", "module.lua", "podman", "default"), + ("lmod", "module.lua", "podman", "unset"), + ("lmod", "module.lua", "podman", "disabled"), + ("tcl", "module.tcl", "singularity", "custom"), + ("tcl", "module.tcl", "singularity", "default"), + ("tcl", "module.tcl", "singularity", "unset"), + ("tcl", "module.tcl", "singularity", "disabled"), + ("tcl", "module.tcl", "podman", "custom"), + ("tcl", "module.tcl", "podman", "default"), + ("tcl", "module.tcl", "podman", "unset"), + ("tcl", "module.tcl", "podman", "disabled"), + ], +) +def test_custom_wrapper_dir( + tmp_path, module_sys, module_file, container_tech, wrapper_type +): + """ + Test that we can customize the install location of wrapper scripts + + There are three cases: + - unset: the user explicitly sets to None + - default: we use the default value + - custom: we use a custom location + """ + + client = init_client(str(tmp_path), module_sys, container_tech) + + if wrapper_type == "custom": + wrapper_dir = os.path.join(str(tmp_path), "wrappers") + client.settings.set("wrapper_base", wrapper_dir) + + elif wrapper_type == "unset": + client.settings.set("wrapper_base", None) + + # Note that if you set enabled to false but don't set path to None, + # we will still generate them, which likely is not desired + elif wrapper_type == "disabled": + client.settings.set("wrapper_scripts:enabled", False) + client.settings.set("wrapper_base", None) + + # Default to $root/modules + elif wrapper_type == "default": + pass + + # A custom wrapper directory should not have a bin in the module base + client.install("python:3.9.2-alpine") + module_dir = os.path.join(client.settings.module_base, "python", "3.9.2-alpine") + if client.settings.wrapper_base: + wrapper_dir = os.path.join( + client.settings.wrapper_base, "python", "3.9.2-alpine" + ) + else: + wrapper_dir = module_dir + + module_filepath = os.path.join(module_dir, module_file) + + # Sanity check module is created and exists + assert os.path.exists(module_dir) + assert os.path.exists(module_filepath) + + # The content of the file + content = utils.read_file(module_filepath) + + print(module_file) + print(wrapper_type) + + # For custom, wrapper_dir will have wrappers. For default, wrapper_dir will be modules + # directory. We can test the same way. + if module_file.endswith("lua") and wrapper_type in ["custom", "default"]: + assert f'local wrapperDir = "{wrapper_dir}"' in content + + elif module_file.endswith("tcl") and wrapper_type in ["custom", "default"]: + assert f'set wrapperDir "{wrapper_dir}"' in content + + elif module_file.endswith("lua") and wrapper_type in ["unset", "disabled"]: + assert "local wrapperDir = moduleDir" in content + + elif module_file.endswith("tcl") and wrapper_type in ["unset", "disabled"]: + assert 'set wrapperDir "${moduleDir}"' in content + + # Wrappers always generated in either wrapper_dir or module_dir, unless disabled + # bin is generated no matter what, but is populated in one case only + if wrapper_type == "disabled": + assert "bin" not in os.listdir(wrapper_dir) + else: + assert "bin" in os.listdir(wrapper_dir) + assert os.listdir(os.path.join(wrapper_dir, "bin")) + + @pytest.mark.parametrize( "make_absolute,exists", [ diff --git a/shpc/version.py b/shpc/version.py index 311dbd49a..43fac3353 100644 --- a/shpc/version.py +++ b/shpc/version.py @@ -2,7 +2,7 @@ __copyright__ = "Copyright 2021-2023, Vanessa Sochat" __license__ = "MPL 2.0" -__version__ = "0.1.23" +__version__ = "0.1.24" AUTHOR = "Vanessa Sochat" EMAIL = "vsoch@users.noreply.github.com" NAME = "singularity-hpc"