Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 46 additions & 0 deletions .github/workflows/test-mlc-apptainer-core.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: MLC Apptainer core action tests

on:
pull_request:
branches: [ "main", "dev" ]
paths:
- '.github/workflows/test-mlc-apptainer-core.yml'
- 'mlc/**'
- 'tests/**'
- 'pyproject.toml'
- '!**.md'

permissions:
contents: read

jobs:
test_mlc_apptainer_core:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.14", "3.9"]

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}

- name: Install mlcflow from the pull request's source repository and branch
run: |
python -m pip install --upgrade pip
python -m pip install --ignore-installed --verbose pip setuptools
python -m pip install .

- name: Test apptainer delegation unit test
run: |
python -m unittest tests.test_script_action_apptainer -v

- name: Test apptainer CLI help paths
run: |
mlca --help | grep -q "apptainer"
mlc apptainer script --help | grep -q "apptainer"
mlc apptainer-run script --help | grep -q "apptainer"
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2.4
1.2.5
10 changes: 5 additions & 5 deletions mlc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def build_parser(pre_args):
reindex_parser.add_argument('extra', nargs=argparse.REMAINDER)

# Script-only
for action in ['docker', 'docker-run', 'apptainer',
for action in ['docker', 'docker-run', 'apptainer', 'apptainer-run',
'experiment', 'remote-run', 'remote-experiment',
'remote-docker', 'doc', 'lint']:
p = subparsers.add_parser(action, add_help=False)
Expand Down Expand Up @@ -432,8 +432,8 @@ def build_run_args(args):
if args.command in ['pull', 'rm', 'add', 'find'] and args.target == "repo":
run_args['repo'] = args.details

if args.command in ['docker', 'docker-run', 'apptainer', 'experiment',
'remote-run', 'remote-experiment',
if args.command in ['docker', 'docker-run', 'apptainer', 'apptainer-run',
'experiment', 'remote-run', 'remote-experiment',
'remote-docker', 'doc', 'lint'] and args.target == "run":
# run_args['target'] = 'script' #dont modify this as script might have
# target as in input
Expand Down Expand Up @@ -507,7 +507,7 @@ def main():

| Target | Actions |
|---------|-----------------------------------------------------------|
| script | run, find/search, rm, mv, cp, add, test, docker-run, show |
| script | run, find/search, rm, mv, cp, add, test, docker-run, apptainer/apptainer-run, show |
| cache | find/search, rm, show, list, prune, mark-tmp |
| repo | pull, search, rm, list, find/search |

Expand Down Expand Up @@ -560,7 +560,7 @@ def main():
help_text = ""
if pre_args.target == "run":
if pre_args.action.startswith(
"docker") or pre_args.action == "apptainer":
"docker") or pre_args.action in ("apptainer", "apptainer-run"):
pre_args.target = "script"
else:
logger.error(
Expand Down
35 changes: 33 additions & 2 deletions mlc/script_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ class ScriptAction(Action):
6. Copy(cp)
7. Run
8. Docker
9. Test
10. Experiment
9. Apptainer
10. Test
11. Experiment

Scripts in MLCFlow can be identified using different methods:

Expand Down Expand Up @@ -285,6 +286,9 @@ def call_script_module_function(self, function_name, run_args):
elif function_name == "docker":
result = automation_instance.docker(
run_args) # Pass args to the run method
elif function_name == "apptainer":
result = automation_instance.apptainer(
run_args) # Pass args to the apptainer method
elif function_name == "test":
result = automation_instance.test(
run_args) # Pass args to the run method
Expand Down Expand Up @@ -414,6 +418,33 @@ def docker_run(self, run_args):

docker.__doc__ = docker_run.__doc__

def apptainer(self, run_args):
return self.apptainer_run(run_args)

def apptainer_run(self, run_args):
"""
####################################################################################################################
Target: Script
Action: Apptainer
####################################################################################################################

The `apptainer` action runs scripts inside an Apptainer containerized environment.

An MLCFlow script can be executed inside Apptainer using either of the following syntaxes:

1. Apptainer Run: mlc apptainer run --tags=<script tags> <run flags>
2. Apptainer Script: mlc apptainer script --tags=<script tags> <run flags>

Example Command:

mlc apptainer script --tags=detect,os -j
mlca detect,os -j

"""
return self.call_script_module_function("apptainer", run_args)

apptainer.__doc__ = apptainer_run.__doc__

def remote_run(self, run_args):
"""
####################################################################################################################
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "mlcflow"
version = "1.2.4"
version = "1.2.5"

description = "An automation interface tailored for CPU/GPU benchmarking"
authors = [
Expand Down
29 changes: 29 additions & 0 deletions tests/test_script_action_apptainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import unittest
from unittest.mock import patch

from mlc.script_action import ScriptAction


class _Parent:
pass


class ScriptActionApptainerTest(unittest.TestCase):
def test_apptainer_delegates_to_script_module(self):
action = ScriptAction(_Parent())
run_args = {"tags": "detect,os"}
expected = {"return": 0}

with patch.object(
ScriptAction,
"call_script_module_function",
return_value=expected) as call_script_module_function:
result = action.apptainer(run_args)

self.assertEqual(result, expected)
call_script_module_function.assert_called_once_with(
"apptainer", run_args)


if __name__ == "__main__":
unittest.main()
Loading