Skip to content

Commit d1d1f74

Browse files
authored
Various wrapper scripts improvement (#548)
* Defines the constructor arguments just once * Factored out the shell method as it is almost the same between both container technologies * The DockerContainer doesn't need to know about its subclass any more * Stop forbidding templates from being named podman.sh Only use WrapperScript - no more subclasses. * WrapperScript objects don't have the wrapper_template attribute anymore * Merged the three generation methods of WrapperScript into one * Rewrote the template search - Don't restrict user-defined global templates to be named singularity.sh or docker.sh as in shpc's default template directory - Properly defined the search path based on all available template directories - Minor bugfix: do not search the templates in the current working directory * Allow using a custom template for aliases * Turned get_shell_path to a property Co-authored-by: Matthieu Muffato <[email protected]> * Allow defining the alternative template script in a long form option rather than hijacking the singularity_scripts section * Ensure that the wrapper is always defined * Extracted a function that checks the wrapper exists and returns where to find it * Updated the docs to reflect the current wait templates are rendered * Not used anywhere, so no need to make it a class variable * Reinstated wrapper_template as a (mandatory) constructor argument * Further refactoring to split the function that establishes the search path, from the one that finds the template file * Added a test for the two new methods of WrapperScript * Fixed the comment * Pass the config as a kwarg so that we don't need to pass None for the container * Applied black
1 parent eaf978c commit d1d1f74

File tree

9 files changed

+245
-184
lines changed

9 files changed

+245
-184
lines changed

docs/getting_started/developer-guide.rst

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ about it. For alias wrapper scripts, the following variables are passed for rend
267267
- Example
268268
* - alias
269269
- dictionary
270-
- The entire alias in question, including subfields name, command, singularity_options or docker_options, and args
270+
- The entire alias in question, including subfields name, command, singularity_options or docker_options, singularity_script or docker_script, and args
271271
- ``{{ alias.name }}``
272272
* - settings
273273
- dictionary
@@ -304,44 +304,10 @@ If you want to write a custom container.yaml script:
304304
1. Add either (or both) of singularity_scripts/docker_scripts in the container.yaml, including an alias command and an associated script.
305305
2. Write the script with the associated name into that folder.
306306

307-
The following variables are passed for rendering.
308-
309-
.. list-table:: Container YAML Alias Variables
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 }}``
307+
For rendering, the same variables as for alias wrapper scripts are passed,
308+
**except** ``alias`` which is now a *string* (the name of the alias defined
309+
under singularity_scripts or docker_scripts) and should be used directly, e.g.
310+
``{{ alias }}``.
345311

346312

347313
Templating for both wrapper script types

shpc/main/container/docker.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,20 @@ def __init__(self):
3333
)
3434
super(DockerContainer, self).__init__()
3535

36+
@property
37+
def shell_path(self):
38+
"""
39+
Return the path of the shell to use with this container.
40+
"""
41+
return self.settings.docker_shell
42+
3643
def shell(self, image):
3744
"""
3845
Interactive shell into a container image.
3946
"""
4047
os.system(
41-
"docker run -it --rm --entrypoint %s %s"
42-
% (self.settings.docker_shell, image)
48+
"%s run -it --rm --entrypoint %s %s"
49+
% (self.command, self.shell_path, image)
4350
)
4451

4552
def add_registry(self, uri):
@@ -231,17 +238,10 @@ def install(
231238
config=config,
232239
)
233240

