kblaunch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kblaunch-0.1.0/.github/workflows/release.yaml +21 -0
- kblaunch-0.1.0/.github/workflows/test.yaml +26 -0
- kblaunch-0.1.0/.gitignore +173 -0
- kblaunch-0.1.0/LICENSE +21 -0
- kblaunch-0.1.0/PKG-INFO +126 -0
- kblaunch-0.1.0/README.md +107 -0
- kblaunch-0.1.0/kblaunch/__init__.py +0 -0
- kblaunch-0.1.0/kblaunch/cli.py +195 -0
- kblaunch-0.1.0/pyproject.toml +41 -0
- kblaunch-0.1.0/tests/__init__.py +0 -0
- kblaunch-0.1.0/tests/test_cli.py +203 -0
- kblaunch-0.1.0/uv.lock +1358 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# publish.yml
|
|
2
|
+
name: "Publish"
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: ["published"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi:
|
|
9
|
+
name: Publish to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
# Environment and permissions trusted publishing.
|
|
12
|
+
environment:
|
|
13
|
+
# Create this environment in the GitHub repository under Settings -> Environments
|
|
14
|
+
name: release
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v3
|
|
20
|
+
- run: uv build
|
|
21
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# publish.yml
|
|
2
|
+
name: "Test"
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
jobs:
|
|
11
|
+
run:
|
|
12
|
+
name: "Run tests"
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v3
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
cache-dependency-glob: uv.lock
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
run: uv python install 3.12
|
|
24
|
+
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: uv run --extra test pytest
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
.python-version
|
|
3
|
+
.vscode/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
#uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
#poetry.lock
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
#pdm.lock
|
|
115
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
116
|
+
# in version control.
|
|
117
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
118
|
+
.pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
123
|
+
__pypackages__/
|
|
124
|
+
|
|
125
|
+
# Celery stuff
|
|
126
|
+
celerybeat-schedule
|
|
127
|
+
celerybeat.pid
|
|
128
|
+
|
|
129
|
+
# SageMath parsed files
|
|
130
|
+
*.sage.py
|
|
131
|
+
|
|
132
|
+
# Environments
|
|
133
|
+
.env
|
|
134
|
+
.venv
|
|
135
|
+
env/
|
|
136
|
+
venv/
|
|
137
|
+
ENV/
|
|
138
|
+
env.bak/
|
|
139
|
+
venv.bak/
|
|
140
|
+
|
|
141
|
+
# Spyder project settings
|
|
142
|
+
.spyderproject
|
|
143
|
+
.spyproject
|
|
144
|
+
|
|
145
|
+
# Rope project settings
|
|
146
|
+
.ropeproject
|
|
147
|
+
|
|
148
|
+
# mkdocs documentation
|
|
149
|
+
/site
|
|
150
|
+
|
|
151
|
+
# mypy
|
|
152
|
+
.mypy_cache/
|
|
153
|
+
.dmypy.json
|
|
154
|
+
dmypy.json
|
|
155
|
+
|
|
156
|
+
# Pyre type checker
|
|
157
|
+
.pyre/
|
|
158
|
+
|
|
159
|
+
# pytype static type analyzer
|
|
160
|
+
.pytype/
|
|
161
|
+
|
|
162
|
+
# Cython debug symbols
|
|
163
|
+
cython_debug/
|
|
164
|
+
|
|
165
|
+
# PyCharm
|
|
166
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
167
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
168
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
169
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
170
|
+
#.idea/
|
|
171
|
+
|
|
172
|
+
# PyPI configuration file
|
|
173
|
+
.pypirc
|
kblaunch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gautier Dagan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kblaunch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kblaunch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI tool for launching Kubernetes jobs in EIDF
|
|
5
|
+
Author-email: Gautier Dagan <gautier.dagan@ed.ac.uk>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: kubejobs>=0.4.5
|
|
10
|
+
Requires-Dist: kubernetes>=31.0.0
|
|
11
|
+
Requires-Dist: loguru>=0.7.3
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
13
|
+
Requires-Dist: typer>=0.15.1
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# kblaunch
|
|
21
|
+
|
|
22
|
+
[](https://github.com/gautierdag/kblaunch/actions/workflows/test.yaml)
|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
[](https://pypi.org/project/kblaunch/)
|
|
26
|
+
|
|
27
|
+
A CLI tool for launching Kubernetes jobs with environment variable and secret management.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install git+https://github.com/gautierdag/kblaunch.git
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Basic Usage
|
|
38
|
+
|
|
39
|
+
Launch a simple job:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
kblaunch \
|
|
43
|
+
--email your.email@ed.ac.uk \
|
|
44
|
+
--job-name myjob \
|
|
45
|
+
--command "python script.py"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With Environment Variables
|
|
49
|
+
|
|
50
|
+
1. From local environment:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kblaunch \
|
|
54
|
+
--job-name myjob \
|
|
55
|
+
--command "python script.py" \
|
|
56
|
+
--local-env-vars PATH \
|
|
57
|
+
--local-env-vars PYTHONPATH
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. From Kubernetes secrets:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
kblaunch \
|
|
64
|
+
--job-name myjob \
|
|
65
|
+
--command "python script.py" \
|
|
66
|
+
--secrets-env-vars mysecret1 \
|
|
67
|
+
--secrets-env-vars mysecret2
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. From .env file:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
kblaunch \
|
|
74
|
+
--job-name myjob \
|
|
75
|
+
--command "python script.py" \
|
|
76
|
+
--load-dotenv
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### GPU Jobs
|
|
80
|
+
|
|
81
|
+
Specify GPU requirements:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
kblaunch \
|
|
85
|
+
--job-name gpu-job \
|
|
86
|
+
--command "python train.py" \
|
|
87
|
+
--gpu-limit 2 \
|
|
88
|
+
--gpu-product "NVIDIA-A100-80GB-PCIe"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Interactive Mode
|
|
92
|
+
|
|
93
|
+
Launch an interactive job:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
kblaunch \
|
|
97
|
+
--job-name interactive \
|
|
98
|
+
--interactive
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Options
|
|
102
|
+
|
|
103
|
+
- `--email`: User email [required]
|
|
104
|
+
- `--job-name`: Name of the Kubernetes job [required]
|
|
105
|
+
- `--docker-image`: Docker image (default: "nvcr.io/nvidia/cuda:12.0.0-devel-ubuntu22.04")
|
|
106
|
+
- `--namespace`: Kubernetes namespace (default: "informatics")
|
|
107
|
+
- `--queue-name`: Kueue queue name
|
|
108
|
+
- `--interactive`: Run in interactive mode (default: False)
|
|
109
|
+
- `--command`: Command to run in the container [required]
|
|
110
|
+
- `--cpu-request`: CPU request (default: "1")
|
|
111
|
+
- `--ram-request`: RAM request (default: "8Gi")
|
|
112
|
+
- `--gpu-limit`: GPU limit (default: 1)
|
|
113
|
+
- `--gpu-product`: GPU product (default: "NVIDIA-A100-80GB-PCIe")
|
|
114
|
+
- `--secrets-env-vars`: List of secret environment variables
|
|
115
|
+
- `--local-env-vars`: List of local environment variables
|
|
116
|
+
- `--load-dotenv`: Load environment variables from .env file (default: True)
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- Kubernetes job management
|
|
121
|
+
- Environment variable handling from multiple sources
|
|
122
|
+
- Kubernetes secrets integration
|
|
123
|
+
- GPU job support
|
|
124
|
+
- Interactive mode
|
|
125
|
+
- Automatic job cleanup
|
|
126
|
+
- Slack notifications (when configured)
|
kblaunch-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# kblaunch
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gautierdag/kblaunch/actions/workflows/test.yaml)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://pypi.org/project/kblaunch/)
|
|
7
|
+
|
|
8
|
+
A CLI tool for launching Kubernetes jobs with environment variable and secret management.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install git+https://github.com/gautierdag/kblaunch.git
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Basic Usage
|
|
19
|
+
|
|
20
|
+
Launch a simple job:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
kblaunch \
|
|
24
|
+
--email your.email@ed.ac.uk \
|
|
25
|
+
--job-name myjob \
|
|
26
|
+
--command "python script.py"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### With Environment Variables
|
|
30
|
+
|
|
31
|
+
1. From local environment:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
kblaunch \
|
|
35
|
+
--job-name myjob \
|
|
36
|
+
--command "python script.py" \
|
|
37
|
+
--local-env-vars PATH \
|
|
38
|
+
--local-env-vars PYTHONPATH
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. From Kubernetes secrets:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
kblaunch \
|
|
45
|
+
--job-name myjob \
|
|
46
|
+
--command "python script.py" \
|
|
47
|
+
--secrets-env-vars mysecret1 \
|
|
48
|
+
--secrets-env-vars mysecret2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
3. From .env file:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
kblaunch \
|
|
55
|
+
--job-name myjob \
|
|
56
|
+
--command "python script.py" \
|
|
57
|
+
--load-dotenv
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### GPU Jobs
|
|
61
|
+
|
|
62
|
+
Specify GPU requirements:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
kblaunch \
|
|
66
|
+
--job-name gpu-job \
|
|
67
|
+
--command "python train.py" \
|
|
68
|
+
--gpu-limit 2 \
|
|
69
|
+
--gpu-product "NVIDIA-A100-80GB-PCIe"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Interactive Mode
|
|
73
|
+
|
|
74
|
+
Launch an interactive job:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
kblaunch \
|
|
78
|
+
--job-name interactive \
|
|
79
|
+
--interactive
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Options
|
|
83
|
+
|
|
84
|
+
- `--email`: User email [required]
|
|
85
|
+
- `--job-name`: Name of the Kubernetes job [required]
|
|
86
|
+
- `--docker-image`: Docker image (default: "nvcr.io/nvidia/cuda:12.0.0-devel-ubuntu22.04")
|
|
87
|
+
- `--namespace`: Kubernetes namespace (default: "informatics")
|
|
88
|
+
- `--queue-name`: Kueue queue name
|
|
89
|
+
- `--interactive`: Run in interactive mode (default: False)
|
|
90
|
+
- `--command`: Command to run in the container [required]
|
|
91
|
+
- `--cpu-request`: CPU request (default: "1")
|
|
92
|
+
- `--ram-request`: RAM request (default: "8Gi")
|
|
93
|
+
- `--gpu-limit`: GPU limit (default: 1)
|
|
94
|
+
- `--gpu-product`: GPU product (default: "NVIDIA-A100-80GB-PCIe")
|
|
95
|
+
- `--secrets-env-vars`: List of secret environment variables
|
|
96
|
+
- `--local-env-vars`: List of local environment variables
|
|
97
|
+
- `--load-dotenv`: Load environment variables from .env file (default: True)
|
|
98
|
+
|
|
99
|
+
## Features
|
|
100
|
+
|
|
101
|
+
- Kubernetes job management
|
|
102
|
+
- Environment variable handling from multiple sources
|
|
103
|
+
- Kubernetes secrets integration
|
|
104
|
+
- GPU job support
|
|
105
|
+
- Interactive mode
|
|
106
|
+
- Automatic job cleanup
|
|
107
|
+
- Slack notifications (when configured)
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from kubejobs.jobs import KubernetesJob, KueueQueue
|
|
6
|
+
from kubernetes import client, config
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_if_completed(job_name: str, namespace: str = "informatics") -> bool:
|
|
13
|
+
# Load the kube config
|
|
14
|
+
config.load_kube_config()
|
|
15
|
+
|
|
16
|
+
# Create an instance of the API class
|
|
17
|
+
api = client.BatchV1Api()
|
|
18
|
+
|
|
19
|
+
job_exists = False
|
|
20
|
+
is_completed = True
|
|
21
|
+
|
|
22
|
+
# Check if the job exists in the specified namespace
|
|
23
|
+
jobs = api.list_namespaced_job(namespace)
|
|
24
|
+
if job_name in {job.metadata.name for job in jobs.items}:
|
|
25
|
+
job_exists = True
|
|
26
|
+
|
|
27
|
+
if job_exists is True:
|
|
28
|
+
job = api.read_namespaced_job(job_name, namespace)
|
|
29
|
+
is_completed = False
|
|
30
|
+
|
|
31
|
+
# Check the status conditions
|
|
32
|
+
if job.status.conditions:
|
|
33
|
+
for condition in job.status.conditions:
|
|
34
|
+
if condition.type == "Complete" and condition.status == "True":
|
|
35
|
+
is_completed = True
|
|
36
|
+
elif condition.type == "Failed" and condition.status == "True":
|
|
37
|
+
logger.error(f"Job {job_name} has failed.")
|
|
38
|
+
else:
|
|
39
|
+
logger.info(f"Job {job_name} still running or status is unknown.")
|
|
40
|
+
|
|
41
|
+
if is_completed:
|
|
42
|
+
api_res = api.delete_namespaced_job(
|
|
43
|
+
name=job_name,
|
|
44
|
+
namespace=namespace,
|
|
45
|
+
body=client.V1DeleteOptions(propagation_policy="Foreground"),
|
|
46
|
+
)
|
|
47
|
+
logger.info(f"Job '{job_name}' deleted. Status: {api_res.status}")
|
|
48
|
+
return is_completed
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def send_message_command(env_vars: dict) -> str:
|
|
52
|
+
if "SLACK_WEBHOOK" not in env_vars:
|
|
53
|
+
logger.debug("SLACK_WEBHOOK not found in env_vars.")
|
|
54
|
+
return ""
|
|
55
|
+
webhook = env_vars["SLACK_WEBHOOK"]
|
|
56
|
+
return (
|
|
57
|
+
"""curl -X POST -H 'Content-type: application/json' --data '{"text":"Job started in '"$POD_NAME"'"}' """
|
|
58
|
+
+ webhook
|
|
59
|
+
+ " ; "
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_env_vars(
|
|
64
|
+
local_env_vars: list[str],
|
|
65
|
+
secrets_env_vars: list[str],
|
|
66
|
+
load_dotenv: bool = False,
|
|
67
|
+
namespace: str = "informatics",
|
|
68
|
+
) -> dict:
|
|
69
|
+
"""Get environment variables from local environment and secrets."""
|
|
70
|
+
|
|
71
|
+
if load_dotenv:
|
|
72
|
+
from dotenv import load_dotenv
|
|
73
|
+
|
|
74
|
+
load_dotenv()
|
|
75
|
+
|
|
76
|
+
env_vars = {}
|
|
77
|
+
for var_name in local_env_vars:
|
|
78
|
+
value = os.getenv(var_name)
|
|
79
|
+
if value is not None:
|
|
80
|
+
env_vars[var_name] = value
|
|
81
|
+
else:
|
|
82
|
+
logger.warning(
|
|
83
|
+
f"Environment variable {var_name} not found in local environment"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
for secret_name in secrets_env_vars:
|
|
87
|
+
v1 = client.CoreV1Api()
|
|
88
|
+
try:
|
|
89
|
+
secret = v1.read_namespaced_secret(name=secret_name, namespace=namespace)
|
|
90
|
+
for key, value in secret.data.items():
|
|
91
|
+
decoded_value = base64.b64decode(value).decode("utf-8")
|
|
92
|
+
if key in env_vars:
|
|
93
|
+
logger.warning(f"Key {key} already set in env_vars.")
|
|
94
|
+
env_vars[key] = decoded_value
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Error reading secret {secret_name}: {e}")
|
|
97
|
+
raise e
|
|
98
|
+
|
|
99
|
+
return env_vars
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def export_env_vars(env_vars: dict) -> str:
|
|
103
|
+
"""Export environment variables."""
|
|
104
|
+
cmd = ""
|
|
105
|
+
for key, value in env_vars.items():
|
|
106
|
+
cmd += f" export {key}='{value}' &&"
|
|
107
|
+
cmd = cmd.strip(" &&") + " ; "
|
|
108
|
+
return cmd
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.command()
|
|
112
|
+
def launch(
|
|
113
|
+
email: str = typer.Option(..., help="User email"),
|
|
114
|
+
job_name: str = typer.Option(..., help="Name of the Kubernetes job"),
|
|
115
|
+
docker_image: str = typer.Option(
|
|
116
|
+
"nvcr.io/nvidia/cuda:12.0.0-devel-ubuntu22.04", help="Docker image"
|
|
117
|
+
),
|
|
118
|
+
namespace: str = typer.Option("informatics", help="Kubernetes namespace"),
|
|
119
|
+
queue_name: str = typer.Option(KueueQueue.INFORMATICS, help="Kueue queue name"),
|
|
120
|
+
interactive: bool = typer.Option(False, help="Run in interactive mode"),
|
|
121
|
+
command: str = typer.Option(..., help="Command to run in the container"),
|
|
122
|
+
cpu_request: str = typer.Option("1", help="CPU request"),
|
|
123
|
+
ram_request: str = typer.Option("8Gi", help="RAM request"),
|
|
124
|
+
gpu_limit: int = typer.Option(1, help="GPU limit"),
|
|
125
|
+
gpu_product: str = typer.Option("NVIDIA-A100-80GB-PCIe", help="GPU product"),
|
|
126
|
+
secrets_env_vars: list[str] = typer.Option(
|
|
127
|
+
[], # Use empty list as default instead of None
|
|
128
|
+
help="List of secret environment variables to export to the container",
|
|
129
|
+
),
|
|
130
|
+
local_env_vars: list[str] = typer.Option(
|
|
131
|
+
[], # Use empty list as default instead of None
|
|
132
|
+
help="List of local environment variables to export to the container",
|
|
133
|
+
),
|
|
134
|
+
load_dotenv: bool = typer.Option(
|
|
135
|
+
True, help="Load environment variables from .env file"
|
|
136
|
+
),
|
|
137
|
+
):
|
|
138
|
+
"""Launch a Kubernetes job with the specified configuration."""
|
|
139
|
+
|
|
140
|
+
is_completed = check_if_completed(job_name, namespace=namespace)
|
|
141
|
+
|
|
142
|
+
if is_completed is True:
|
|
143
|
+
logger.info(f"Job '{job_name}' is completed. Launching a new job.")
|
|
144
|
+
|
|
145
|
+
if interactive:
|
|
146
|
+
cmd = "while true; do sleep 60; done;"
|
|
147
|
+
else:
|
|
148
|
+
cmd = command
|
|
149
|
+
logger.info(f"Command: {cmd}")
|
|
150
|
+
|
|
151
|
+
# Get local environment variables
|
|
152
|
+
env_vars = get_env_vars(
|
|
153
|
+
local_env_vars=local_env_vars,
|
|
154
|
+
secrets_env_vars=secrets_env_vars,
|
|
155
|
+
load_dotenv=load_dotenv,
|
|
156
|
+
namespace=namespace,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
logger.info(f"Creating job for: {cmd}")
|
|
160
|
+
job = KubernetesJob(
|
|
161
|
+
name=job_name,
|
|
162
|
+
cpu_request=cpu_request,
|
|
163
|
+
ram_request=ram_request,
|
|
164
|
+
image=docker_image,
|
|
165
|
+
gpu_type="nvidia.com/gpu",
|
|
166
|
+
gpu_limit=gpu_limit,
|
|
167
|
+
gpu_product=gpu_product,
|
|
168
|
+
backoff_limit=0,
|
|
169
|
+
command=["/bin/bash", "-c", "--"],
|
|
170
|
+
args=[export_env_vars(env_vars) + send_message_command(env_vars) + cmd],
|
|
171
|
+
user_email=email,
|
|
172
|
+
namespace=namespace,
|
|
173
|
+
kueue_queue_name=queue_name,
|
|
174
|
+
secret_env_vars=env_vars,
|
|
175
|
+
volume_mounts={
|
|
176
|
+
"nfs": {"mountPath": "/nfs", "server": "10.24.1.255", "path": "/"}
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
job_yaml = job.generate_yaml()
|
|
181
|
+
logger.info(job_yaml)
|
|
182
|
+
|
|
183
|
+
# Run the Job on the Kubernetes cluster
|
|
184
|
+
job.run()
|
|
185
|
+
else:
|
|
186
|
+
logger.info(f"Job '{job_name}' is still running.")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def cli():
|
|
190
|
+
"""Entry point for the application"""
|
|
191
|
+
app()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
cli()
|