truefoundry 0.2.10__py3-none-any.whl → 0.3.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.

Files changed (100) hide show
  1. truefoundry/__init__.py +1 -0
  2. truefoundry/autodeploy/cli.py +31 -18
  3. truefoundry/deploy/__init__.py +119 -1
  4. truefoundry/deploy/auto_gen/models.py +1791 -0
  5. truefoundry/deploy/builder/__init__.py +138 -0
  6. truefoundry/deploy/builder/builders/__init__.py +22 -0
  7. truefoundry/deploy/builder/builders/dockerfile.py +57 -0
  8. truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py +44 -0
  9. truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +51 -0
  10. truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +44 -0
  11. truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +158 -0
  12. truefoundry/deploy/builder/docker_service.py +168 -0
  13. truefoundry/deploy/cli/cli.py +19 -26
  14. truefoundry/deploy/cli/commands/__init__.py +18 -0
  15. truefoundry/deploy/cli/commands/apply_command.py +52 -0
  16. truefoundry/deploy/cli/commands/build_command.py +45 -0
  17. truefoundry/deploy/cli/commands/build_logs_command.py +89 -0
  18. truefoundry/deploy/cli/commands/create_command.py +75 -0
  19. truefoundry/deploy/cli/commands/delete_command.py +77 -0
  20. truefoundry/deploy/cli/commands/deploy_command.py +99 -0
  21. truefoundry/deploy/cli/commands/get_command.py +216 -0
  22. truefoundry/deploy/cli/commands/list_command.py +171 -0
  23. truefoundry/deploy/cli/commands/login_command.py +33 -0
  24. truefoundry/deploy/cli/commands/logout_command.py +20 -0
  25. truefoundry/deploy/cli/commands/logs_command.py +134 -0
  26. truefoundry/deploy/cli/commands/patch_application_command.py +79 -0
  27. truefoundry/deploy/cli/commands/patch_command.py +70 -0
  28. truefoundry/deploy/cli/commands/redeploy_command.py +41 -0
  29. truefoundry/deploy/cli/commands/terminate_comand.py +44 -0
  30. truefoundry/deploy/cli/commands/trigger_command.py +87 -0
  31. truefoundry/deploy/cli/config.py +10 -0
  32. truefoundry/deploy/cli/console.py +5 -0
  33. truefoundry/deploy/cli/const.py +12 -0
  34. truefoundry/deploy/cli/display_util.py +118 -0
  35. truefoundry/deploy/cli/util.py +92 -0
  36. truefoundry/deploy/core/__init__.py +7 -0
  37. truefoundry/deploy/core/login.py +9 -0
  38. truefoundry/deploy/core/logout.py +5 -0
  39. truefoundry/deploy/function_service/__init__.py +3 -0
  40. truefoundry/deploy/function_service/__main__.py +27 -0
  41. truefoundry/deploy/function_service/app.py +92 -0
  42. truefoundry/deploy/function_service/build.py +45 -0
  43. truefoundry/deploy/function_service/remote/__init__.py +6 -0
  44. truefoundry/deploy/function_service/remote/context.py +3 -0
  45. truefoundry/deploy/function_service/remote/method.py +67 -0
  46. truefoundry/deploy/function_service/remote/remote.py +144 -0
  47. truefoundry/deploy/function_service/route.py +137 -0
  48. truefoundry/deploy/function_service/service.py +113 -0
  49. truefoundry/deploy/function_service/utils.py +53 -0
  50. truefoundry/deploy/io/__init__.py +0 -0
  51. truefoundry/deploy/io/output_callback.py +23 -0
  52. truefoundry/deploy/io/rich_output_callback.py +27 -0
  53. truefoundry/deploy/json_util.py +7 -0
  54. truefoundry/deploy/lib/__init__.py +0 -0
  55. truefoundry/deploy/lib/auth/auth_service_client.py +81 -0
  56. truefoundry/deploy/lib/auth/credential_file_manager.py +115 -0
  57. truefoundry/deploy/lib/auth/credential_provider.py +131 -0
  58. truefoundry/deploy/lib/auth/servicefoundry_session.py +59 -0
  59. truefoundry/deploy/lib/clients/__init__.py +0 -0
  60. truefoundry/deploy/lib/clients/servicefoundry_client.py +723 -0
  61. truefoundry/deploy/lib/clients/shell_client.py +13 -0
  62. truefoundry/deploy/lib/clients/utils.py +41 -0
  63. truefoundry/deploy/lib/const.py +43 -0
  64. truefoundry/deploy/lib/dao/__init__.py +0 -0
  65. truefoundry/deploy/lib/dao/application.py +246 -0
  66. truefoundry/deploy/lib/dao/apply.py +80 -0
  67. truefoundry/deploy/lib/dao/version.py +33 -0
  68. truefoundry/deploy/lib/dao/workspace.py +71 -0
  69. truefoundry/deploy/lib/exceptions.py +23 -0
  70. truefoundry/deploy/lib/logs_utils.py +43 -0
  71. truefoundry/deploy/lib/messages.py +12 -0
  72. truefoundry/deploy/lib/model/__init__.py +0 -0
  73. truefoundry/deploy/lib/model/entity.py +382 -0
  74. truefoundry/deploy/lib/session.py +146 -0
  75. truefoundry/deploy/lib/util.py +70 -0
  76. truefoundry/deploy/lib/win32.py +129 -0
  77. truefoundry/deploy/v2/__init__.py +0 -0
  78. truefoundry/deploy/v2/lib/__init__.py +3 -0
  79. truefoundry/deploy/v2/lib/deploy.py +232 -0
  80. truefoundry/deploy/v2/lib/deployable_patched_models.py +72 -0
  81. truefoundry/deploy/v2/lib/models.py +53 -0
  82. truefoundry/deploy/v2/lib/patched_models.py +515 -0
  83. truefoundry/deploy/v2/lib/source.py +267 -0
  84. truefoundry/flyte/__init__.py +6 -0
  85. truefoundry/langchain/__init__.py +12 -1
  86. truefoundry/langchain/deprecated.py +302 -0
  87. truefoundry/langchain/truefoundry_chat.py +130 -0
  88. truefoundry/langchain/truefoundry_embeddings.py +171 -0
  89. truefoundry/langchain/truefoundry_llm.py +106 -0
  90. truefoundry/langchain/utils.py +85 -0
  91. truefoundry/logger.py +17 -0
  92. truefoundry/pydantic_v1.py +5 -0
  93. truefoundry/python_deploy_codegen.py +132 -0
  94. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/METADATA +25 -6
  95. truefoundry-0.3.0rc2.dist-info/RECORD +125 -0
  96. truefoundry/deploy/cli/deploy.py +0 -165
  97. truefoundry-0.2.10.dist-info/RECORD +0 -38
  98. /truefoundry/{deploy/cli/version.py → version.py} +0 -0
  99. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/WHEEL +0 -0
  100. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,138 @@
