truefoundry 0.11.12__py3-none-any.whl → 0.12.0rc2__py3-none-any.whl
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.
Potentially problematic release.
This version of truefoundry might be problematic. Click here for more details.
- truefoundry/common/constants.py +8 -1
- truefoundry/common/utils.py +4 -0
- truefoundry/deploy/__init__.py +3 -0
- truefoundry/deploy/_autogen/models.py +192 -145
- truefoundry/deploy/builder/__init__.py +1 -0
- truefoundry/deploy/builder/builders/dockerfile.py +4 -6
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +3 -6
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +351 -84
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/dockerfile_template.py +19 -25
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/dockerfile_template.py +19 -29
- truefoundry/deploy/builder/constants.py +12 -0
- truefoundry/deploy/builder/utils.py +223 -21
- truefoundry/deploy/lib/util.py +120 -0
- truefoundry/deploy/v2/lib/deploy.py +7 -1
- truefoundry/deploy/v2/lib/patched_models.py +60 -11
- truefoundry/deploy/v2/lib/source.py +6 -31
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc2.dist-info}/METADATA +2 -1
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc2.dist-info}/RECORD +22 -22
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc2.dist-info}/WHEEL +0 -0
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc2.dist-info}/entry_points.txt +0 -0
|
@@ -1,67 +1,150 @@
|
|
|
1
|
-
from
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from typing import Dict, List, Optional, Set, Union
|
|
2
3
|
|
|
3
|
-
from
|
|
4
|
+
from jinja2 import Template
|
|
4
5
|
|
|
5
6
|
from truefoundry.common.constants import ENV_VARS, PythonPackageManager
|
|
6
|
-
from truefoundry.deploy._autogen.models import
|
|
7
|
-
from truefoundry.deploy.builder.constants import (
|
|
8
|
-
PIP_CONF_BUILDKIT_SECRET_MOUNT,
|
|
9
|
-
UV_CONF_BUILDKIT_SECRET_MOUNT,
|
|
10
|
-
)
|
|
7
|
+
from truefoundry.deploy._autogen.models import UV, Pip, Poetry
|
|
11
8
|
from truefoundry.deploy.builder.utils import (
|
|
9
|
+
check_whether_poetry_toml_exists,
|
|
12
10
|
generate_apt_install_command,
|
|
11
|
+
generate_command_to_install_from_uv_lock,
|
|
13
12
|
generate_pip_install_command,
|
|
13
|
+
generate_poetry_install_command,
|
|
14
|
+
generate_secret_mounts,
|
|
14
15
|
generate_uv_pip_install_command,
|
|
16
|
+
get_available_secrets,
|
|
15
17
|
)
|
|
16
18
|
from truefoundry.deploy.v2.lib.patched_models import (
|
|
17
19
|
CUDAVersion,
|
|
20
|
+
PythonBuild,
|
|
18
21
|
_resolve_requirements_path,
|
|
19
22
|
)
|
|
23
|
+
from truefoundry.pydantic_v1 import BaseModel
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TemplateContext(BaseModel):
|
|
27
|
+
"""Pydantic model for template context used in Dockerfile generation."""
|
|
28
|
+
|
|
29
|
+
# Common fields
|
|
30
|
+
python_image_repo: str
|
|
31
|
+
python_version: str
|
|
32
|
+
apt_install_command: Optional[str] = None
|
|
33
|
+
package_manager_config_secret_mount: str = ""
|
|
34
|
+
|
|
35
|
+
# Pip-specific fields
|
|
36
|
+
requirements_path: Optional[str] = None
|
|
37
|
+
requirements_destination_path: Optional[str] = None
|
|
38
|
+
python_packages_install_command: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
# UV-specific fields
|
|
41
|
+
final_uv_sync_command: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
# Poetry-specific fields
|
|
44
|
+
final_poetry_install_command: Optional[str] = None
|
|
45
|
+
poetry_version_expression: Optional[str] = None
|
|
46
|
+
poetry_toml_exists: bool = False
|
|
47
|
+
|
|
20
48
|
|
|
21
|
-
|
|
49
|
+
CUDA_BASE_IMAGE_TEMPLATE = """\
|
|
50
|
+
FROM nvidia/cuda:{{ cuda_image_tag }} AS base
|
|
51
|
+
SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-c"]
|
|
52
|
+
|
|
53
|
+
ENV PATH=/virtualenvs/venv/bin:$PATH
|
|
54
|
+
ENV VIRTUAL_ENV=/virtualenvs/venv/
|
|
55
|
+
RUN echo "deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \\
|
|
56
|
+
echo "deb-src https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \\
|
|
57
|
+
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F23C5A6CF475977595C89F51BA6932366A755776 && \\
|
|
58
|
+
apt update && \\
|
|
59
|
+
DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git python{{ python_version }}-dev python{{ python_version }}-venv && \\
|
|
60
|
+
python{{ python_version }} -m venv /virtualenvs/venv/ && \\
|
|
61
|
+
rm -rf /var/lib/apt/lists/* && \\
|
|
62
|
+
python -m pip install -U pip setuptools wheel
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
STANDARD_BASE_IMAGE_TEMPLATE = """\
|
|
66
|
+
FROM {{ python_image_repo }}:{{ python_version }} AS base
|
|
67
|
+
SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-c"]
|
|
68
|
+
|
|
69
|
+
ENV PATH=/virtualenvs/venv/bin:$PATH
|
|
70
|
+
ENV VIRTUAL_ENV=/virtualenvs/venv/
|
|
71
|
+
RUN apt update && \\
|
|
72
|
+
DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git && \\
|
|
73
|
+
python -m venv /virtualenvs/venv/ && \\
|
|
74
|
+
rm -rf /var/lib/apt/lists/* && \\
|
|
75
|
+
python -m pip install -U pip setuptools wheel
|
|
76
|
+
"""
|
|
22
77
|
|
|
23
|
-
DEFAULT_PYTHON_IMAGE_REPO = "public.ecr.aws/docker/library/python"
|
|
24
78
|
|
|
25
|
-
|
|
26
|
-
% if apt_install_command
|
|
27
|
-
RUN
|
|
28
|
-
% endif
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
79
|
+
APT_PACKAGES_TEMPLATE = """
|
|
80
|
+
{% if apt_install_command %}
|
|
81
|
+
RUN {{ apt_install_command }}
|
|
82
|
+
{% endif %}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
PIP_DEPENDENCIES_TEMPLATE = """\
|
|
87
|
+
{% if requirements_path %}
|
|
88
|
+
COPY {{ requirements_path }} {{ requirements_destination_path }}
|
|
89
|
+
{% endif %}
|
|
90
|
+
|
|
91
|
+
{% if python_packages_install_command %}
|
|
92
|
+
RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
|
|
93
|
+
{% endif %}
|
|
94
|
+
|
|
35
95
|
COPY . /app
|
|
36
96
|
WORKDIR /app
|
|
37
97
|
"""
|
|
38
98
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
99
|
+
UV_DEPENDENCIES_TEMPLATE = """\
|
|
100
|
+
# Set up UV environment
|
|
101
|
+
WORKDIR /app
|
|
102
|
+
COPY pyproject.toml uv.lock .
|
|
103
|
+
{% if python_packages_install_command %}
|
|
104
|
+
RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
|
|
105
|
+
{% endif %}
|
|
106
|
+
|
|
107
|
+
COPY ./ .
|
|
108
|
+
{% if final_uv_sync_command %}
|
|
109
|
+
RUN {{ package_manager_config_secret_mount }} {{ final_uv_sync_command }}
|
|
110
|
+
{% endif %}
|
|
47
111
|
"""
|
|
48
|
-
+ _POST_PYTHON_INSTALL_TEMPLATE
|
|
49
|
-
)
|
|
50
112
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
113
|
+
POETRY_DEPENDENCIES_TEMPLATE = """\
|
|
114
|
+
RUN python -m venv /opt/poetry
|
|
115
|
+
ENV PATH="/opt/poetry/bin:$PATH"
|
|
116
|
+
RUN pip install -q --no-cache-dir -U "poetry{{ poetry_version_expression }}"
|
|
117
|
+
|
|
118
|
+
WORKDIR /app
|
|
119
|
+
COPY pyproject.toml poetry.lock .
|
|
120
|
+
{% if poetry_toml_exists %}
|
|
121
|
+
COPY poetry.toml .
|
|
122
|
+
{%- endif %}
|
|
123
|
+
RUN poetry config virtualenvs.create false --local \\
|
|
124
|
+
&& poetry config virtualenvs.in-project false --local
|
|
125
|
+
|
|
126
|
+
ENV PATH="/virtualenvs/venv/bin:$PATH"
|
|
127
|
+
ENV VIRTUAL_ENV="/virtualenvs/venv/"
|
|
128
|
+
|
|
129
|
+
{% if python_packages_install_command %}
|
|
130
|
+
RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
|
|
131
|
+
{% endif %}
|
|
132
|
+
|
|
133
|
+
COPY ./ .
|
|
134
|
+
|
|
135
|
+
{% if final_poetry_install_command %}
|
|
136
|
+
RUN {{ package_manager_config_secret_mount }} {{ final_poetry_install_command }}
|
|
137
|
+
{% endif %}
|
|
138
|
+
|
|
139
|
+
RUN rm -rf /opt/poetry
|
|
62
140
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
141
|
+
|
|
142
|
+
DOCKERFILE_TEMPLATE = """\
|
|
143
|
+
{{ base_image_setup }}
|
|
144
|
+
{{ apt_packages_section }}
|
|
145
|
+
{{ python_dependencies_section }}
|
|
146
|
+
"""
|
|
147
|
+
|
|
65
148
|
|
|
66
149
|
CUDA_VERSION_TO_IMAGE_TAG: Dict[str, str] = {
|
|
67
150
|
CUDAVersion.CUDA_11_0_CUDNN8.value: "11.0.3-cudnn8-runtime-ubuntu20.04",
|
|
@@ -89,68 +172,252 @@ CUDA_VERSION_TO_IMAGE_TAG: Dict[str, str] = {
|
|
|
89
172
|
def generate_dockerfile_content(
|
|
90
173
|
build_configuration: PythonBuild,
|
|
91
174
|
package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
|
|
92
|
-
|
|
175
|
+
docker_build_extra_args: Optional[List[str]] = None,
|
|
93
176
|
) -> str:
|
|
177
|
+
if isinstance(build_configuration, dict):
|
|
178
|
+
build_configuration = PythonBuild(**build_configuration)
|
|
179
|
+
if not build_configuration.python_version:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Set up Python dependencies
|
|
185
|
+
python_dependencies = deepcopy(build_configuration.python_dependencies)
|
|
186
|
+
if not python_dependencies:
|
|
187
|
+
python_dependencies = Pip(
|
|
188
|
+
type="pip",
|
|
189
|
+
requirements_path=build_configuration.requirements_path,
|
|
190
|
+
pip_packages=build_configuration.pip_packages,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Get available secrets from docker build extra args
|
|
194
|
+
available_secrets = set()
|
|
195
|
+
if docker_build_extra_args:
|
|
196
|
+
available_secrets = get_available_secrets(docker_build_extra_args)
|
|
197
|
+
|
|
198
|
+
# Prepare template context
|
|
199
|
+
context = _build_template_context(
|
|
200
|
+
build_configuration=build_configuration,
|
|
201
|
+
python_dependencies=python_dependencies,
|
|
202
|
+
package_manager=package_manager,
|
|
203
|
+
available_secrets=available_secrets,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Build sections
|
|
207
|
+
base_image_setup = _build_base_image_section(build_configuration)
|
|
208
|
+
apt_packages_section = _build_apt_packages_section(context.apt_install_command)
|
|
209
|
+
python_dependencies_section = _build_python_dependencies_section(
|
|
210
|
+
python_dependencies.type, context
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
template = Template(DOCKERFILE_TEMPLATE)
|
|
214
|
+
return template.render(
|
|
215
|
+
base_image_setup=base_image_setup,
|
|
216
|
+
apt_packages_section=apt_packages_section,
|
|
217
|
+
python_dependencies_section=python_dependencies_section,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _build_template_context(
|
|
222
|
+
build_configuration: PythonBuild,
|
|
223
|
+
python_dependencies: Union[Pip, UV, Poetry],
|
|
224
|
+
package_manager: str,
|
|
225
|
+
available_secrets: Set[str],
|
|
226
|
+
) -> TemplateContext:
|
|
227
|
+
if not build_configuration.python_version:
|
|
228
|
+
raise ValueError(
|
|
229
|
+
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
230
|
+
)
|
|
231
|
+
# Set up package manager config secret mount
|
|
232
|
+
python_dependencies_type = python_dependencies.type
|
|
233
|
+
package_manager_config_secret_mount = ""
|
|
234
|
+
if available_secrets:
|
|
235
|
+
package_manager_config_secret_mount = generate_secret_mounts(
|
|
236
|
+
available_secrets=available_secrets,
|
|
237
|
+
python_dependencies_type=python_dependencies_type,
|
|
238
|
+
package_manager=package_manager,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Configure dependencies based on type
|
|
242
|
+
if isinstance(python_dependencies, UV):
|
|
243
|
+
uv_context = _build_uv_context(
|
|
244
|
+
python_dependencies,
|
|
245
|
+
available_secrets,
|
|
246
|
+
)
|
|
247
|
+
return TemplateContext(
|
|
248
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
249
|
+
python_version=build_configuration.python_version,
|
|
250
|
+
apt_install_command=generate_apt_install_command(
|
|
251
|
+
apt_packages=build_configuration.apt_packages
|
|
252
|
+
),
|
|
253
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
254
|
+
python_packages_install_command=uv_context[
|
|
255
|
+
"python_packages_install_command"
|
|
256
|
+
],
|
|
257
|
+
final_uv_sync_command=uv_context["final_uv_sync_command"],
|
|
258
|
+
)
|
|
259
|
+
elif isinstance(python_dependencies, Poetry):
|
|
260
|
+
poetry_toml_exists = check_whether_poetry_toml_exists(
|
|
261
|
+
build_context_path=build_configuration.build_context_path,
|
|
262
|
+
)
|
|
263
|
+
poetry_context = _build_poetry_context(python_dependencies, available_secrets)
|
|
264
|
+
return TemplateContext(
|
|
265
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
266
|
+
python_version=build_configuration.python_version,
|
|
267
|
+
apt_install_command=generate_apt_install_command(
|
|
268
|
+
apt_packages=build_configuration.apt_packages
|
|
269
|
+
),
|
|
270
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
271
|
+
python_packages_install_command=poetry_context[
|
|
272
|
+
"python_packages_install_command"
|
|
273
|
+
],
|
|
274
|
+
final_poetry_install_command=poetry_context["final_poetry_install_command"],
|
|
275
|
+
poetry_version_expression=poetry_context["poetry_version_expression"],
|
|
276
|
+
poetry_toml_exists=poetry_toml_exists,
|
|
277
|
+
)
|
|
278
|
+
elif isinstance(python_dependencies, Pip):
|
|
279
|
+
pip_context = _build_pip_context(
|
|
280
|
+
build_configuration,
|
|
281
|
+
python_dependencies,
|
|
282
|
+
package_manager,
|
|
283
|
+
available_secrets,
|
|
284
|
+
)
|
|
285
|
+
return TemplateContext(
|
|
286
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
287
|
+
python_version=build_configuration.python_version,
|
|
288
|
+
apt_install_command=generate_apt_install_command(
|
|
289
|
+
apt_packages=build_configuration.apt_packages
|
|
290
|
+
),
|
|
291
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
292
|
+
requirements_path=pip_context["requirements_path"],
|
|
293
|
+
requirements_destination_path=pip_context["requirements_destination_path"],
|
|
294
|
+
python_packages_install_command=pip_context[
|
|
295
|
+
"python_packages_install_command"
|
|
296
|
+
],
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _build_base_image_section(build_configuration: PythonBuild) -> str:
|
|
303
|
+
if build_configuration.cuda_version:
|
|
304
|
+
cuda_image_tag = CUDA_VERSION_TO_IMAGE_TAG.get(
|
|
305
|
+
build_configuration.cuda_version, build_configuration.cuda_version
|
|
306
|
+
)
|
|
307
|
+
template = Template(CUDA_BASE_IMAGE_TEMPLATE)
|
|
308
|
+
return template.render(
|
|
309
|
+
cuda_image_tag=cuda_image_tag,
|
|
310
|
+
python_version=build_configuration.python_version,
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
template = Template(STANDARD_BASE_IMAGE_TEMPLATE)
|
|
314
|
+
return template.render(
|
|
315
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
316
|
+
python_version=build_configuration.python_version,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _build_apt_packages_section(apt_install_command: Optional[str]) -> str:
|
|
321
|
+
template = Template(APT_PACKAGES_TEMPLATE)
|
|
322
|
+
return template.render(apt_install_command=apt_install_command)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _build_python_dependencies_section(
|
|
326
|
+
python_dependencies_type: str, context: TemplateContext
|
|
327
|
+
) -> str:
|
|
328
|
+
if python_dependencies_type == "pip":
|
|
329
|
+
template = Template(PIP_DEPENDENCIES_TEMPLATE)
|
|
330
|
+
return template.render(**context.dict())
|
|
331
|
+
elif python_dependencies_type == "uv":
|
|
332
|
+
template = Template(UV_DEPENDENCIES_TEMPLATE)
|
|
333
|
+
return template.render(**context.dict())
|
|
334
|
+
elif python_dependencies_type == "poetry":
|
|
335
|
+
template = Template(POETRY_DEPENDENCIES_TEMPLATE)
|
|
336
|
+
return template.render(**context.dict())
|
|
337
|
+
else:
|
|
338
|
+
raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _build_uv_context(python_dependencies: UV, available_secrets: Set[str]) -> Dict:
|
|
342
|
+
python_packages_install_command = generate_command_to_install_from_uv_lock(
|
|
343
|
+
sync_options=python_dependencies.sync_options,
|
|
344
|
+
uv_version=python_dependencies.uv_version,
|
|
345
|
+
available_secrets=available_secrets,
|
|
346
|
+
)
|
|
347
|
+
final_uv_sync_command = generate_command_to_install_from_uv_lock(
|
|
348
|
+
sync_options=python_dependencies.sync_options,
|
|
349
|
+
uv_version=python_dependencies.uv_version,
|
|
350
|
+
install_project=True,
|
|
351
|
+
available_secrets=available_secrets,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
"python_packages_install_command": python_packages_install_command,
|
|
356
|
+
"final_uv_sync_command": final_uv_sync_command,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _build_poetry_context(
|
|
361
|
+
python_dependencies: Poetry, available_secrets: Set[str]
|
|
362
|
+
) -> Dict:
|
|
363
|
+
python_packages_install_command = generate_poetry_install_command(
|
|
364
|
+
install_options=python_dependencies.install_options,
|
|
365
|
+
available_secrets=available_secrets,
|
|
366
|
+
)
|
|
367
|
+
final_poetry_install_command = generate_poetry_install_command(
|
|
368
|
+
install_options=python_dependencies.install_options,
|
|
369
|
+
install_project=True,
|
|
370
|
+
available_secrets=available_secrets,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
poetry_version = python_dependencies.poetry_version
|
|
374
|
+
# Handle "latest" poetry version by using major version constraint
|
|
375
|
+
if poetry_version == "latest":
|
|
376
|
+
major_version = ENV_VARS.TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION
|
|
377
|
+
poetry_version_expression = f">={major_version},<{major_version + 1}"
|
|
378
|
+
else:
|
|
379
|
+
# For regular versions, use ~= operator for compatibility
|
|
380
|
+
poetry_version_expression = f"~={poetry_version}"
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
"python_packages_install_command": python_packages_install_command,
|
|
384
|
+
"final_poetry_install_command": final_poetry_install_command,
|
|
385
|
+
"poetry_version_expression": poetry_version_expression,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _build_pip_context(
|
|
390
|
+
build_configuration: PythonBuild,
|
|
391
|
+
python_dependencies: Pip,
|
|
392
|
+
package_manager: str,
|
|
393
|
+
available_secrets: Set[str],
|
|
394
|
+
) -> Dict:
|
|
94
395
|
# TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
|
|
95
396
|
requirements_path = _resolve_requirements_path(
|
|
96
397
|
build_context_path=build_configuration.build_context_path,
|
|
97
|
-
requirements_path=
|
|
398
|
+
requirements_path=python_dependencies.requirements_path,
|
|
98
399
|
)
|
|
99
400
|
requirements_destination_path = (
|
|
100
401
|
"/tmp/requirements.txt" if requirements_path else None
|
|
101
402
|
)
|
|
102
|
-
|
|
103
|
-
raise ValueError(
|
|
104
|
-
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
105
|
-
)
|
|
403
|
+
|
|
106
404
|
if package_manager == PythonPackageManager.PIP.value:
|
|
107
405
|
python_packages_install_command = generate_pip_install_command(
|
|
108
406
|
requirements_path=requirements_destination_path,
|
|
109
|
-
pip_packages=
|
|
110
|
-
|
|
407
|
+
pip_packages=python_dependencies.pip_packages,
|
|
408
|
+
available_secrets=available_secrets,
|
|
111
409
|
)
|
|
112
410
|
elif package_manager == PythonPackageManager.UV.value:
|
|
113
411
|
python_packages_install_command = generate_uv_pip_install_command(
|
|
114
412
|
requirements_path=requirements_destination_path,
|
|
115
|
-
pip_packages=
|
|
116
|
-
|
|
413
|
+
pip_packages=python_dependencies.pip_packages,
|
|
414
|
+
available_secrets=available_secrets,
|
|
117
415
|
)
|
|
118
416
|
else:
|
|
119
417
|
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
120
418
|
|
|
121
|
-
|
|
122
|
-
apt_packages=build_configuration.apt_packages
|
|
123
|
-
)
|
|
124
|
-
template_args = {
|
|
125
|
-
"python_image_repo": ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
126
|
-
"python_version": build_configuration.python_version,
|
|
127
|
-
"apt_install_command": apt_install_command,
|
|
419
|
+
return {
|
|
128
420
|
"requirements_path": requirements_path,
|
|
129
421
|
"requirements_destination_path": requirements_destination_path,
|
|
130
422
|
"python_packages_install_command": python_packages_install_command,
|
|
131
423
|
}
|
|
132
|
-
|
|
133
|
-
if mount_python_package_manager_conf_secret:
|
|
134
|
-
if package_manager == PythonPackageManager.PIP.value:
|
|
135
|
-
template_args["package_manager_config_secret_mount"] = (
|
|
136
|
-
PIP_CONF_BUILDKIT_SECRET_MOUNT
|
|
137
|
-
)
|
|
138
|
-
elif package_manager == PythonPackageManager.UV.value:
|
|
139
|
-
template_args["package_manager_config_secret_mount"] = (
|
|
140
|
-
UV_CONF_BUILDKIT_SECRET_MOUNT
|
|
141
|
-
)
|
|
142
|
-
else:
|
|
143
|
-
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
144
|
-
else:
|
|
145
|
-
template_args["package_manager_config_secret_mount"] = ""
|
|
146
|
-
|
|
147
|
-
if build_configuration.cuda_version:
|
|
148
|
-
template = CUDA_DOCKERFILE_TEMPLATE
|
|
149
|
-
template_args["nvidia_cuda_image_tag"] = CUDA_VERSION_TO_IMAGE_TAG.get(
|
|
150
|
-
build_configuration.cuda_version, build_configuration.cuda_version
|
|
151
|
-
)
|
|
152
|
-
else:
|
|
153
|
-
template = DOCKERFILE_TEMPLATE
|
|
154
|
-
|
|
155
|
-
dockerfile_content = template.render(**template_args)
|
|
156
|
-
return dockerfile_content
|
|
@@ -17,12 +17,12 @@ __all__ = ["generate_dockerfile_content", "build"]
|
|
|
17
17
|
def _convert_to_dockerfile_build_config(
|
|
18
18
|
build_configuration: SparkBuild,
|
|
19
19
|
dockerfile_path: str,
|
|
20
|
-
|
|
20
|
+
extra_opts: Optional[List[str]] = None,
|
|
21
21
|
) -> DockerFileBuild:
|
|
22
22
|
dockerfile_content = generate_dockerfile_content(
|
|
23
23
|
build_configuration=build_configuration,
|
|
24
|
-
mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
|
|
25
24
|
package_manager=PythonPackageManager.PIP.value,
|
|
25
|
+
docker_build_extra_args=extra_opts,
|
|
26
26
|
)
|
|
27
27
|
with open(dockerfile_path, "w", encoding="utf8") as fp:
|
|
28
28
|
fp.write(dockerfile_content)
|
|
@@ -39,10 +39,6 @@ def build(
|
|
|
39
39
|
build_configuration: SparkBuild,
|
|
40
40
|
extra_opts: Optional[List[str]] = None,
|
|
41
41
|
):
|
|
42
|
-
mount_python_package_manager_conf_secret = (
|
|
43
|
-
has_python_package_manager_conf_secret(extra_opts) if extra_opts else False
|
|
44
|
-
)
|
|
45
|
-
|
|
46
42
|
# Copy tfy_execute_notebook.py to the build context
|
|
47
43
|
execute_notebook_src = os.path.join(
|
|
48
44
|
os.path.dirname(__file__), "tfy_execute_notebook.py"
|
|
@@ -63,7 +59,7 @@ def build(
|
|
|
63
59
|
docker_build_configuration = _convert_to_dockerfile_build_config(
|
|
64
60
|
build_configuration,
|
|
65
61
|
dockerfile_path=os.path.join(local_dir, "Dockerfile"),
|
|
66
|
-
|
|
62
|
+
extra_opts=extra_opts,
|
|
67
63
|
)
|
|
68
64
|
dockerfile.build(
|
|
69
65
|
tag=tag,
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
1
3
|
from mako.template import Template
|
|
2
4
|
|
|
3
5
|
from truefoundry.common.constants import ENV_VARS, PythonPackageManager
|
|
4
6
|
from truefoundry.deploy._autogen.models import SparkBuild
|
|
5
|
-
from truefoundry.deploy.builder.constants import (
|
|
6
|
-
PIP_CONF_BUILDKIT_SECRET_MOUNT,
|
|
7
|
-
UV_CONF_BUILDKIT_SECRET_MOUNT,
|
|
8
|
-
)
|
|
9
7
|
from truefoundry.deploy.builder.utils import (
|
|
10
8
|
generate_pip_install_command,
|
|
9
|
+
generate_secret_mounts,
|
|
11
10
|
generate_uv_pip_install_command,
|
|
12
|
-
|
|
13
|
-
from truefoundry.deploy.v2.lib.patched_models import (
|
|
14
|
-
_resolve_requirements_path,
|
|
11
|
+
get_available_secrets,
|
|
15
12
|
)
|
|
16
13
|
|
|
17
14
|
# TODO (chiragjn): Switch to a non-root user inside the container
|
|
@@ -71,13 +68,15 @@ ADDITIONAL_PIP_PACKAGES = [
|
|
|
71
68
|
def generate_dockerfile_content(
|
|
72
69
|
build_configuration: SparkBuild,
|
|
73
70
|
package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
|
|
74
|
-
|
|
71
|
+
docker_build_extra_args: Optional[List[str]] = None,
|
|
75
72
|
) -> str:
|
|
73
|
+
# Get available secrets from docker build extra args
|
|
74
|
+
available_secrets = set()
|
|
75
|
+
if docker_build_extra_args:
|
|
76
|
+
available_secrets = get_available_secrets(docker_build_extra_args)
|
|
77
|
+
|
|
76
78
|
# TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
|
|
77
|
-
requirements_path =
|
|
78
|
-
build_context_path=build_configuration.build_context_path,
|
|
79
|
-
requirements_path=build_configuration.requirements_path,
|
|
80
|
-
)
|
|
79
|
+
requirements_path = build_configuration.requirements_path
|
|
81
80
|
requirements_destination_path = (
|
|
82
81
|
"/tmp/requirements.txt" if requirements_path else None
|
|
83
82
|
)
|
|
@@ -90,13 +89,13 @@ def generate_dockerfile_content(
|
|
|
90
89
|
python_packages_install_command = generate_pip_install_command(
|
|
91
90
|
requirements_path=requirements_destination_path,
|
|
92
91
|
pip_packages=ADDITIONAL_PIP_PACKAGES,
|
|
93
|
-
|
|
92
|
+
available_secrets=available_secrets,
|
|
94
93
|
)
|
|
95
94
|
elif package_manager == PythonPackageManager.UV.value:
|
|
96
95
|
python_packages_install_command = generate_uv_pip_install_command(
|
|
97
96
|
requirements_path=requirements_destination_path,
|
|
98
97
|
pip_packages=ADDITIONAL_PIP_PACKAGES,
|
|
99
|
-
|
|
98
|
+
available_secrets=available_secrets,
|
|
100
99
|
)
|
|
101
100
|
else:
|
|
102
101
|
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
@@ -109,17 +108,12 @@ def generate_dockerfile_content(
|
|
|
109
108
|
"python_packages_install_command": python_packages_install_command,
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
if
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
template_args["package_manager_config_secret_mount"] = (
|
|
119
|
-
UV_CONF_BUILDKIT_SECRET_MOUNT
|
|
120
|
-
)
|
|
121
|
-
else:
|
|
122
|
-
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
111
|
+
if available_secrets:
|
|
112
|
+
template_args["package_manager_config_secret_mount"] = generate_secret_mounts(
|
|
113
|
+
available_secrets=available_secrets,
|
|
114
|
+
python_dependencies_type="pip",
|
|
115
|
+
package_manager=package_manager,
|
|
116
|
+
)
|
|
123
117
|
else:
|
|
124
118
|
template_args["package_manager_config_secret_mount"] = ""
|
|
125
119
|
|
|
@@ -15,11 +15,11 @@ __all__ = ["generate_dockerfile_content", "build"]
|
|
|
15
15
|
def _convert_to_dockerfile_build_config(
|
|
16
16
|
build_configuration: TaskPySparkBuild,
|
|
17
17
|
dockerfile_path: str,
|
|
18
|
-
|
|
18
|
+
extra_opts: Optional[List[str]] = None,
|
|
19
19
|
) -> DockerFileBuild:
|
|
20
20
|
dockerfile_content = generate_dockerfile_content(
|
|
21
21
|
build_configuration=build_configuration,
|
|
22
|
-
|
|
22
|
+
docker_build_extra_args=extra_opts,
|
|
23
23
|
)
|
|
24
24
|
with open(dockerfile_path, "w", encoding="utf8") as fp:
|
|
25
25
|
fp.write(dockerfile_content)
|
|
@@ -35,15 +35,11 @@ def build(
|
|
|
35
35
|
build_configuration: TaskPySparkBuild,
|
|
36
36
|
extra_opts: Optional[List[str]] = None,
|
|
37
37
|
):
|
|
38
|
-
mount_python_package_manager_conf_secret = (
|
|
39
|
-
has_python_package_manager_conf_secret(extra_opts) if extra_opts else False
|
|
40
|
-
)
|
|
41
|
-
|
|
42
38
|
with TemporaryDirectory() as local_dir:
|
|
43
39
|
docker_build_configuration = _convert_to_dockerfile_build_config(
|
|
44
40
|
build_configuration,
|
|
45
41
|
dockerfile_path=os.path.join(local_dir, "Dockerfile"),
|
|
46
|
-
|
|
42
|
+
extra_opts=extra_opts,
|
|
47
43
|
)
|
|
48
44
|
dockerfile.build(
|
|
49
45
|
tag=tag,
|