Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
08ffeb9
Filter invalid mounts from apptainer_entrypoint and warn user
MichaelStubbings Sep 18, 2026
3ae2de2
Add optional mounts that do not result in errors when non-existant.
MichaelStubbings Sep 22, 2026
2cd80fc
Exclude optional mount code from apptainer_entrypoint if none defined
MichaelStubbings Sep 22, 2026
46cdf76
Improve naming of variables for optional_mount code
MichaelStubbings Sep 22, 2026
fdd1c25
Fix the regex of optional mounts to match (normal) mounts
MichaelStubbings Sep 23, 2026
ee85172
update mount_paths in apptainer_entrypoint to optional_mount_paths
MichaelStubbings Sep 23, 2026
850dd60
Expand the description of mounts field to include optional_mounts
MichaelStubbings Sep 23, 2026
80e21d4
Make optional mounts their own field under options
MichaelStubbings Sep 23, 2026
b063a5c
Check there are no duplicate entries between mounts and optional mounts
MichaelStubbings Sep 23, 2026
d165e83
update golden master
MichaelStubbings Sep 24, 2026
7a5c7f1
Ensure entrypoint mounts are prioritised over global mounts
MichaelStubbings Sep 28, 2026
84f4806
exclude optional_mount code from entrypoint if length=0
MichaelStubbings Sep 29, 2026
151cc83
Add whitespace handling to entrypoint template
MichaelStubbings Sep 29, 2026
d369581
Move prioritisation of entrypoint mounts to app_builder.py
MichaelStubbings Sep 30, 2026
6c865fc
Move code resolving mounts to a helper function in app_builder.py
MichaelStubbings Sep 30, 2026
15e34c1
Change mount types from lists to sets
MichaelStubbings Sep 30, 2026
b724a78
update whitespace handlng in apptainer_entrypoint
MichaelStubbings Sep 30, 2026
31dad3b
Add unit test to validate entrypoint mounts overriding global mounts
MichaelStubbings Sep 30, 2026
da71b09
Remove warning when an optional path is non-existent
MichaelStubbings Sep 30, 2026
52c0efe
Update optional mount comment in apptainer_entrypoint
MichaelStubbings Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions src/deploy_tools/app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .apptainer import create_sif_file
from .errors import DeployToolsError
from .layout import ModuleBuildLayout
from .models.apptainer_app import ApptainerApp
from .models.apptainer_app import ApptainerApp, EntrypointOptions
from .models.module import Application, Module
from .models.shell_app import ShellApp
from .templater import Templater, TemplateType
Expand Down Expand Up @@ -56,7 +56,9 @@ def _create_apptainer_files(self, app: ApptainerApp, module: Module) -> None:
options = entrypoint.options
entrypoint_file = entrypoints_folder / entrypoint.name

mounts = ",".join(chain(global_options.mounts, options.mounts)).strip()
mounts, optional_mounts = self._resolve_entrypoint_mounts(
options, global_options
)
host_binaries = " ".join(
chain(global_options.host_binaries, options.host_binaries)
).strip()
Expand All @@ -71,6 +73,7 @@ def _create_apptainer_files(self, app: ApptainerApp, module: Module) -> None:

params = {
"mounts": mounts,
"optional_mounts": optional_mounts,
"host_binaries": host_binaries,
"apptainer_args": apptainer_args,
"relative_sif_file": relative_sif_file,
Expand All @@ -86,6 +89,28 @@ def _create_apptainer_files(self, app: ApptainerApp, module: Module) -> None:
create_parents=True,
)

@staticmethod
def _resolve_entrypoint_mounts(
options: EntrypointOptions, global_options: EntrypointOptions
) -> tuple[str, str]:
"""Allow entrypoint options to override mounts defined in global_options."""
entrypoint_mounts = options.mounts.union(options.optional_mounts)
global_mounts = (
mount for mount in global_options.mounts if mount not in entrypoint_mounts
)
global_optional_mounts = (
mount
for mount in global_options.optional_mounts
if mount not in entrypoint_mounts
)

mounts = ",".join(chain(global_mounts, options.mounts)).strip()
optional_mounts = ",".join(
chain(global_optional_mounts, options.optional_mounts)
).strip()

return mounts, optional_mounts

def _generate_sif_file(self, app: ApptainerApp, module: Module) -> None:
sif_file_path = self._get_sif_file_path(app, module)
create_sif_file(sif_file_path, app.container.url, create_parents=True)
Expand Down
32 changes: 27 additions & 5 deletions src/deploy_tools/models/apptainer_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated, Literal

from pydantic import AnyUrl, Field, StringConstraints, UrlConstraints
from pydantic import AnyUrl, Field, StringConstraints, UrlConstraints, model_validator

from .app import ENTRYPOINT_NAME_REGEX
from .parent import ParentModel
Expand All @@ -25,13 +25,24 @@ class EntrypointOptions(ParentModel):
] = ""

mounts: Annotated[
list[MountPoint],
set[MountPoint],
Field(
description="A list of mount points to add to the container in the form of "
description="A set of mount points that will result in an error if their "
"host paths cannot be found. This takes the form of "
"'host_path[:container_path[:opts]]' where opts (mount options) can be "
"'ro' or 'rw' and defaults to 'rw'"
"'ro' or 'rw' and defaults to 'rw'. "
),
] = []
] = set()

optional_mounts: Annotated[
set[MountPoint],
Field(
description="A set of mount points that will not be mounted if their host "
"paths cannot be found (avoiding an error). This takes the form of "
"'host_path[:container_path[:opts]]' where opts (mount options) "
"can be 'ro' or 'rw' and defaults to 'rw'."
),
] = set()

