flyte 2.0.0b22__py3-none-any.whl → 2.0.0b30__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.
- flyte/__init__.py +18 -2
- flyte/_bin/runtime.py +43 -5
- flyte/_cache/cache.py +4 -2
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +1 -1
- flyte/_code_bundle/_packaging.py +4 -4
- flyte/_code_bundle/_utils.py +14 -8
- flyte/_code_bundle/bundle.py +13 -5
- flyte/_constants.py +1 -0
- flyte/_context.py +4 -1
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +0 -1
- flyte/_debug/vscode.py +6 -1
- flyte/_deploy.py +223 -59
- flyte/_environment.py +5 -0
- flyte/_excepthook.py +1 -1
- flyte/_image.py +144 -82
- flyte/_initialize.py +95 -12
- flyte/_interface.py +2 -0
- flyte/_internal/controllers/_local_controller.py +65 -24
- flyte/_internal/controllers/_trace.py +1 -1
- flyte/_internal/controllers/remote/_action.py +13 -11
- flyte/_internal/controllers/remote/_client.py +1 -1
- flyte/_internal/controllers/remote/_controller.py +9 -4
- flyte/_internal/controllers/remote/_core.py +16 -16
- flyte/_internal/controllers/remote/_informer.py +4 -4
- flyte/_internal/controllers/remote/_service_protocol.py +7 -7
- flyte/_internal/imagebuild/docker_builder.py +139 -84
- flyte/_internal/imagebuild/image_builder.py +7 -13
- flyte/_internal/imagebuild/remote_builder.py +65 -13
- flyte/_internal/imagebuild/utils.py +51 -3
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +42 -20
- flyte/_internal/runtime/entrypoints.py +24 -1
- flyte/_internal/runtime/io.py +21 -8
- flyte/_internal/runtime/resources_serde.py +20 -6
- flyte/_internal/runtime/reuse.py +1 -1
- flyte/_internal/runtime/rusty.py +20 -5
- flyte/_internal/runtime/task_serde.py +33 -27
- flyte/_internal/runtime/taskrunner.py +10 -1
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_keyring/file.py +39 -9
- flyte/_logging.py +79 -12
- flyte/_map.py +31 -12
- flyte/_module.py +70 -0
- flyte/_pod.py +2 -2
- flyte/_resources.py +213 -31
- flyte/_run.py +107 -41
- flyte/_task.py +66 -10
- flyte/_task_environment.py +96 -24
- flyte/_task_plugins.py +4 -2
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +2 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/module_loader.py +17 -2
- flyte/_version.py +3 -3
- flyte/cli/_abort.py +3 -3
- flyte/cli/_build.py +1 -3
- flyte/cli/_common.py +78 -7
- flyte/cli/_create.py +178 -3
- flyte/cli/_delete.py +23 -1
- flyte/cli/_deploy.py +49 -11
- flyte/cli/_get.py +79 -34
- flyte/cli/_params.py +8 -6
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +127 -11
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +30 -4
- flyte/config/_config.py +2 -0
- flyte/config/_internal.py +1 -0
- flyte/config/_reader.py +3 -3
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +270 -0
- flyte/connectors/_server.py +197 -0
- flyte/connectors/utils.py +135 -0
- flyte/errors.py +10 -1
- flyte/extend.py +8 -1
- flyte/extras/_container.py +6 -1
- flyte/git/_config.py +11 -9
- flyte/io/__init__.py +2 -0
- flyte/io/_dataframe/__init__.py +2 -0
- flyte/io/_dataframe/basic_dfs.py +1 -1
- flyte/io/_dataframe/dataframe.py +12 -8
- flyte/io/_dir.py +551 -120
- flyte/io/_file.py +538 -141
- flyte/models.py +57 -12
- flyte/remote/__init__.py +6 -1
- flyte/remote/_action.py +18 -16
- flyte/remote/_client/_protocols.py +39 -4
- flyte/remote/_client/auth/_channel.py +10 -6
- flyte/remote/_client/controlplane.py +17 -5
- flyte/remote/_console.py +3 -2
- flyte/remote/_data.py +4 -3
- flyte/remote/_logs.py +3 -3
- flyte/remote/_run.py +47 -7
- flyte/remote/_secret.py +26 -17
- flyte/remote/_task.py +21 -9
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/storage/__init__.py +6 -1
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_storage.py +185 -103
- flyte/types/__init__.py +16 -0
- flyte/types/_interface.py +2 -2
- flyte/types/_pickle.py +17 -4
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +26 -19
- flyte/types/_utils.py +1 -1
- {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/runtime.py +43 -5
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/METADATA +8 -1
- flyte-2.0.0b30.dist-info/RECORD +192 -0
- flyte/_protos/__init__.py +0 -0
- flyte/_protos/common/authorization_pb2.py +0 -66
- flyte/_protos/common/authorization_pb2.pyi +0 -108
- flyte/_protos/common/authorization_pb2_grpc.py +0 -4
- flyte/_protos/common/identifier_pb2.py +0 -99
- flyte/_protos/common/identifier_pb2.pyi +0 -120
- flyte/_protos/common/identifier_pb2_grpc.py +0 -4
- flyte/_protos/common/identity_pb2.py +0 -48
- flyte/_protos/common/identity_pb2.pyi +0 -72
- flyte/_protos/common/identity_pb2_grpc.py +0 -4
- flyte/_protos/common/list_pb2.py +0 -36
- flyte/_protos/common/list_pb2.pyi +0 -71
- flyte/_protos/common/list_pb2_grpc.py +0 -4
- flyte/_protos/common/policy_pb2.py +0 -37
- flyte/_protos/common/policy_pb2.pyi +0 -27
- flyte/_protos/common/policy_pb2_grpc.py +0 -4
- flyte/_protos/common/role_pb2.py +0 -37
- flyte/_protos/common/role_pb2.pyi +0 -53
- flyte/_protos/common/role_pb2_grpc.py +0 -4
- flyte/_protos/common/runtime_version_pb2.py +0 -28
- flyte/_protos/common/runtime_version_pb2.pyi +0 -24
- flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/definition_pb2.py +0 -60
- flyte/_protos/imagebuilder/definition_pb2.pyi +0 -153
- flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/payload_pb2.py +0 -32
- flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
- flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/service_pb2.py +0 -29
- flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
- flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
- flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/definition_pb2.py +0 -49
- flyte/_protos/secret/definition_pb2.pyi +0 -93
- flyte/_protos/secret/definition_pb2_grpc.py +0 -4
- flyte/_protos/secret/payload_pb2.py +0 -62
- flyte/_protos/secret/payload_pb2.pyi +0 -94
- flyte/_protos/secret/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/secret_pb2.py +0 -38
- flyte/_protos/secret/secret_pb2.pyi +0 -6
- flyte/_protos/secret/secret_pb2_grpc.py +0 -198
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/common_pb2.py +0 -27
- flyte/_protos/workflow/common_pb2.pyi +0 -14
- flyte/_protos/workflow/common_pb2_grpc.py +0 -4
- flyte/_protos/workflow/environment_pb2.py +0 -29
- flyte/_protos/workflow/environment_pb2.pyi +0 -12
- flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
- flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
- flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- flyte/_protos/workflow/queue_service_pb2.py +0 -111
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -168
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -123
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -352
- flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
- flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- flyte/_protos/workflow/run_service_pb2.py +0 -137
- flyte/_protos/workflow/run_service_pb2.pyi +0 -185
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
- flyte/_protos/workflow/state_service_pb2.py +0 -67
- flyte/_protos/workflow/state_service_pb2.pyi +0 -76
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -82
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -88
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -60
- flyte/_protos/workflow/task_service_pb2.pyi +0 -59
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
- flyte-2.0.0b22.dist-info/RECORD +0 -250
- {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/debug.py +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ import tempfile
|
|
|
6
6
|
import typing
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from string import Template
|
|
9
|
-
from typing import ClassVar,
|
|
9
|
+
from typing import ClassVar, Optional, Protocol, cast
|
|
10
10
|
|
|
11
11
|
import aiofiles
|
|
12
12
|
import click
|
|
@@ -22,6 +22,7 @@ from flyte._image import (
|
|
|
22
22
|
Layer,
|
|
23
23
|
PipOption,
|
|
24
24
|
PipPackages,
|
|
25
|
+
PoetryProject,
|
|
25
26
|
PythonWheels,
|
|
26
27
|
Requirements,
|
|
27
28
|
UVProject,
|
|
@@ -37,7 +38,7 @@ from flyte._internal.imagebuild.image_builder import (
|
|
|
37
38
|
LocalDockerCommandImageChecker,
|
|
38
39
|
LocalPodmanCommandImageChecker,
|
|
39
40
|
)
|
|
40
|
-
from flyte._internal.imagebuild.utils import copy_files_to_context
|
|
41
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
|
|
41
42
|
from flyte._logging import logger
|
|
42
43
|
|
|
43
44
|
_F_IMG_ID = "_F_IMG_ID"
|
|
@@ -46,44 +47,71 @@ FLYTE_DOCKER_BUILDER_CACHE_TO = "FLYTE_DOCKER_BUILDER_CACHE_TO"
|
|
|
46
47
|
|
|
47
48
|
UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
|
|
48
49
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
--mount=type=bind,target=uv.lock,src=$UV_LOCK_PATH,rw \
|
|
51
|
+
--mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
|
|
52
|
+
$SECRET_MOUNT \
|
|
53
|
+
uv sync --active --inexact $PIP_INSTALL_ARGS
|
|
53
54
|
""")
|
|
54
55
|
|
|
55
56
|
UV_LOCK_INSTALL_TEMPLATE = Template("""\
|
|
56
|
-
COPY $PYPROJECT_PATH $PYPROJECT_PATH
|
|
57
57
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
--mount=type=bind,target=/root/.flyte/$PYPROJECT_PATH,src=$PYPROJECT_PATH,rw \
|
|
59
|
+
$SECRET_MOUNT \
|
|
60
|
+
uv sync --active --inexact --no-editable $PIP_INSTALL_ARGS --project /root/.flyte/$PYPROJECT_PATH
|
|
61
|
+
""")
|
|
62
|
+
|
|
63
|
+
POETRY_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
|
|
64
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
65
|
+
uv pip install poetry
|
|
66
|
+
|
|
67
|
+
ENV POETRY_CACHE_DIR=/tmp/poetry_cache \
|
|
68
|
+
POETRY_VIRTUALENVS_IN_PROJECT=true
|
|
69
|
+
|
|
70
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/tmp/poetry_cache,id=poetry \
|
|
71
|
+
--mount=type=bind,target=poetry.lock,src=$POETRY_LOCK_PATH \
|
|
72
|
+
--mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
|
|
73
|
+
$SECRET_MOUNT \
|
|
74
|
+
poetry install $POETRY_INSTALL_ARGS
|
|
75
|
+
""")
|
|
76
|
+
|
|
77
|
+
POETRY_LOCK_INSTALL_TEMPLATE = Template("""\
|
|
78
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
79
|
+
uv pip install poetry
|
|
80
|
+
|
|
81
|
+
ENV POETRY_CACHE_DIR=/tmp/poetry_cache \
|
|
82
|
+
POETRY_VIRTUALENVS_IN_PROJECT=true
|
|
83
|
+
|
|
84
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/tmp/poetry_cache,id=poetry \
|
|
85
|
+
--mount=type=bind,target=/root/.flyte/$PYPROJECT_PATH,src=$PYPROJECT_PATH,rw \
|
|
86
|
+
$SECRET_MOUNT \
|
|
87
|
+
poetry install $POETRY_INSTALL_ARGS -C /root/.flyte/$PYPROJECT_PATH
|
|
60
88
|
""")
|
|
61
89
|
|
|
62
90
|
UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
63
91
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
$REQUIREMENTS_MOUNT \
|
|
93
|
+
$SECRET_MOUNT \
|
|
94
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
67
95
|
""")
|
|
68
96
|
|
|
69
97
|
UV_WHEEL_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
70
98
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=wheel \
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
99
|
+
--mount=source=/dist,target=/dist,type=bind \
|
|
100
|
+
$SECRET_MOUNT \
|
|
101
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
74
102
|
""")
|
|
75
103
|
|
|
76
104
|
APT_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
77
105
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
$SECRET_MOUNT \
|
|
107
|
+
apt-get update && apt-get install -y --no-install-recommends \
|
|
108
|
+
$APT_PACKAGES
|
|
81
109
|
""")
|
|
82
110
|
|
|
83
111
|
UV_PYTHON_INSTALL_COMMAND = Template("""\
|
|
84
112
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
$SECRET_MOUNT \
|
|
114
|
+
uv pip install $PIP_INSTALL_ARGS
|
|
87
115
|
""")
|
|
88
116
|
|
|
89
117
|
# uv pip install --python /root/env/bin/python
|
|
@@ -93,24 +121,29 @@ DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
|
|
|
93
121
|
FROM ghcr.io/astral-sh/uv:0.8.13 AS uv
|
|
94
122
|
FROM $BASE_IMAGE
|
|
95
123
|
|
|
124
|
+
|
|
96
125
|
USER root
|
|
97
126
|
|
|
127
|
+
|
|
98
128
|
# Copy in uv so that later commands don't have to mount it in
|
|
99
129
|
COPY --from=uv /uv /usr/bin/uv
|
|
100
130
|
|
|
131
|
+
|
|
101
132
|
# Configure default envs
|
|
102
133
|
ENV UV_COMPILE_BYTECODE=1 \
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
134
|
+
UV_LINK_MODE=copy \
|
|
135
|
+
VIRTUALENV=/opt/venv \
|
|
136
|
+
UV_PYTHON=/opt/venv/bin/python \
|
|
137
|
+
PATH="/opt/venv/bin:$$PATH"
|
|
138
|
+
|
|
107
139
|
|
|
108
140
|
# Create a virtualenv with the user specified python version
|
|
109
141
|
RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION
|
|
110
142
|
|
|
143
|
+
|
|
111
144
|
# Adds nvidia just in case it exists
|
|
112
145
|
ENV PATH="$$PATH:/usr/local/nvidia/bin:/usr/local/cuda/bin" \
|
|
113
|
-
|
|
146
|
+
LD_LIBRARY_PATH="/usr/local/nvidia/lib64"
|
|
114
147
|
""")
|
|
115
148
|
|
|
116
149
|
# This gets added on to the end of the dockerfile
|
|
@@ -230,24 +263,37 @@ class AptPackagesHandler:
|
|
|
230
263
|
|
|
231
264
|
class UVProjectHandler:
|
|
232
265
|
@staticmethod
|
|
233
|
-
async def handle(
|
|
266
|
+
async def handle(
|
|
267
|
+
layer: UVProject, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
268
|
+
) -> str:
|
|
234
269
|
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
235
|
-
if layer.
|
|
270
|
+
if layer.project_install_mode == "dependencies_only":
|
|
271
|
+
pip_install_args = " ".join(layer.get_pip_install_args())
|
|
272
|
+
if "--no-install-project" not in pip_install_args:
|
|
273
|
+
pip_install_args += " --no-install-project"
|
|
274
|
+
if "--no-sources" not in pip_install_args:
|
|
275
|
+
pip_install_args += " --no-sources"
|
|
236
276
|
# Only Copy pyproject.yaml and uv.lock.
|
|
237
277
|
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
238
278
|
uvlock_dst = copy_files_to_context(layer.uvlock, context_path)
|
|
239
279
|
delta = UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
|
|
240
280
|
UV_LOCK_PATH=uvlock_dst.relative_to(context_path),
|
|
241
281
|
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
242
|
-
PIP_INSTALL_ARGS=
|
|
282
|
+
PIP_INSTALL_ARGS=pip_install_args,
|
|
243
283
|
SECRET_MOUNT=secret_mounts,
|
|
244
284
|
)
|
|
245
285
|
else:
|
|
246
286
|
# Copy the entire project.
|
|
247
|
-
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
|
|
248
|
-
|
|
249
|
-
|
|
287
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
|
|
288
|
+
|
|
289
|
+
# Make sure pyproject.toml and uv.lock files are not removed by docker ignore
|
|
290
|
+
uv_lock_context_path = pyproject_dst / "uv.lock"
|
|
291
|
+
pyproject_context_path = pyproject_dst / "pyproject.toml"
|
|
292
|
+
if not uv_lock_context_path.exists():
|
|
250
293
|
shutil.copy(layer.uvlock, pyproject_dst)
|
|
294
|
+
if not pyproject_context_path.exists():
|
|
295
|
+
shutil.copy(layer.pyproject, pyproject_dst)
|
|
296
|
+
|
|
251
297
|
delta = UV_LOCK_INSTALL_TEMPLATE.substitute(
|
|
252
298
|
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
253
299
|
PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
|
|
@@ -258,6 +304,46 @@ class UVProjectHandler:
|
|
|
258
304
|
return dockerfile
|
|
259
305
|
|
|
260
306
|
|
|
307
|
+
class PoetryProjectHandler:
|
|
308
|
+
@staticmethod
|
|
309
|
+
async def handel(
|
|
310
|
+
layer: PoetryProject, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
311
|
+
) -> str:
|
|
312
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
313
|
+
extra_args = layer.extra_args or ""
|
|
314
|
+
if layer.project_install_mode == "dependencies_only":
|
|
315
|
+
# Only Copy pyproject.yaml and poetry.lock.
|
|
316
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
317
|
+
poetry_lock_dst = copy_files_to_context(layer.poetry_lock, context_path)
|
|
318
|
+
if "--no-root" not in extra_args:
|
|
319
|
+
extra_args += " --no-root"
|
|
320
|
+
delta = POETRY_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
|
|
321
|
+
POETRY_LOCK_PATH=poetry_lock_dst.relative_to(context_path),
|
|
322
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
323
|
+
POETRY_INSTALL_ARGS=extra_args,
|
|
324
|
+
SECRET_MOUNT=secret_mounts,
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
# Copy the entire project.
|
|
328
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
|
|
329
|
+
|
|
330
|
+
# Make sure pyproject.toml and poetry.lock files are not removed by docker ignore
|
|
331
|
+
poetry_lock_context_path = pyproject_dst / "poetry.lock"
|
|
332
|
+
pyproject_context_path = pyproject_dst / "pyproject.toml"
|
|
333
|
+
if not poetry_lock_context_path.exists():
|
|
334
|
+
shutil.copy(layer.poetry_lock, pyproject_dst)
|
|
335
|
+
if not pyproject_context_path.exists():
|
|
336
|
+
shutil.copy(layer.pyproject, pyproject_dst)
|
|
337
|
+
|
|
338
|
+
delta = POETRY_LOCK_INSTALL_TEMPLATE.substitute(
|
|
339
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
340
|
+
POETRY_INSTALL_ARGS=extra_args,
|
|
341
|
+
SECRET_MOUNT=secret_mounts,
|
|
342
|
+
)
|
|
343
|
+
dockerfile += delta
|
|
344
|
+
return dockerfile
|
|
345
|
+
|
|
346
|
+
|
|
261
347
|
class DockerIgnoreHandler:
|
|
262
348
|
@staticmethod
|
|
263
349
|
async def handle(layer: DockerIgnore, context_path: Path, _: str):
|
|
@@ -265,37 +351,9 @@ class DockerIgnoreHandler:
|
|
|
265
351
|
|
|
266
352
|
|
|
267
353
|
class CopyConfigHandler:
|
|
268
|
-
@staticmethod
|
|
269
|
-
def list_dockerignore(root_path: Optional[Path], docker_ignore_file_path: Optional[Path]) -> List[str]:
|
|
270
|
-
patterns: List[str] = []
|
|
271
|
-
dockerignore_path: Optional[Path] = None
|
|
272
|
-
if root_path:
|
|
273
|
-
dockerignore_path = root_path / ".dockerignore"
|
|
274
|
-
# DockerIgnore layer should be first priority
|
|
275
|
-
if docker_ignore_file_path:
|
|
276
|
-
dockerignore_path = docker_ignore_file_path
|
|
277
|
-
|
|
278
|
-
# Return empty list if no .dockerignore file found
|
|
279
|
-
if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
|
|
280
|
-
logger.info(f".dockerignore file not found at path: {dockerignore_path}")
|
|
281
|
-
return patterns
|
|
282
|
-
|
|
283
|
-
try:
|
|
284
|
-
with open(dockerignore_path, "r", encoding="utf-8") as f:
|
|
285
|
-
for line in f:
|
|
286
|
-
stripped_line = line.strip()
|
|
287
|
-
# Skip empty lines, whitespace-only lines, and comments
|
|
288
|
-
if not stripped_line or stripped_line.startswith("#"):
|
|
289
|
-
continue
|
|
290
|
-
patterns.append(stripped_line)
|
|
291
|
-
except Exception as e:
|
|
292
|
-
logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
|
|
293
|
-
return []
|
|
294
|
-
return patterns
|
|
295
|
-
|
|
296
354
|
@staticmethod
|
|
297
355
|
async def handle(
|
|
298
|
-
layer: CopyConfig, context_path: Path, dockerfile: str,
|
|
356
|
+
layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
299
357
|
) -> str:
|
|
300
358
|
# Copy the source config file or directory to the context path
|
|
301
359
|
if layer.src.is_absolute() or ".." in str(layer.src):
|
|
@@ -311,11 +369,6 @@ class CopyConfigHandler:
|
|
|
311
369
|
shutil.copy(abs_path, dst_path)
|
|
312
370
|
elif layer.src.is_dir():
|
|
313
371
|
# Copy the entire directory
|
|
314
|
-
from flyte._initialize import _get_init_config
|
|
315
|
-
|
|
316
|
-
init_config = _get_init_config()
|
|
317
|
-
root_path = init_config.root_dir if init_config else None
|
|
318
|
-
docker_ignore_patterns = CopyConfigHandler.list_dockerignore(root_path, docker_ignore_file_path)
|
|
319
372
|
shutil.copytree(
|
|
320
373
|
abs_path, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*docker_ignore_patterns)
|
|
321
374
|
)
|
|
@@ -332,8 +385,9 @@ class CommandsHandler:
|
|
|
332
385
|
@staticmethod
|
|
333
386
|
async def handle(layer: Commands, _: Path, dockerfile: str) -> str:
|
|
334
387
|
# Append raw commands to the dockerfile
|
|
388
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
335
389
|
for command in layer.commands:
|
|
336
|
-
dockerfile += f"\nRUN {command}\n"
|
|
390
|
+
dockerfile += f"\nRUN {secret_mounts} {command}\n"
|
|
337
391
|
|
|
338
392
|
return dockerfile
|
|
339
393
|
|
|
@@ -355,9 +409,8 @@ def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
|
|
|
355
409
|
secret = Secret(key=secret)
|
|
356
410
|
secret_id = hash(secret)
|
|
357
411
|
secret_env_key = "_".join([k.upper() for k in filter(None, (secret.group, secret.key))])
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return ["--secret", f"id={secret_id},env={secret_env}"]
|
|
412
|
+
if os.getenv(secret_env_key):
|
|
413
|
+
return ["--secret", f"id={secret_id},env={secret_env_key}"]
|
|
361
414
|
secret_file_name = "_".join(list(filter(None, (secret.group, secret.key))))
|
|
362
415
|
secret_file_path = f"/etc/secrets/{secret_file_name}"
|
|
363
416
|
if not os.path.exists(secret_file_path):
|
|
@@ -365,7 +418,7 @@ def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
|
|
|
365
418
|
return ["--secret", f"id={secret_id},src={secret_file_path}"]
|
|
366
419
|
|
|
367
420
|
for layer in layers:
|
|
368
|
-
if isinstance(layer, (PipOption, AptPackages)):
|
|
421
|
+
if isinstance(layer, (PipOption, AptPackages, Commands)):
|
|
369
422
|
if layer.secret_mounts:
|
|
370
423
|
for secret_mount in layer.secret_mounts:
|
|
371
424
|
commands.extend(_get_secret_command(secret_mount))
|
|
@@ -391,7 +444,7 @@ def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) ->
|
|
|
391
444
|
|
|
392
445
|
|
|
393
446
|
async def _process_layer(
|
|
394
|
-
layer: Layer, context_path: Path, dockerfile: str,
|
|
447
|
+
layer: Layer, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
395
448
|
) -> str:
|
|
396
449
|
match layer:
|
|
397
450
|
case PythonWheels():
|
|
@@ -424,11 +477,19 @@ async def _process_layer(
|
|
|
424
477
|
|
|
425
478
|
case UVProject():
|
|
426
479
|
# Handle UV project
|
|
427
|
-
dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile)
|
|
480
|
+
dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
481
|
+
|
|
482
|
+
case PoetryProject():
|
|
483
|
+
# Handle Poetry project
|
|
484
|
+
dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
485
|
+
|
|
486
|
+
case PoetryProject():
|
|
487
|
+
# Handle Poetry project
|
|
488
|
+
dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
428
489
|
|
|
429
490
|
case CopyConfig():
|
|
430
491
|
# Handle local files and folders
|
|
431
|
-
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile,
|
|
492
|
+
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
432
493
|
|
|
433
494
|
case Commands():
|
|
434
495
|
# Handle commands
|
|
@@ -555,15 +616,6 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
555
616
|
else:
|
|
556
617
|
logger.info("Buildx builder already exists.")
|
|
557
618
|
|
|
558
|
-
def get_docker_ignore(self, image: Image) -> Optional[Path]:
|
|
559
|
-
"""Get the .dockerignore file path from the image layers."""
|
|
560
|
-
# Look for DockerIgnore layer in the image layers
|
|
561
|
-
for layer in image._layers:
|
|
562
|
-
if isinstance(layer, DockerIgnore) and layer.path.strip():
|
|
563
|
-
return Path(layer.path)
|
|
564
|
-
|
|
565
|
-
return None
|
|
566
|
-
|
|
567
619
|
async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool = False) -> str:
|
|
568
620
|
"""
|
|
569
621
|
if default image (only base image and locked), raise an error, don't have a dockerfile
|
|
@@ -572,6 +624,7 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
572
624
|
- start from the base image
|
|
573
625
|
- use python to create a default venv and export variables
|
|
574
626
|
|
|
627
|
+
|
|
575
628
|
Then for the layers
|
|
576
629
|
- for each layer
|
|
577
630
|
- find the appropriate layer handler
|
|
@@ -593,9 +646,11 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
593
646
|
PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
|
|
594
647
|
)
|
|
595
648
|
|
|
596
|
-
|
|
649
|
+
# Get .dockerignore file patterns first
|
|
650
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
651
|
+
|
|
597
652
|
for layer in image._layers:
|
|
598
|
-
dockerfile = await _process_layer(layer, tmp_path, dockerfile,
|
|
653
|
+
dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_patterns)
|
|
599
654
|
|
|
600
655
|
dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
|
|
601
656
|
|
|
@@ -135,11 +135,6 @@ class ImageBuildEngine:
|
|
|
135
135
|
|
|
136
136
|
ImageBuilderType = typing.Literal["local", "remote"]
|
|
137
137
|
|
|
138
|
-
_SEEN_IMAGES: typing.ClassVar[typing.Dict[str, str]] = {
|
|
139
|
-
# Set default for the auto container. See Image._identifier_override for more info.
|
|
140
|
-
"auto": Image.from_debian_base().uri,
|
|
141
|
-
}
|
|
142
|
-
|
|
143
138
|
@staticmethod
|
|
144
139
|
@alru_cache
|
|
145
140
|
async def image_exists(image: Image) -> Optional[str]:
|
|
@@ -235,7 +230,7 @@ class ImageBuildEngine:
|
|
|
235
230
|
|
|
236
231
|
|
|
237
232
|
class ImageCache(BaseModel):
|
|
238
|
-
image_lookup: Dict[str,
|
|
233
|
+
image_lookup: Dict[str, str]
|
|
239
234
|
serialized_form: str | None = None
|
|
240
235
|
|
|
241
236
|
@property
|
|
@@ -273,11 +268,10 @@ class ImageCache(BaseModel):
|
|
|
273
268
|
"""
|
|
274
269
|
tuples = []
|
|
275
270
|
for k, v in self.image_lookup.items():
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
271
|
+
tuples.append(
|
|
272
|
+
[
|
|
273
|
+
("Name", k),
|
|
274
|
+
("image", v),
|
|
275
|
+
]
|
|
276
|
+
)
|
|
283
277
|
return tuples
|
|
@@ -16,6 +16,7 @@ import flyte.errors
|
|
|
16
16
|
from flyte import Image, remote
|
|
17
17
|
from flyte._code_bundle._utils import tar_strip_file_attributes
|
|
18
18
|
from flyte._image import (
|
|
19
|
+
_BASE_REGISTRY,
|
|
19
20
|
AptPackages,
|
|
20
21
|
Architecture,
|
|
21
22
|
Commands,
|
|
@@ -24,6 +25,7 @@ from flyte._image import (
|
|
|
24
25
|
Env,
|
|
25
26
|
PipOption,
|
|
26
27
|
PipPackages,
|
|
28
|
+
PoetryProject,
|
|
27
29
|
PythonWheels,
|
|
28
30
|
Requirements,
|
|
29
31
|
UVProject,
|
|
@@ -31,14 +33,14 @@ from flyte._image import (
|
|
|
31
33
|
WorkDir,
|
|
32
34
|
)
|
|
33
35
|
from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
|
|
34
|
-
from flyte._internal.imagebuild.utils import copy_files_to_context
|
|
36
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
|
|
35
37
|
from flyte._internal.runtime.task_serde import get_security_context
|
|
36
38
|
from flyte._logging import logger
|
|
37
39
|
from flyte._secret import Secret
|
|
38
40
|
from flyte.remote import ActionOutputs, Run
|
|
39
41
|
|
|
40
42
|
if TYPE_CHECKING:
|
|
41
|
-
from
|
|
43
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
|
|
42
44
|
|
|
43
45
|
IMAGE_TASK_NAME = "build-image"
|
|
44
46
|
IMAGE_TASK_PROJECT = "system"
|
|
@@ -68,10 +70,11 @@ class RemoteImageChecker(ImageChecker):
|
|
|
68
70
|
image_name = f"{repository.split('/')[-1]}:{tag}"
|
|
69
71
|
|
|
70
72
|
try:
|
|
73
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition__pb2
|
|
74
|
+
from flyteidl2.imagebuilder import payload_pb2 as image_payload__pb2
|
|
75
|
+
from flyteidl2.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
|
|
76
|
+
|
|
71
77
|
from flyte._initialize import _get_init_config
|
|
72
|
-
from flyte._protos.imagebuilder import definition_pb2 as image_definition__pb2
|
|
73
|
-
from flyte._protos.imagebuilder import payload_pb2 as image_payload__pb2
|
|
74
|
-
from flyte._protos.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
|
|
75
78
|
|
|
76
79
|
cfg = _get_init_config()
|
|
77
80
|
if cfg is None:
|
|
@@ -96,7 +99,7 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
96
99
|
return [RemoteImageChecker]
|
|
97
100
|
|
|
98
101
|
async def build_image(self, image: Image, dry_run: bool = False) -> str:
|
|
99
|
-
from
|
|
102
|
+
from flyteidl2.workflow import run_definition_pb2
|
|
100
103
|
|
|
101
104
|
image_name = f"{image.name}:{image._final_tag}"
|
|
102
105
|
spec, context = await _validate_configuration(image)
|
|
@@ -110,10 +113,18 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
110
113
|
).override.aio(secrets=_get_build_secrets_from_image(image))
|
|
111
114
|
|
|
112
115
|
logger.warning("[bold blue]🐳 Submitting a new build...[/bold blue]")
|
|
116
|
+
if image.registry and image.registry != _BASE_REGISTRY:
|
|
117
|
+
target_image = f"{image.registry}/{image_name}"
|
|
118
|
+
else:
|
|
119
|
+
# Use the default system registry in the backend.
|
|
120
|
+
target_image = image_name
|
|
121
|
+
|
|
122
|
+
from flyte._initialize import get_init_config
|
|
123
|
+
cfg = get_init_config()
|
|
113
124
|
run = cast(
|
|
114
125
|
Run,
|
|
115
|
-
await flyte.with_runcontext(project=
|
|
116
|
-
entity, spec=spec, context=context, target_image=
|
|
126
|
+
await flyte.with_runcontext(project=cfg.project, domain=cfg.domain).run.aio(
|
|
127
|
+
entity, spec=spec, context=context, target_image=target_image
|
|
117
128
|
),
|
|
118
129
|
)
|
|
119
130
|
logger.warning(f"⏳ Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
|
|
@@ -126,7 +137,7 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
126
137
|
if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
|
|
127
138
|
logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
|
|
128
139
|
else:
|
|
129
|
-
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at
|
|
140
|
+
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {run.url}")
|
|
130
141
|
|
|
131
142
|
outputs = await run_details.outputs()
|
|
132
143
|
return _get_fully_qualified_image_name(outputs)
|
|
@@ -180,12 +191,17 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
|
180
191
|
|
|
181
192
|
|
|
182
193
|
def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2.ImageSpec":
|
|
183
|
-
from
|
|
194
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
|
|
195
|
+
|
|
196
|
+
if image.dockerfile is not None:
|
|
197
|
+
raise flyte.errors.ImageBuildError(
|
|
198
|
+
"Custom Dockerfile is not supported with remote image builder.You can use local image builder instead."
|
|
199
|
+
)
|
|
184
200
|
|
|
185
201
|
layers = []
|
|
186
202
|
for layer in image._layers:
|
|
187
203
|
secret_mounts = None
|
|
188
|
-
pip_options =
|
|
204
|
+
pip_options = image_definition_pb2.PipOptions()
|
|
189
205
|
|
|
190
206
|
if isinstance(layer, PipOption):
|
|
191
207
|
pip_options = image_definition_pb2.PipOptions(
|
|
@@ -251,12 +267,20 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
251
267
|
if "tool.uv.index" in line:
|
|
252
268
|
raise ValueError("External sources are not supported in pyproject.toml")
|
|
253
269
|
|
|
254
|
-
if layer.
|
|
270
|
+
if layer.project_install_mode == "dependencies_only":
|
|
255
271
|
# Copy pyproject itself
|
|
256
272
|
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
273
|
+
if pip_options.extra_args:
|
|
274
|
+
if "--no-install-project" not in pip_options.extra_args:
|
|
275
|
+
pip_options.extra_args += " --no-install-project"
|
|
276
|
+
else:
|
|
277
|
+
pip_options.extra_args = " --no-install-project"
|
|
278
|
+
if "--no-sources" not in pip_options.extra_args:
|
|
279
|
+
pip_options.extra_args += " --no-sources"
|
|
257
280
|
else:
|
|
258
281
|
# Copy the entire project
|
|
259
|
-
|
|
282
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
283
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
|
|
260
284
|
|
|
261
285
|
uv_layer = image_definition_pb2.Layer(
|
|
262
286
|
uv_project=image_definition_pb2.UVProject(
|
|
@@ -267,6 +291,29 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
267
291
|
)
|
|
268
292
|
)
|
|
269
293
|
layers.append(uv_layer)
|
|
294
|
+
elif isinstance(layer, PoetryProject):
|
|
295
|
+
for line in layer.pyproject.read_text().splitlines():
|
|
296
|
+
if "tool.poetry.source" in line:
|
|
297
|
+
raise ValueError("External sources are not supported in pyproject.toml")
|
|
298
|
+
extra_args = layer.extra_args or ""
|
|
299
|
+
if layer.project_install_mode == "dependencies_only":
|
|
300
|
+
# Copy pyproject itself
|
|
301
|
+
if "--no-root" not in extra_args:
|
|
302
|
+
extra_args += " --no-root"
|
|
303
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
304
|
+
else:
|
|
305
|
+
# Copy the entire project
|
|
306
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
|
|
307
|
+
|
|
308
|
+
poetry_layer = image_definition_pb2.Layer(
|
|
309
|
+
poetry_project=image_definition_pb2.PoetryProject(
|
|
310
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
311
|
+
poetry_lock=str(copy_files_to_context(layer.poetry_lock, context_path).relative_to(context_path)),
|
|
312
|
+
extra_args=extra_args,
|
|
313
|
+
secret_mounts=secret_mounts,
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
layers.append(poetry_layer)
|
|
270
317
|
elif isinstance(layer, Commands):
|
|
271
318
|
commands_layer = image_definition_pb2.Layer(
|
|
272
319
|
commands=image_definition_pb2.Commands(
|
|
@@ -325,4 +372,9 @@ def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]
|
|
|
325
372
|
else:
|
|
326
373
|
raise ValueError(f"Unsupported secret_mount type: {type(secret_mount)}")
|
|
327
374
|
|
|
375
|
+
image_registry_secret = image._image_registry_secret
|
|
376
|
+
if image_registry_secret:
|
|
377
|
+
secrets.append(
|
|
378
|
+
Secret(key=image_registry_secret.key, group=image_registry_secret.group, mount=DEFAULT_SECRET_DIR)
|
|
379
|
+
)
|
|
328
380
|
return secrets
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import shutil
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import List, Optional
|
|
3
4
|
|
|
5
|
+
from flyte._image import DockerIgnore, Image
|
|
6
|
+
from flyte._logging import logger
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
|
|
9
|
+
def copy_files_to_context(src: Path, context_path: Path, ignore_patterns: list[str] = []) -> Path:
|
|
6
10
|
"""
|
|
7
11
|
This helper function ensures that absolute paths that users specify are converted correctly to a path in the
|
|
8
12
|
context directory. Doing this prevents collisions while ensuring files are available in the context.
|
|
@@ -23,8 +27,52 @@ def copy_files_to_context(src: Path, context_path: Path) -> Path:
|
|
|
23
27
|
dst_path = context_path / src
|
|
24
28
|
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
25
29
|
if src.is_dir():
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
default_ignore_patterns = [".idea", ".venv"]
|
|
31
|
+
ignore_patterns = list(set(ignore_patterns + default_ignore_patterns))
|
|
32
|
+
shutil.copytree(src, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*ignore_patterns))
|
|
28
33
|
else:
|
|
29
34
|
shutil.copy(src, dst_path)
|
|
30
35
|
return dst_path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_and_list_dockerignore(image: Image) -> List[str]:
|
|
39
|
+
"""
|
|
40
|
+
Get and parse dockerignore patterns from .dockerignore file.
|
|
41
|
+
|
|
42
|
+
This function first looks for a DockerIgnore layer in the image's layers. If found, it uses
|
|
43
|
+
the path specified in that layer. If no DockerIgnore layer is found, it falls back to looking
|
|
44
|
+
for a .dockerignore file in the root_path directory.
|
|
45
|
+
|
|
46
|
+
:param image: The Image object
|
|
47
|
+
"""
|
|
48
|
+
from flyte._initialize import _get_init_config
|
|
49
|
+
|
|
50
|
+
# Look for DockerIgnore layer in the image layers
|
|
51
|
+
dockerignore_path: Optional[Path] = None
|
|
52
|
+
patterns: List[str] = []
|
|
53
|
+
|
|
54
|
+
for layer in image._layers:
|
|
55
|
+
if isinstance(layer, DockerIgnore) and layer.path.strip():
|
|
56
|
+
dockerignore_path = Path(layer.path)
|
|
57
|
+
# If DockerIgnore layer not specified, set dockerignore_path under root_path
|
|
58
|
+
init_config = _get_init_config()
|
|
59
|
+
root_path = init_config.root_dir if init_config else None
|
|
60
|
+
if not dockerignore_path and root_path:
|
|
61
|
+
dockerignore_path = Path(root_path) / ".dockerignore"
|
|
62
|
+
# Return empty list if no .dockerignore file found
|
|
63
|
+
if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
|
|
64
|
+
logger.info(f".dockerignore file not found at path: {dockerignore_path}")
|
|
65
|
+
return patterns
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
with open(dockerignore_path, "r", encoding="utf-8") as f:
|
|
69
|
+
for line in f:
|
|
70
|
+
stripped_line = line.strip()
|
|
71
|
+
# Skip empty lines, whitespace-only lines, and comments
|
|
72
|
+
if not stripped_line or stripped_line.startswith("#"):
|
|
73
|
+
continue
|
|
74
|
+
patterns.append(stripped_line)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
|
|
77
|
+
return []
|
|
78
|
+
return patterns
|