1
+ from typing import Dict, List, Optional, Union
2
+
3
+ from truefoundry.deploy.auto_gen.models import (
4
+ DockerFileBuild,
5
+ PythonBuild,
6
+ TaskDockerFileBuild,
7
+ TaskPythonBuild,
8
+ )
9
+ from truefoundry.deploy.builder.builders import get_builder
10
+ from truefoundry.deploy.builder.builders.tfy_notebook_buildpack.dockerfile_template import (
11
+ NotebookImageBuild,
12
+ )
13
+ from truefoundry.pydantic_v1 import BaseModel
14
+
15
+
16
+ class _BuildConfig(BaseModel):
17
+ # I cannot use Field(discriminator="build_config_type") here as
18
+ # build_config_type in the build configs is not a Literal.
19
+ __root__: Union[
20
+ DockerFileBuild,
21
+ PythonBuild,
22
+ NotebookImageBuild,
23
+ TaskPythonBuild,
24
+ TaskDockerFileBuild,
25
+ ]
26
+
27
+
28
+ def build(
29
+ build_configuration: Union[BaseModel, Dict],
30
+ tag: str,
31
+ extra_opts: Optional[List[str]] = None,
32
+ ):
33
+ build_configuration = _BuildConfig.parse_obj(build_configuration).__root__
34
+ if isinstance(build_configuration, TaskPythonBuild):
35
+ build_configuration_dict = build_configuration.dict()
36
+ build_configuration_dict.update(
37
+ {"type": "tfy-python-buildpack", "command": "python"}
38
+ )
39
+ build_configuration = PythonBuild.parse_obj(build_configuration_dict)
40
+ if isinstance(build_configuration, TaskDockerFileBuild):
41
+ build_configuration_dict = build_configuration.dict()
42
+ build_configuration_dict.update({"type": "dockerfile"})
43
+ build_configuration = DockerFileBuild.parse_obj(build_configuration_dict)
44
+
45
+ builder = get_builder(build_configuration.type)
46
+ return builder(
47
+ build_configuration=build_configuration,
48
+ tag=tag,
49
+ extra_opts=extra_opts,
50
+ )
51
+
52
+
53
+ if __name__ == "__main__":
54
+ # TODO: remove these and write tests for Build class to dockerfile content.
55
+ import os
56
+ from tempfile import TemporaryDirectory
57
+
58
+ with TemporaryDirectory() as local_dir:
59
+ dockerfile_path = os.path.join(local_dir, "Dockerfile.test")
60
+ with open(dockerfile_path, "w", encoding="utf8") as fp:
61
+ fp.write("from postgres:latest")
62
+
63
+ build_config = DockerFileBuild(
64
+ type="dockerfile",
65
+ build_context_path=local_dir,
66
+ dockerfile_path=dockerfile_path,
67
+ )
68
+
69
+ build(tag="docker-test", build_configuration=build_config)
70
+
71
+ with TemporaryDirectory() as local_dir:
72
+ requirements_path = os.path.join(local_dir, "requirements.txt")
73
+ with open(requirements_path, "w", encoding="utf8") as fp:
74
+ fp.write("requests")
75
+
76
+ python_path = os.path.join(local_dir, "main.py")
77
+ with open(python_path, "w", encoding="utf8") as fp:
78
+ fp.write("import requests; print(requests.__version__);")
79
+
80
+ build_config = PythonBuild(
81
+ type="tfy-python-buildpack",
82
+ build_context_path=local_dir,
83
+ command=["python", "main.py"],
84
+ python_version="3.7.13",
85
+ )
86
+ build(tag="python-test-exec", build_configuration=build_config)
87
+ build_config = PythonBuild(
88
+ type="tfy-python-buildpack",
89
+ build_context_path=local_dir,
90
+ command="python main.py",
91
+ python_version="3.8",
92
+ )
93
+ build(tag="python-test-shell", build_configuration=build_config)
94
+
95
+ with TemporaryDirectory() as local_dir:
96
+ requirements_path = os.path.join(local_dir, "requirements.txt")
97
+ with open(requirements_path, "w", encoding="utf8") as fp:
98
+ fp.write("numpy")
99
+
100
+ python_path = os.path.join(local_dir, "main.py")
101
+ with open(python_path, "w", encoding="utf8") as fp:
102
+ fp.write("import numpy; import requests; print(requests.__version__);")
103
+
104
+ build_config = PythonBuild(
105
+ type="tfy-python-buildpack",
106
+ build_context_path=local_dir,
107
+ command=["python", "main.py"],
108
+ python_version="3.7.13",
109
+ pip_packages=["requests"],
110
+ )
111
+ build(tag="python-pip-and-req-file", build_configuration=build_config)
112
+
113
+ with TemporaryDirectory() as local_dir:
114
+ python_path = os.path.join(local_dir, "main.py")
115
+ with open(python_path, "w", encoding="utf8") as fp:
116
+ fp.write("import requests; print(requests.__version__);")
117
+
118
+ build_config = PythonBuild(
119
+ type="tfy-python-buildpack",
120
+ build_context_path=local_dir,
121
+ command=["python", "main.py"],
122
+ python_version="3.7.13",
123
+ pip_packages=["requests>0.1"],
124
+ )
125
+ build(tag="python-only-pip", build_configuration=build_config)
126
+
127
+ with TemporaryDirectory() as local_dir:
128
+ python_path = os.path.join(local_dir, "main.py")
129
+ with open(python_path, "w", encoding="utf8") as fp:
130
+ fp.write("print(1)")
131
+
132
+ build_config = PythonBuild(
133
+ type="tfy-python-buildpack",
134
+ build_context_path=local_dir,
135
+ command=["python", "main.py"],
136
+ python_version="3.7.13",
137
+ )
138
+ build(tag="python-only-no-pip-no-req", build_configuration=build_config)
@@ -0,0 +1,22 @@
1
+ from typing import Callable, Dict
2
+
3
+ from truefoundry.deploy.builder.builders import (
4
+ dockerfile,
5
+ tfy_notebook_buildpack,
6
+ tfy_python_buildpack,
7
+ )
8
+
9
+ BUILD_REGISTRY: Dict[str, Callable] = {
10
+ "dockerfile": dockerfile.build,
11
+ "tfy-python-buildpack": tfy_python_buildpack.build,
12
+ "tfy-notebook-buildpack": tfy_notebook_buildpack.build,
13
+ }
14
+
15
+ __all__ = ["get_builder"]
16
+
17
+
18
+ def get_builder(build_configuration_type: str) -> Callable:
19
+ if build_configuration_type not in BUILD_REGISTRY:
20
+ raise NotImplementedError(f"Builder for {build_configuration_type} not found")
21
+
22
+ return BUILD_REGISTRY[build_configuration_type]
@@ -0,0 +1,57 @@
1
+ import os
2
+ from typing import Dict, List, Optional
3
+
4
+ from truefoundry.deploy.auto_gen.models import DockerFileBuild
5
+ from truefoundry.deploy.builder.docker_service import build_docker_image
6
+ from truefoundry.logger import logger
7
+
8
+ __all__ = ["build"]
9
+
10
+
11
+ def _get_expanded_and_absolute_path(path: str):
12
+ return os.path.abspath(os.path.expanduser(path))
13
+
14
+
15
+ def _build_docker_image(
16
+ tag: str,
17
+ path: str = ".",
18
+ file: Optional[str] = None,
19
+ build_args: Optional[Dict[str, str]] = None,
20
+ extra_opts: Optional[List[str]] = None,
21
+ ):
22
+ path = _get_expanded_and_absolute_path(path)
23
+
24
+ if file:
25
+ file = _get_expanded_and_absolute_path(file)
26
+
27
+ build_docker_image(
28
+ path=path,
29
+ tag=tag,
30
+ # TODO: can we pick target platform(s) picked from cluster
31
+ platform="linux/amd64",
32
+ dockerfile=file,
33
+ build_args=build_args,
34
+ extra_opts=extra_opts,
35
+ )
36
+
37
+
38
+ def build(
39
+ tag: str,
40
+ build_configuration: DockerFileBuild,
41
+ extra_opts: Optional[List[str]] = None,
42
+ ):
43
+ dockerfile_path = _get_expanded_and_absolute_path(
44
+ build_configuration.dockerfile_path
45
+ )
46
+ with open(dockerfile_path) as f:
47
+ dockerfile_content = f.read()
48
+ logger.info("Dockerfile content:-")
49
+ logger.info(dockerfile_content)
50
+
51
+ _build_docker_image(
52
+ tag=tag,
53
+ path=build_configuration.build_context_path,
54
+ file=build_configuration.dockerfile_path,
55
+ build_args=build_configuration.build_args,
56
+ extra_opts=extra_opts,
57
+ )
@@ -0,0 +1,44 @@
1
+ import os
2
+ from tempfile import TemporaryDirectory
3
+ from typing import List, Optional
4
+
5
+ from truefoundry.deploy.auto_gen.models import DockerFileBuild
6
+ from truefoundry.deploy.builder.builders import dockerfile
7
+ from truefoundry.deploy.builder.builders.tfy_notebook_buildpack.dockerfile_template import (
8
+ NotebookImageBuild,
9
+ generate_dockerfile_content,
10
+ )
11
+
12
+ __all__ = ["generate_dockerfile_content", "build"]
13
+
14
+
15
+ def _convert_to_dockerfile_build_config(
16
+ build_configuration: NotebookImageBuild,
17
+ dockerfile_path: str,
18
+ ) -> DockerFileBuild:
19
+ dockerfile_content = generate_dockerfile_content(
20
+ build_configuration=build_configuration
21
+ )
22
+ with open(dockerfile_path, "w", encoding="utf8") as fp:
23
+ fp.write(dockerfile_content)
24
+
25
+ return DockerFileBuild(
26
+ type="dockerfile",
27
+ dockerfile_path=dockerfile_path,
28
+ )
29
+
30
+
31
+ def build(
32
+ tag: str,
33
+ build_configuration: NotebookImageBuild,
34
+ extra_opts: Optional[List[str]] = None,
35
+ ):
36
+ with TemporaryDirectory() as local_dir:
37
+ docker_build_configuration = _convert_to_dockerfile_build_config(
38
+ build_configuration, dockerfile_path=os.path.join(local_dir, "Dockerfile")
39
+ )
40
+ dockerfile.build(
41
+ tag=tag,
42
+ build_configuration=docker_build_configuration,
43
+ extra_opts=extra_opts,
44
+ )
@@ -0,0 +1,51 @@
1
+ from typing import List, Optional
2
+
3
+ from mako.template import Template
4
+
5
+ from truefoundry.pydantic_v1 import BaseModel
6
+
7
+
8
+ class NotebookImageBuild(BaseModel):
9
+ type: str
10
+ base_image_uri: str
11
+ apt_packages: List[str]
12
+
13
+
14
+ DOCKERFILE_TEMPLATE = Template(
15
+ """
16
+ FROM ${base_image_uri}
17
+ USER root
18
+ RUN ${apt_install_command}
19
+ USER $NB_UID
20
+ """
21
+ )
22
+
23
+
24
+ def generate_apt_install_command(apt_packages: Optional[List[str]]) -> Optional[str]:
25
+ packages_list = None
26
+ if apt_packages:
27
+ packages_list = " ".join(p.strip() for p in apt_packages if p.strip())
28
+ if not packages_list:
29
+ return None
30
+ apt_update_command = "apt update"
31
+ apt_install_command = f"DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends {packages_list}"
32
+ clear_apt_lists_command = "rm -rf /var/lib/apt/lists/*"
33
+ return " && ".join(
34
+ [apt_update_command, apt_install_command, clear_apt_lists_command]
35
+ )
36
+
37
+
38
+ def generate_dockerfile_content(build_configuration: NotebookImageBuild) -> str:
39
+ apt_install_command = generate_apt_install_command(
40
+ apt_packages=build_configuration.apt_packages
41
+ )
42
+
43
+ template_args = {
44
+ "base_image_uri": build_configuration.base_image_uri,
45
+ "apt_install_command": apt_install_command,
46
+ }
47
+
48
+ template = DOCKERFILE_TEMPLATE
49
+
50
+ dockerfile_content = template.render(**template_args)
51
+ return dockerfile_content
@@ -0,0 +1,44 @@
1
+ import os
2
+ from tempfile import TemporaryDirectory
3
+ from typing import List, Optional
4
+
5
+ from truefoundry.deploy.auto_gen.models import DockerFileBuild, PythonBuild
6
+ from truefoundry.deploy.builder.builders import dockerfile
7
+ from truefoundry.deploy.builder.builders.tfy_python_buildpack.dockerfile_template import (
8
+ generate_dockerfile_content,
9
+ )
10
+
11
+ __all__ = ["generate_dockerfile_content", "build"]
12
+
13
+
14
+ def _convert_to_dockerfile_build_config(
15
+ build_configuration: PythonBuild,
16
+ dockerfile_path: str,
17
+ ) -> DockerFileBuild:
18
+ dockerfile_content = generate_dockerfile_content(
19
+ build_configuration=build_configuration
20
+ )
21
+ with open(dockerfile_path, "w", encoding="utf8") as fp:
22
+ fp.write(dockerfile_content)
23
+
24
+ return DockerFileBuild(
25
+ type="dockerfile",
26
+ dockerfile_path=dockerfile_path,
27
+ build_context_path=build_configuration.build_context_path,
28
+ )
29
+
30
+
31
+ def build(
32
+ tag: str,
33
+ build_configuration: PythonBuild,
34
+ extra_opts: Optional[List[str]] = None,
35
+ ):
36
+ with TemporaryDirectory() as local_dir:
37
+ docker_build_configuration = _convert_to_dockerfile_build_config(
38
+ build_configuration, dockerfile_path=os.path.join(local_dir, "Dockerfile")
39
+ )
40
+ dockerfile.build(
41
+ tag=tag,
42
+ build_configuration=docker_build_configuration,
43
+ extra_opts=extra_opts,
44
+ )
@@ -0,0 +1,158 @@
1
+ import os
2
+ from typing import Dict, List, Optional
3
+
4
+ from mako.template import Template
5
+
6
+ from truefoundry.deploy.auto_gen.models import PythonBuild
7
+ from truefoundry.deploy.v2.lib.patched_models import CUDAVersion
8
+
9
+ # TODO (chiragjn): Switch to a non-root user inside the container
10
+
11
+ _POST_PYTHON_INSTALL_TEMPLATE = """
12
+ % if apt_install_command is not None:
13
+ RUN ${apt_install_command}
14
+ % endif
15
+
16
+ % if requirements_path is not None:
17
+ COPY ${requirements_path} ${requirements_destination_path}
18
+ % endif
19
+
20
+ % if pip_install_command is not None:
21
+ RUN ${pip_install_command}
22
+ % endif
23
+
24
+ COPY . /app
25
+ WORKDIR /app
26
+ """
27
+
28
+ DOCKERFILE_TEMPLATE = Template(
29
+ """
30
+ FROM --platform=linux/amd64 python:${python_version}
31
+ ENV PATH=/virtualenvs/venv/bin:$PATH
32
+ RUN apt update && \
33
+ DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git && \
34
+ python -m venv /virtualenvs/venv/ && \
35
+ rm -rf /var/lib/apt/lists/*
36
+ """
37
+ + _POST_PYTHON_INSTALL_TEMPLATE
38
+ )
39
+
40
+ CUDA_DOCKERFILE_TEMPLATE = Template(
41
+ """
42
+ FROM --platform=linux/amd64 nvidia/cuda:${nvidia_cuda_image_tag}
43
+ ENV PATH=/virtualenvs/venv/bin:$PATH
44
+ 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 && \
45
+ 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 && \
46
+ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F23C5A6CF475977595C89F51BA6932366A755776 && \
47
+ apt update && \
48
+ DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git python${python_version}-dev python${python_version}-venv && \
49
+ python${python_version} -m venv /virtualenvs/venv/ && \
50
+ rm -rf /var/lib/apt/lists/*
51
+ """
52
+ + _POST_PYTHON_INSTALL_TEMPLATE
53
+ )
54
+
55
+ CUDA_VERSION_TO_IMAGE_TAG: Dict[str, str] = {
56
+ CUDAVersion.CUDA_11_0_CUDNN8.value: "11.0.3-cudnn8-runtime-ubuntu20.04",
57
+ CUDAVersion.CUDA_11_1_CUDNN8.value: "11.1.1-cudnn8-runtime-ubuntu20.04",
58
+ CUDAVersion.CUDA_11_2_CUDNN8.value: "11.2.2-cudnn8-runtime-ubuntu20.04",
59
+ CUDAVersion.CUDA_11_3_CUDNN8.value: "11.3.1-cudnn8-runtime-ubuntu20.04",
60
+ CUDAVersion.CUDA_11_4_CUDNN8.value: "11.4.3-cudnn8-runtime-ubuntu20.04",
61
+ CUDAVersion.CUDA_11_5_CUDNN8.value: "11.5.2-cudnn8-runtime-ubuntu20.04",
62
+ CUDAVersion.CUDA_11_6_CUDNN8.value: "11.6.2-cudnn8-runtime-ubuntu20.04",
63
+ CUDAVersion.CUDA_11_7_CUDNN8.value: "11.7.1-cudnn8-runtime-ubuntu22.04",
64
+ CUDAVersion.CUDA_11_8_CUDNN8.value: "11.8.0-cudnn8-runtime-ubuntu22.04",
65
+ CUDAVersion.CUDA_12_0_CUDNN8.value: "12.0.1-cudnn8-runtime-ubuntu22.04",
66
+ CUDAVersion.CUDA_12_1_CUDNN8.value: "12.1.1-cudnn8-runtime-ubuntu22.04",
67
+ CUDAVersion.CUDA_12_2_CUDNN8.value: "12.2.2-cudnn8-runtime-ubuntu22.04",
68
+ }
69
+
70
+
71
+ def resolve_requirements_txt_path(build_configuration: PythonBuild) -> Optional[str]:
72
+ if build_configuration.requirements_path:
73
+ return build_configuration.requirements_path
74
+
75
+ # TODO: what if there is a requirements.txt but user does not wants us to use it.
76
+ possible_requirements_txt_path = os.path.join(
77
+ build_configuration.build_context_path, "requirements.txt"
78
+ )
79
+
80
+ if os.path.isfile(possible_requirements_txt_path):
81
+ return os.path.relpath(
82
+ possible_requirements_txt_path, start=build_configuration.build_context_path
83
+ )
84
+
85
+ return None
86
+
87
+
88
+ def generate_apt_install_command(apt_packages: Optional[List[str]]) -> Optional[str]:
89
+ packages_list = None
90
+ if apt_packages:
91
+ packages_list = " ".join(p.strip() for p in apt_packages if p.strip())
92
+ if not packages_list:
93
+ return None
94
+ apt_update_command = "apt update"
95
+ apt_install_command = f"DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends {packages_list}"
96
+ clear_apt_lists_command = "rm -rf /var/lib/apt/lists/*"
97
+ return " && ".join(
98
+ [apt_update_command, apt_install_command, clear_apt_lists_command]
99
+ )
100
+
101
+
102
+ def generate_pip_install_command(
103
+ requirements_path: Optional[str], pip_packages: Optional[List[str]]
104
+ ) -> Optional[str]:
105
+ upgrade_pip_command = "python -m pip install -U pip setuptools wheel"
106
+ final_pip_install_command = None
107
+ pip_install_base_command = "python -m pip install --use-pep517 --no-cache-dir"
108
+ if requirements_path:
109
+ final_pip_install_command = f"{pip_install_base_command} -r {requirements_path}"
110
+
111
+ if pip_packages:
112
+ final_pip_install_command = (
113
+ final_pip_install_command or pip_install_base_command
114
+ )
115
+ final_pip_install_command += " " + " ".join(
116
+ f"'{package}'" for package in pip_packages
117
+ )
118
+
119
+ if not final_pip_install_command:
120
+ return None
121
+
122
+ return " && ".join([upgrade_pip_command, final_pip_install_command])
123
+
124
+
125
+ def generate_dockerfile_content(
126
+ build_configuration: PythonBuild,
127
+ ) -> str:
128
+ # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
129
+ requirements_path = resolve_requirements_txt_path(build_configuration)
130
+ requirements_destination_path = (
131
+ "/tmp/requirements.txt" if requirements_path else None
132
+ )
133
+ pip_install_command = generate_pip_install_command(
134
+ requirements_path=requirements_destination_path,
135
+ pip_packages=build_configuration.pip_packages,
136
+ )
137
+ apt_install_command = generate_apt_install_command(
138
+ apt_packages=build_configuration.apt_packages
139
+ )
140
+
141
+ template_args = {
142
+ "python_version": build_configuration.python_version,
143
+ "apt_install_command": apt_install_command,
144
+ "requirements_path": requirements_path,
145
+ "requirements_destination_path": requirements_destination_path,
146
+ "pip_install_command": pip_install_command,
147
+ }
148
+
149
+ if build_configuration.cuda_version:
150
+ template = CUDA_DOCKERFILE_TEMPLATE
151
+ template_args["nvidia_cuda_image_tag"] = CUDA_VERSION_TO_IMAGE_TAG.get(
152
+ build_configuration.cuda_version, build_configuration.cuda_version
153
+ )
154
+ else:
155
+ template = DOCKERFILE_TEMPLATE
156
+
157
+ dockerfile_content = template.render(**template_args)
158
+ return dockerfile_content
@@ -0,0 +1,168 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Dict, List, Optional
4
+
5
+ import docker
6
+ from rich.console import Console
7
+
8
+ from truefoundry.deploy.lib.clients.shell_client import Shell
9
+ from truefoundry.logger import logger
10
+
11
+ __all__ = [
12
+ "build_docker_image",
13
+ "push_docker_image",
14
+ "pull_docker_image",
15
+ "push_docker_image_with_latest_tag",
16
+ "env_has_docker",
17
+ ]
18
+
19
+
20
+ def _get_build_args_string(build_args: Optional[Dict[str, str]] = None) -> str:
21
+ if not build_args:
22
+ return None
23
+ result = []
24
+ for param, value in build_args.items():
25
+ result.extend(["--build-arg", f"{param.strip()}={value}"])
26
+ return result
27
+
28
+
29
+ def _get_docker_client():
30
+ try:
31
+ return docker.from_env()
32
+ except Exception as ex:
33
+ raise Exception("Could not connect to Docker") from ex
34
+
35
+
36
+ def env_has_docker():
37
+ try:
38
+ _get_docker_client()
39
+ return True
40
+ except Exception:
41
+ return False
42
+
43
+
44
+ # this is required since push does throw an error if it
45
+ # fails - so we have to parse the response logs to catch the error
46
+ # the other option is to run `docker login` as a subprocess, but it's
47
+ # not recommended to provide password to subprocess
48
+ def _catch_error_in_push(response: List[dict]):
49
+ for line in response:
50
+ if line.get("error") is not None:
51
+ raise Exception(
52
+ f'Failed to push to registry with message \'{line.get("error")}\''
53
+ )
54
+
55
+
56
+ def _run_cmds(command: List[str]):
57
+ console = Console(color_system=None, markup=False, soft_wrap=True)
58
+ with subprocess.Popen(
59
+ command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
60
+ ) as sp:
61
+ for line in sp.stdout:
62
+ console.print(line.decode("utf-8").strip())
63
+ sp.communicate()
64
+ if sp.returncode is not None and sp.returncode != 0:
65
+ raise Exception(f"Command: {command} failed")
66
+
67
+
68
+ def build_docker_image(
69
+ path: str,
70
+ tag: str,
71
+ platform: str,
72
+ dockerfile: str,
73
+ extra_opts: Optional[List[str]] = None,
74
+ build_args: Optional[Dict[str, str]] = None,
75
+ ):
76
+ use_depot = bool(os.environ.get("USE_DEPOT"))
77
+ depot_project_id = os.environ.get("DEPOT_PROJECT_KEY")
78
+ logger.info("Starting docker build...")
79
+ if use_depot and depot_project_id:
80
+ try:
81
+ command = [
82
+ "depot",
83
+ "build",
84
+ "--project",
85
+ depot_project_id,
86
+ "-f",
87
+ dockerfile,
88
+ "-t",
89
+ tag,
90
+ path,
91
+ ]
92
+ final_build_args = _get_build_args_string(build_args=build_args)
93
+ if final_build_args:
94
+ command.extend(final_build_args)
95
+ command.append("--push") # keep push at last
96
+ Shell().execute_shell_command(command=command)
97
+ except Exception as e:
98
+ raise Exception("Error while building Docker image using Depot") from e
99
+ else:
100
+ try:
101
+ # TODO (chiragjn): Maybe consider using client.images.build
102
+ build_args_list = []
103
+ if build_args:
104
+ for k, v in build_args.items():
105
+ build_args_list += ["--build-arg", f"{k}={v}"]
106
+
107
+ docker_build_cmd = [
108
+ "docker",
109
+ "build",
110
+ "-t",
111
+ tag,
112
+ "-f",
113
+ dockerfile,
114
+ "--platform",
115
+ platform,
116
+ ]
117
+ docker_build_cmd += [path]
118
+ docker_build_cmd += build_args_list
119
+ docker_build_cmd += extra_opts if extra_opts else []
120
+ _run_cmds(docker_build_cmd)
121
+ except Exception as e:
122
+ raise Exception(f"Error while building Docker image: {e}") from e
123
+
124
+
125
+ def push_docker_image(
126
+ image_uri: str,
127
+ docker_login_username: str,
128
+ docker_login_password: str,
129
+ ):
130
+ client = _get_docker_client()
131
+ auth_config = {"username": docker_login_username, "password": docker_login_password}
132
+ logger.info(f"Pushing {image_uri}")
133
+ response = client.images.push(
134
+ repository=image_uri, auth_config=auth_config, decode=True, stream=True
135
+ )
136
+ _catch_error_in_push(response=response)
137
+
138
+
139
+ def push_docker_image_with_latest_tag(
140
+ image_uri: str,
141
+ docker_login_username: str,
142
+ docker_login_password: str,
143
+ ):
144
+ client = _get_docker_client()
145
+ auth_config = {"username": docker_login_username, "password": docker_login_password}
146
+ repository_without_tag, _ = image_uri.rsplit(":", 1)
147
+ image = client.images.get(image_uri)
148
+ image.tag(repository=repository_without_tag, tag="latest")
149
+
150
+ logger.info(f"Pushing {repository_without_tag}:latest")
151
+ response = client.images.push(
152
+ repository=repository_without_tag,
153
+ tag="latest",
154
+ auth_config=auth_config,
155
+ decode=True,
156
+ stream=True,
157
+ )
158
+ _catch_error_in_push(response=response)
159
+
160
+
161
+ def pull_docker_image(
162
+ image_uri: str,
163
+ docker_login_username: str,
164
+ docker_login_password: str,
165
+ ):
166
+ auth_config = {"username": docker_login_username, "password": docker_login_password}
167
+ logger.info(f"Pulling cache image {image_uri}")
168
+ _get_docker_client().images.pull(repository=image_uri, auth_config=auth_config)