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.

@@ -1,67 +1,150 @@
1
- from typing import Dict
1
+ from copy import deepcopy
2
+ from typing import Dict, List, Optional, Set, Union
2
3
 
3
- from mako.template import Template
4
+ from jinja2 import Template
4
5
 
5
6
  from truefoundry.common.constants import ENV_VARS, PythonPackageManager
6
- from truefoundry.deploy._autogen.models import PythonBuild
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
- # TODO (chiragjn): Switch to a non-root user inside the container
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
- _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
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
- DOCKERFILE_TEMPLATE = Template(
40
- """
41
- FROM ${python_image_repo}:${python_version}
42
- ENV PATH=/virtualenvs/venv/bin:$PATH
43
- RUN apt update && \
44
- DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git && \
45
- python -m venv /virtualenvs/venv/ && \
46
- rm -rf /var/lib/apt/lists/*
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
- CUDA_DOCKERFILE_TEMPLATE = Template(
52
- """
53
- FROM nvidia/cuda:${nvidia_cuda_image_tag}
54
- ENV PATH=/virtualenvs/venv/bin:$PATH
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/*
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
- + _POST_PYTHON_INSTALL_TEMPLATE
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
- mount_python_package_manager_conf_secret: bool = False,
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=build_configuration.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
- if not build_configuration.python_version:
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=build_configuration.pip_packages,
110
- mount_pip_conf_secret=mount_python_package_manager_conf_secret,
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=build_configuration.pip_packages,
116
- mount_uv_conf_secret=mount_python_package_manager_conf_secret,
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
- apt_install_command = generate_apt_install_command(
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
- mount_python_package_manager_conf_secret: bool = False,
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
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
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
- mount_python_package_manager_conf_secret: bool = False,
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 = _resolve_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
- mount_pip_conf_secret=mount_python_package_manager_conf_secret,
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
- mount_uv_conf_secret=mount_python_package_manager_conf_secret,
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 mount_python_package_manager_conf_secret:
113
- if package_manager == PythonPackageManager.PIP.value:
114
- template_args["package_manager_config_secret_mount"] = (
115
- PIP_CONF_BUILDKIT_SECRET_MOUNT
116
- )
117
- elif package_manager == PythonPackageManager.UV.value:
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
- mount_python_package_manager_conf_secret: bool = False,
18
+ extra_opts: Optional[List[str]] = None,
19
19
  ) -> DockerFileBuild:
20
20
  dockerfile_content = generate_dockerfile_content(
21
21
  build_configuration=build_configuration,
22
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
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
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
42
+ extra_opts=extra_opts,
47
43
  )
48
44
  dockerfile.build(
49
45
  tag=tag,