host_binaries: Annotated[
list[str],
Expand All @@ -42,6 +53,17 @@ class EntrypointOptions(ParentModel):
),
] = []

@model_validator(mode="after")
def check_unique_mounts(self) -> "EntrypointOptions":
"""Ensure that mounts and optional_mounts do not contain duplicates."""
duplicate_mounts = self.mounts.intersection(self.optional_mounts)
if duplicate_mounts:
raise ValueError(
f"Duplicate paths found in mounts and optional_mounts: "
f"{duplicate_mounts}"
)
return self


class Entrypoint(ParentModel):
"""Represents an entrypoint to a command on the Apptainer image.
Expand Down
17 changes: 15 additions & 2 deletions src/deploy_tools/models/schemas/deployment.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Global options that apply to all Entrypoints"
Expand Down Expand Up @@ -157,6 +158,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Options to apply for this entrypoint"
Expand Down Expand Up @@ -186,12 +188,23 @@
},
"mounts": {
"default": [],
"description": "A list of mount points to add to the container in the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'",
"description": "A set of mount points that will result in an error if their host paths cannot be found. This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'. ",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Mounts",
"type": "array"
"type": "array",
"uniqueItems": true
},
"optional_mounts": {
"default": [],
"description": "A set of mount points that will not be mounted if their host paths cannot be found (avoiding an error). This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'.",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Optional Mounts",
"type": "array",
"uniqueItems": true
},
"host_binaries": {
"default": [],
Expand Down
17 changes: 15 additions & 2 deletions src/deploy_tools/models/schemas/module.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Global options that apply to all Entrypoints"
Expand Down Expand Up @@ -138,6 +139,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Options to apply for this entrypoint"
Expand Down Expand Up @@ -167,12 +169,23 @@
},
"mounts": {
"default": [],
"description": "A list of mount points to add to the container in the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'",
"description": "A set of mount points that will result in an error if their host paths cannot be found. This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'. ",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Mounts",
"type": "array"
"type": "array",
"uniqueItems": true
},
"optional_mounts": {
"default": [],
"description": "A set of mount points that will not be mounted if their host paths cannot be found (avoiding an error). This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'.",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Optional Mounts",
"type": "array",
"uniqueItems": true
},
"host_binaries": {
"default": [],
Expand Down
17 changes: 15 additions & 2 deletions src/deploy_tools/models/schemas/release.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Global options that apply to all Entrypoints"
Expand Down Expand Up @@ -138,6 +139,7 @@
"apptainer_args": "",
"command_args": "",
"mounts": [],
"optional_mounts": [],
"host_binaries": []
},
"description": "Options to apply for this entrypoint"
Expand Down Expand Up @@ -167,12 +169,23 @@
},
"mounts": {
"default": [],
"description": "A list of mount points to add to the container in the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'",
"description": "A set of mount points that will result in an error if their host paths cannot be found. This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'. ",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Mounts",
"type": "array"
"type": "array",
"uniqueItems": true
},
"optional_mounts": {
"default": [],
"description": "A set of mount points that will not be mounted if their host paths cannot be found (avoiding an error). This takes the form of 'host_path[:container_path[:opts]]' where opts (mount options) can be 'ro' or 'rw' and defaults to 'rw'.",
"items": {
"$ref": "#/$defs/MountPoint"
},
"title": "Optional Mounts",
"type": "array",
"uniqueItems": true
},
"host_binaries": {
"default": [],
Expand Down
18 changes: 18 additions & 0 deletions src/deploy_tools/templates/apptainer_entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -e

# Mounts for container
mounts="{{ mounts }}"
optional_mounts="{{ optional_mounts }}"
# Additional arguments for apptainer
apptainer_args="{{ apptainer_args }}"
# Sif file path
Expand All @@ -17,6 +18,23 @@ command="{{ command }}"
# Options and arguments to pass to command
command_args="{{ command_args }}"

{% if optional_mounts|length %}
# Filter out optional mounts that are non-existent
IFS="," read -ra optional_mount_paths <<< "$optional_mounts"

validated_mounts=()

for mount_path in "${optional_mount_paths[@]}"; do
host_path="${mount_path%%:*}"

if [[ -d "$host_path" || -f "$host_path" ]]; then
validated_mounts+=("$mount_path")
fi
done

mounts="${mounts},$(IFS=','; echo "${validated_mounts[*]}")"

{% endif -%}
# Raise an error if sif file does not exist
if [[ ! -f ${sif_file} ]]; then
echo "ERROR: sif file ${sif_file} does not exist" 1>&2
Expand Down
2 changes: 2 additions & 0 deletions tests/samples/01-initial/deploy-tools-output/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ releases:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -e

# Mounts for container
mounts=""
optional_mounts=""
# Additional arguments for apptainer
apptainer_args=""
# Sif file path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ applications:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
2 changes: 2 additions & 0 deletions tests/samples/02-added/deploy-tools-output/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ releases:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -e

# Mounts for container
mounts=""
optional_mounts=""
# Additional arguments for apptainer
apptainer_args=""
# Sif file path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ applications:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
2 changes: 2 additions & 0 deletions tests/samples/03-updated/deploy-tools-output/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ releases:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -e

# Mounts for container
mounts=""
optional_mounts=""
# Additional arguments for apptainer
apptainer_args=""
# Sif file path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ applications:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ releases:
command_args: Hello
host_binaries: []
mounts: []
optional_mounts: []
global_options:
apptainer_args: ''
command_args: ''
host_binaries: []
mounts: []
optional_mounts: []
- app_type: shell
name: test-echo-module-var
script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -e

# Mounts for container
mounts=""
optional_mounts=""
# Additional arguments for apptainer
apptainer_args=""
# Sif file path
Expand Down
Loading
Loading