truefoundry 0.11.12__py3-none-any.whl → 0.12.0rc1__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/deploy/__init__.py +3 -0
- truefoundry/deploy/_autogen/models.py +192 -145
- truefoundry/deploy/builder/__init__.py +1 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +3 -6
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +347 -84
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/dockerfile_template.py +18 -18
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/dockerfile_template.py +18 -18
- truefoundry/deploy/builder/constants.py +12 -0
- truefoundry/deploy/builder/utils.py +210 -21
- truefoundry/deploy/lib/util.py +35 -0
- truefoundry/deploy/v2/lib/deploy.py +3 -1
- truefoundry/deploy/v2/lib/patched_models.py +76 -3
- truefoundry/deploy/v2/lib/source.py +4 -0
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc1.dist-info}/METADATA +2 -1
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc1.dist-info}/RECORD +20 -20
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc1.dist-info}/WHEEL +0 -0
- {truefoundry-0.11.12.dist-info → truefoundry-0.12.0rc1.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 (
|
|
12
9
|
generate_apt_install_command,
|
|
10
|
+
generate_command_to_install_from_uv_lock,
|
|
13
11
|
generate_pip_install_command,
|
|
12
|
+
generate_poetry_install_command,
|
|
13
|
+
generate_secret_mounts,
|
|
14
14
|
generate_uv_pip_install_command,
|
|
15
|
+
get_available_secrets,
|
|
15
16
|
)
|
|
16
17
|
from truefoundry.deploy.v2.lib.patched_models import (
|
|
17
18
|
CUDAVersion,
|
|
19
|
+
PythonBuild,
|
|
18
20
|
_resolve_requirements_path,
|
|
21
|
+
check_whether_poetry_toml_exists,
|
|
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
|
+
|
|
48
|
+
|
|
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
|
+
"""
|
|
20
77
|
|
|
21
|
-
# TODO (chiragjn): Switch to a non-root user inside the container
|
|
22
78
|
|
|
23
|
-
|
|
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 %}
|
|
24
94
|
|
|
25
|
-
_POST_PYTHON_INSTALL_TEMPLATE = """
|
|
26
|
-
% if apt_install_command is not None:
|
|
27
|
-
RUN ${apt_install_command}
|
|
28
|
-
% endif
|
|
29
|
-
% if requirements_path is not None:
|
|
30
|
-
COPY ${requirements_path} ${requirements_destination_path}
|
|
31
|
-
% endif
|
|
32
|
-
% if python_packages_install_command is not None:
|
|
33
|
-
RUN ${package_manager_config_secret_mount} ${python_packages_install_command}
|
|
34
|
-
% endif
|
|
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
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
DOCKERFILE_TEMPLATE = """\
|
|
143
|
+
{{ base_image_setup }}
|
|
144
|
+
{{ apt_packages_section }}
|
|
145
|
+
{{ python_dependencies_section }}
|
|
62
146
|
"""
|
|
63
|
-
|
|
64
|
-
)
|
|
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,248 @@ 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
|
+
# Set up package manager config secret mount
|
|
228
|
+
python_dependencies_type = python_dependencies.type
|
|
229
|
+
package_manager_config_secret_mount = ""
|
|
230
|
+
if available_secrets:
|
|
231
|
+
package_manager_config_secret_mount = generate_secret_mounts(
|
|
232
|
+
available_secrets=available_secrets,
|
|
233
|
+
python_dependencies_type=python_dependencies_type,
|
|
234
|
+
package_manager=package_manager,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Configure dependencies based on type
|
|
238
|
+
if python_dependencies_type == "uv":
|
|
239
|
+
uv_context = _build_uv_context(
|
|
240
|
+
python_dependencies,
|
|
241
|
+
available_secrets,
|
|
242
|
+
)
|
|
243
|
+
return TemplateContext(
|
|
244
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
245
|
+
python_version=build_configuration.python_version,
|
|
246
|
+
apt_install_command=generate_apt_install_command(
|
|
247
|
+
apt_packages=build_configuration.apt_packages
|
|
248
|
+
),
|
|
249
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
250
|
+
python_packages_install_command=uv_context[
|
|
251
|
+
"python_packages_install_command"
|
|
252
|
+
],
|
|
253
|
+
final_uv_sync_command=uv_context["final_uv_sync_command"],
|
|
254
|
+
)
|
|
255
|
+
elif python_dependencies_type == "poetry":
|
|
256
|
+
poetry_toml_exists = check_whether_poetry_toml_exists(
|
|
257
|
+
build_context_path=build_configuration.build_context_path,
|
|
258
|
+
)
|
|
259
|
+
poetry_context = _build_poetry_context(python_dependencies, available_secrets)
|
|
260
|
+
return TemplateContext(
|
|
261
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
262
|
+
python_version=build_configuration.python_version,
|
|
263
|
+
apt_install_command=generate_apt_install_command(
|
|
264
|
+
apt_packages=build_configuration.apt_packages
|
|
265
|
+
),
|
|
266
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
267
|
+
python_packages_install_command=poetry_context[
|
|
268
|
+
"python_packages_install_command"
|
|
269
|
+
],
|
|
270
|
+
final_poetry_install_command=poetry_context["final_poetry_install_command"],
|
|
271
|
+
poetry_version_expression=poetry_context["poetry_version_expression"],
|
|
272
|
+
poetry_toml_exists=poetry_toml_exists,
|
|
273
|
+
)
|
|
274
|
+
elif python_dependencies_type == "pip":
|
|
275
|
+
pip_context = _build_pip_context(
|
|
276
|
+
build_configuration,
|
|
277
|
+
python_dependencies,
|
|
278
|
+
package_manager,
|
|
279
|
+
available_secrets,
|
|
280
|
+
)
|
|
281
|
+
return TemplateContext(
|
|
282
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
283
|
+
python_version=build_configuration.python_version,
|
|
284
|
+
apt_install_command=generate_apt_install_command(
|
|
285
|
+
apt_packages=build_configuration.apt_packages
|
|
286
|
+
),
|
|
287
|
+
package_manager_config_secret_mount=package_manager_config_secret_mount,
|
|
288
|
+
requirements_path=pip_context["requirements_path"],
|
|
289
|
+
requirements_destination_path=pip_context["requirements_destination_path"],
|
|
290
|
+
python_packages_install_command=pip_context[
|
|
291
|
+
"python_packages_install_command"
|
|
292
|
+
],
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _build_base_image_section(build_configuration: PythonBuild) -> str:
|
|
299
|
+
if build_configuration.cuda_version:
|
|
300
|
+
cuda_image_tag = CUDA_VERSION_TO_IMAGE_TAG.get(
|
|
301
|
+
build_configuration.cuda_version, build_configuration.cuda_version
|
|
302
|
+
)
|
|
303
|
+
template = Template(CUDA_BASE_IMAGE_TEMPLATE)
|
|
304
|
+
return template.render(
|
|
305
|
+
cuda_image_tag=cuda_image_tag,
|
|
306
|
+
python_version=build_configuration.python_version,
|
|
307
|
+
)
|
|
308
|
+
else:
|
|
309
|
+
template = Template(STANDARD_BASE_IMAGE_TEMPLATE)
|
|
310
|
+
return template.render(
|
|
311
|
+
python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
|
|
312
|
+
python_version=build_configuration.python_version,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _build_apt_packages_section(apt_install_command: Optional[str]) -> str:
|
|
317
|
+
template = Template(APT_PACKAGES_TEMPLATE)
|
|
318
|
+
return template.render(apt_install_command=apt_install_command)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _build_python_dependencies_section(
|
|
322
|
+
python_dependencies_type: str, context: TemplateContext
|
|
323
|
+
) -> str:
|
|
324
|
+
if python_dependencies_type == "pip":
|
|
325
|
+
template = Template(PIP_DEPENDENCIES_TEMPLATE)
|
|
326
|
+
return template.render(**context.dict())
|
|
327
|
+
elif python_dependencies_type == "uv":
|
|
328
|
+
template = Template(UV_DEPENDENCIES_TEMPLATE)
|
|
329
|
+
return template.render(**context.dict())
|
|
330
|
+
elif python_dependencies_type == "poetry":
|
|
331
|
+
template = Template(POETRY_DEPENDENCIES_TEMPLATE)
|
|
332
|
+
return template.render(**context.dict())
|
|
333
|
+
else:
|
|
334
|
+
raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _build_uv_context(python_dependencies: UV, available_secrets: Set[str]) -> Dict:
|
|
338
|
+
python_packages_install_command = generate_command_to_install_from_uv_lock(
|
|
339
|
+
sync_options=python_dependencies.sync_options,
|
|
340
|
+
uv_version=python_dependencies.uv_version,
|
|
341
|
+
available_secrets=available_secrets,
|
|
342
|
+
)
|
|
343
|
+
final_uv_sync_command = generate_command_to_install_from_uv_lock(
|
|
344
|
+
sync_options=python_dependencies.sync_options,
|
|
345
|
+
uv_version=python_dependencies.uv_version,
|
|
346
|
+
install_project=True,
|
|
347
|
+
available_secrets=available_secrets,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
"python_packages_install_command": python_packages_install_command,
|
|
352
|
+
"final_uv_sync_command": final_uv_sync_command,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _build_poetry_context(
|
|
357
|
+
python_dependencies: Poetry, available_secrets: Set[str]
|
|
358
|
+
) -> Dict:
|
|
359
|
+
python_packages_install_command = generate_poetry_install_command(
|
|
360
|
+
install_options=python_dependencies.install_options,
|
|
361
|
+
available_secrets=available_secrets,
|
|
362
|
+
)
|
|
363
|
+
final_poetry_install_command = generate_poetry_install_command(
|
|
364
|
+
install_options=python_dependencies.install_options,
|
|
365
|
+
install_project=True,
|
|
366
|
+
available_secrets=available_secrets,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
poetry_version = python_dependencies.poetry_version
|
|
370
|
+
# Handle "latest" poetry version by using major version constraint
|
|
371
|
+
if poetry_version == "latest":
|
|
372
|
+
major_version = ENV_VARS.TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION
|
|
373
|
+
poetry_version_expression = f">={major_version},<{major_version + 1}"
|
|
374
|
+
else:
|
|
375
|
+
# For regular versions, use ~= operator for compatibility
|
|
376
|
+
poetry_version_expression = f"~={poetry_version}"
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"python_packages_install_command": python_packages_install_command,
|
|
380
|
+
"final_poetry_install_command": final_poetry_install_command,
|
|
381
|
+
"poetry_version_expression": poetry_version_expression,
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _build_pip_context(
|
|
386
|
+
build_configuration: PythonBuild,
|
|
387
|
+
python_dependencies: Pip,
|
|
388
|
+
package_manager: str,
|
|
389
|
+
available_secrets: Set[str],
|
|
390
|
+
) -> Dict:
|
|
94
391
|
# TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
|
|
95
392
|
requirements_path = _resolve_requirements_path(
|
|
96
393
|
build_context_path=build_configuration.build_context_path,
|
|
97
|
-
requirements_path=
|
|
394
|
+
requirements_path=python_dependencies.requirements_path,
|
|
98
395
|
)
|
|
99
396
|
requirements_destination_path = (
|
|
100
397
|
"/tmp/requirements.txt" if requirements_path else None
|
|
101
398
|
)
|
|
102
|
-
|
|
103
|
-
raise ValueError(
|
|
104
|
-
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
105
|
-
)
|
|
399
|
+
|
|
106
400
|
if package_manager == PythonPackageManager.PIP.value:
|
|
107
401
|
python_packages_install_command = generate_pip_install_command(
|
|
108
402
|
requirements_path=requirements_destination_path,
|
|
109
|
-
pip_packages=
|
|
110
|
-
|
|
403
|
+
pip_packages=python_dependencies.pip_packages,
|
|
404
|
+
available_secrets=available_secrets,
|
|
111
405
|
)
|
|
112
406
|
elif package_manager == PythonPackageManager.UV.value:
|
|
113
407
|
python_packages_install_command = generate_uv_pip_install_command(
|
|
114
408
|
requirements_path=requirements_destination_path,
|
|
115
|
-
pip_packages=
|
|
116
|
-
|
|
409
|
+
pip_packages=python_dependencies.pip_packages,
|
|
410
|
+
available_secrets=available_secrets,
|
|
117
411
|
)
|
|
118
412
|
else:
|
|
119
413
|
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
120
414
|
|
|
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,
|
|
415
|
+
return {
|
|
128
416
|
"requirements_path": requirements_path,
|
|
129
417
|
"requirements_destination_path": requirements_destination_path,
|
|
130
418
|
"python_packages_install_command": python_packages_install_command,
|
|
131
419
|
}
|
|
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,14 +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,
|
|
11
|
+
get_available_secrets,
|
|
12
12
|
)
|
|
13
13
|
from truefoundry.deploy.v2.lib.patched_models import (
|
|
14
14
|
_resolve_requirements_path,
|
|
@@ -71,8 +71,13 @@ ADDITIONAL_PIP_PACKAGES = [
|
|
|
71
71
|
def generate_dockerfile_content(
|
|
72
72
|
build_configuration: SparkBuild,
|
|
73
73
|
package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
|
|
74
|
-
|
|
74
|
+
docker_build_extra_args: Optional[List[str]] = None,
|
|
75
75
|
) -> str:
|
|
76
|
+
# Get available secrets from docker build extra args
|
|
77
|
+
available_secrets = set()
|
|
78
|
+
if docker_build_extra_args:
|
|
79
|
+
available_secrets = get_available_secrets(docker_build_extra_args)
|
|
80
|
+
|
|
76
81
|
# TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
|
|
77
82
|
requirements_path = _resolve_requirements_path(
|
|
78
83
|
build_context_path=build_configuration.build_context_path,
|
|
@@ -90,13 +95,13 @@ def generate_dockerfile_content(
|
|
|
90
95
|
python_packages_install_command = generate_pip_install_command(
|
|
91
96
|
requirements_path=requirements_destination_path,
|
|
92
97
|
pip_packages=ADDITIONAL_PIP_PACKAGES,
|
|
93
|
-
|
|
98
|
+
available_secrets=available_secrets,
|
|
94
99
|
)
|
|
95
100
|
elif package_manager == PythonPackageManager.UV.value:
|
|
96
101
|
python_packages_install_command = generate_uv_pip_install_command(
|
|
97
102
|
requirements_path=requirements_destination_path,
|
|
98
103
|
pip_packages=ADDITIONAL_PIP_PACKAGES,
|
|
99
|
-
|
|
104
|
+
available_secrets=available_secrets,
|
|
100
105
|
)
|
|
101
106
|
else:
|
|
102
107
|
raise ValueError(f"Unsupported package manager: {package_manager}")
|
|
@@ -109,17 +114,12 @@ def generate_dockerfile_content(
|
|
|
109
114
|
"python_packages_install_command": python_packages_install_command,
|
|
110
115
|
}
|
|
111
116
|
|
|
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}")
|
|
117
|
+
if available_secrets:
|
|
118
|
+
template_args["package_manager_config_secret_mount"] = generate_secret_mounts(
|
|
119
|
+
available_secrets=available_secrets,
|
|
120
|
+
python_dependencies_type="pip",
|
|
121
|
+
package_manager=package_manager,
|
|
122
|
+
)
|
|
123
123
|
else:
|
|
124
124
|
template_args["package_manager_config_secret_mount"] = ""
|
|
125
125
|
|
|
@@ -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,
|