234-
# What shell to use?
235-
shell = (
236-
self.settings.podman_shell
237-
if self.command == "podman"
238-
else self.settings.docker_shell
239-
)
240-
241241
# Make sure to render all values!
242242
out = template.render(
243243
settings=self.settings,
244-
shell=shell,
244+
shell=self.shell_path,
245245
image=container_path,
246246
description=description,
247247
aliases=aliases,

shpc/main/container/podman.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
from .docker import DockerContainer
77

8-
import os
9-
108

119
class PodmanContainer(DockerContainer):
1210
"""
@@ -17,11 +15,9 @@ class PodmanContainer(DockerContainer):
1715
templatefile = "docker"
1816
command = "podman"
1917

20-
def shell(self, image):
18+
@property
19+
def shell_path(self):
2120
"""
22-
Interactive shell into a container image.
21+
Return the path of the shell to use with this container.
2322
"""
24-
os.system(
25-
"podman run -it --rm --entrypoint %s %s"
26-
% (self.settings.podman_shell, image)
27-
)
23+
return self.settings.podman_shell

shpc/main/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"command": {"type": "string"},
5353
"singularity_options": {"type": "string"},
5454
"docker_options": {"type": "string"},
55+
"singularity_script": {"type": "string"},
56+
"docker_script": {"type": "string"},
5557
},
5658
},
5759
}

shpc/main/wrappers/__init__.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
__copyright__ = "Copyright 2022, Vanessa Sochat"
33
__license__ = "MPL 2.0"
44

5-
from .scripts import get_wrapper_script
5+
from shpc.logger import logger
6+
from .base import WrapperScript
67
import os
78

89

@@ -22,40 +23,56 @@ def generate(image, container, config, **kwargs):
2223
settings = container.settings
2324
aliases = kwargs.get("aliases", [])
2425

25-
# Global wrapper scripts are generated via aliases
26-
for container_tech in ["docker", "podman", "singularity"]:
27-
template_name = settings.wrapper_scripts.get(container_tech)
28-
if template_name and aliases and container_tech == container.command:
29-
wrapper = get_wrapper_script(template_name)(
30-
settings=settings,
31-
wrapper_template=template_name,
32-
config=config,
33-
container=container,
34-
image=image,
35-
**kwargs
26+
constructor_kwargs = {
27+
"settings": settings,
28+
"config": config,
29+
"container": container,
30+
"image": image,
31+
**kwargs,
32+
}
33+
34+
# Default wrapper for this container technology. Will be used for all
35+
# aliases (unless overriden)
36+
default_wrapper = None
37+
default_template_name = settings.wrapper_scripts.get(container.command)
38+
if default_template_name:
39+
default_wrapper = WrapperScript(default_template_name, **constructor_kwargs)
40+
# include_container_dir not set -> only look in the global locations
41+
default_wrapper.load_template()
42+
43+
# Command aliases
44+
custom_wrapper_option_name = "%s_script" % container.templatefile
45+
for alias in aliases:
46+
# Allow overriding the template name in the script option
47+
if custom_wrapper_option_name in alias:
48+
wrapper = WrapperScript(
49+
alias[custom_wrapper_option_name], **constructor_kwargs
3650
)
37-
generated += wrapper.generate_aliases()
51+
wrapper.load_template(include_container_dir=True)
52+
elif default_wrapper:
53+
wrapper = default_wrapper
54+
else:
55+
logger.exit(
56+
"Can't generate a wrapper script for '%s' as there is no template defined for %s"
57+
% (alias["name"], container.templatefile)
58+
)
59+
60+
# NB: alias is a dictionary
61+
generated += wrapper.generate(alias["name"], alias)
3862

3963
# Container level wrapper scripts (allow eventually supporting custom podman)
4064
scripts = {
4165
"singularity": config.singularity_scripts,
4266
"docker": config.docker_scripts,
4367
"podman": config.docker_scripts,
4468
}
45-
for command, listing in scripts.items():
46-
47-
# Don't look if no custom container.yaml scripts OR wrong container tech
48-
if not listing or container.templatefile != command:
49-
continue
50-
for alias, template_name in listing.items():
51-
wrapper = get_wrapper_script(template_name)(
52-
settings=settings,
53-
wrapper_template=template_name,
54-
config=config,
55-
container=container,
56-
image=image,
57-
**kwargs
58-
)
59-
generated += wrapper.generate(alias)
69+
# Additional commands defined in the custom container.yaml script section
70+
listing = scripts.get(container.templatefile, [])
71+
for alias, template_name in listing.items():
72+
wrapper = WrapperScript(template_name, **constructor_kwargs)
73+
# Template wrapper scripts may live alongside container.yaml
74+
wrapper.load_template(include_container_dir=True)
75+
# NB: alias is a string
76+
generated += wrapper.generate(alias, alias)
6077

6178
return list(set(generated))

0 commit comments

Comments
 (0)