flyte 0.2.0b1__py3-none-any.whl → 2.0.0b46__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 +83 -30
- flyte/_bin/connect.py +61 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +87 -19
- flyte/_bin/serve.py +351 -0
- flyte/_build.py +3 -2
- flyte/_cache/cache.py +6 -5
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +31 -5
- flyte/_code_bundle/_packaging.py +42 -11
- flyte/_code_bundle/_utils.py +57 -34
- flyte/_code_bundle/bundle.py +130 -27
- flyte/_constants.py +1 -0
- flyte/_context.py +21 -5
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +37 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +315 -0
- flyte/_deploy.py +396 -75
- flyte/_deployer.py +109 -0
- flyte/_environment.py +94 -11
- flyte/_excepthook.py +37 -0
- flyte/_group.py +2 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +544 -231
- flyte/_initialize.py +456 -316
- flyte/_interface.py +40 -5
- flyte/_internal/controllers/__init__.py +22 -8
- flyte/_internal/controllers/_local_controller.py +159 -35
- flyte/_internal/controllers/_trace.py +18 -10
- flyte/_internal/controllers/remote/__init__.py +38 -9
- flyte/_internal/controllers/remote/_action.py +82 -12
- flyte/_internal/controllers/remote/_client.py +6 -2
- flyte/_internal/controllers/remote/_controller.py +290 -64
- flyte/_internal/controllers/remote/_core.py +155 -95
- flyte/_internal/controllers/remote/_informer.py +40 -20
- flyte/_internal/controllers/remote/_service_protocol.py +2 -2
- flyte/_internal/imagebuild/__init__.py +2 -10
- flyte/_internal/imagebuild/docker_builder.py +391 -84
- flyte/_internal/imagebuild/image_builder.py +111 -55
- flyte/_internal/imagebuild/remote_builder.py +409 -0
- flyte/_internal/imagebuild/utils.py +79 -0
- flyte/_internal/resolvers/_app_env_module.py +92 -0
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/app_env.py +26 -0
- flyte/_internal/resolvers/common.py +8 -1
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +319 -36
- flyte/_internal/runtime/entrypoints.py +106 -18
- flyte/_internal/runtime/io.py +71 -23
- flyte/_internal/runtime/resources_serde.py +21 -7
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +196 -0
- flyte/_internal/runtime/task_serde.py +239 -66
- flyte/_internal/runtime/taskrunner.py +48 -8
- flyte/_internal/runtime/trigger_serde.py +162 -0
- flyte/_internal/runtime/types_serde.py +7 -16
- flyte/_keyring/file.py +115 -0
- flyte/_link.py +30 -0
- flyte/_logging.py +241 -42
- flyte/_map.py +312 -0
- flyte/_metrics.py +59 -0
- flyte/_module.py +74 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +296 -33
- flyte/_retry.py +1 -7
- flyte/_reusable_environment.py +72 -7
- flyte/_run.py +462 -132
- flyte/_secret.py +47 -11
- flyte/_serve.py +333 -0
- flyte/_task.py +245 -56
- flyte/_task_environment.py +219 -97
- flyte/_task_plugins.py +47 -0
- flyte/_tools.py +8 -8
- flyte/_trace.py +15 -24
- flyte/_trigger.py +1027 -0
- flyte/_utils/__init__.py +12 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +5 -4
- flyte/_utils/description_parser.py +19 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/helpers.py +45 -19
- flyte/_utils/module_loader.py +123 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +8 -1
- flyte/_version.py +16 -3
- flyte/app/__init__.py +27 -0
- flyte/app/_app_environment.py +362 -0
- flyte/app/_connector_environment.py +40 -0
- flyte/app/_deploy.py +130 -0
- flyte/app/_parameter.py +343 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +383 -0
- flyte/app/_types.py +113 -0
- flyte/app/extras/__init__.py +9 -0
- flyte/app/extras/_auth_middleware.py +217 -0
- flyte/app/extras/_fastapi.py +93 -0
- flyte/app/extras/_model_loader/__init__.py +3 -0
- flyte/app/extras/_model_loader/config.py +7 -0
- flyte/app/extras/_model_loader/loader.py +288 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +493 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +401 -0
- flyte/cli/_gen.py +316 -0
- flyte/cli/_get.py +446 -0
- flyte/cli/_option.py +33 -0
- flyte/{_cli → cli}/_params.py +57 -17
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_prefetch.py +292 -0
- flyte/cli/_run.py +690 -0
- flyte/cli/_serve.py +338 -0
- flyte/cli/_update.py +86 -0
- flyte/cli/_user.py +20 -0
- flyte/cli/main.py +246 -0
- flyte/config/__init__.py +2 -167
- flyte/config/_config.py +215 -163
- flyte/config/_internal.py +10 -1
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +330 -0
- flyte/connectors/_server.py +194 -0
- flyte/connectors/utils.py +159 -0
- flyte/errors.py +134 -2
- flyte/extend.py +24 -0
- flyte/extras/_container.py +69 -56
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +279 -0
- flyte/io/__init__.py +8 -1
- flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
- flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
- flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +587 -141
- flyte/io/_hashing_io.py +342 -0
- flyte/io/extend.py +7 -0
- flyte/models.py +635 -0
- flyte/prefetch/__init__.py +22 -0
- flyte/prefetch/_hf_model.py +563 -0
- flyte/remote/__init__.py +14 -3
- flyte/remote/_action.py +879 -0
- flyte/remote/_app.py +346 -0
- flyte/remote/_auth_metadata.py +42 -0
- flyte/remote/_client/_protocols.py +62 -4
- flyte/remote/_client/auth/_auth_utils.py +19 -0
- flyte/remote/_client/auth/_authenticators/base.py +8 -2
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/factory.py +4 -0
- flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
- flyte/remote/_client/auth/_channel.py +47 -18
- flyte/remote/_client/auth/_client_config.py +5 -3
- flyte/remote/_client/auth/_keyring.py +15 -2
- flyte/remote/_client/auth/_token_client.py +3 -3
- flyte/remote/_client/controlplane.py +206 -18
- flyte/remote/_common.py +66 -0
- flyte/remote/_data.py +107 -22
- flyte/remote/_logs.py +116 -33
- flyte/remote/_project.py +21 -19
- flyte/remote/_run.py +164 -631
- flyte/remote/_secret.py +72 -29
- flyte/remote/_task.py +387 -46
- flyte/remote/_trigger.py +368 -0
- flyte/remote/_user.py +43 -0
- flyte/report/_report.py +10 -6
- flyte/storage/__init__.py +13 -1
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +289 -0
- flyte/storage/_storage.py +268 -59
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +414 -0
- flyte/types/__init__.py +39 -0
- flyte/types/_interface.py +22 -7
- flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +226 -126
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b46.data/scripts/debug.py +38 -0
- flyte-2.0.0b46.data/scripts/runtime.py +194 -0
- flyte-2.0.0b46.dist-info/METADATA +352 -0
- flyte-2.0.0b46.dist-info/RECORD +221 -0
- flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
- flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
- flyte/_api_commons.py +0 -3
- flyte/_cli/_common.py +0 -299
- flyte/_cli/_create.py +0 -42
- flyte/_cli/_delete.py +0 -23
- flyte/_cli/_deploy.py +0 -140
- flyte/_cli/_get.py +0 -235
- flyte/_cli/_run.py +0 -174
- flyte/_cli/main.py +0 -98
- flyte/_datastructures.py +0 -342
- flyte/_internal/controllers/pbhash.py +0 -39
- 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 -71
- flyte/_protos/common/identifier_pb2.pyi +0 -82
- 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 -69
- 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/logs/dataplane/payload_pb2.py +0 -96
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- 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/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 -106
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -128
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
- 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 -133
- flyte/_protos/workflow/run_service_pb2.pyi +0 -175
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
- flyte/_protos/workflow/state_service_pb2.py +0 -58
- flyte/_protos/workflow/state_service_pb2.pyi +0 -71
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -72
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -44
- flyte/_protos/workflow/task_service_pb2.pyi +0 -31
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
- flyte/io/_dataframe.py +0 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte/remote/_console.py +0 -18
- flyte-0.2.0b1.dist-info/METADATA +0 -179
- flyte-0.2.0b1.dist-info/RECORD +0 -204
- flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
- /flyte/{_cli → _debug}/__init__.py +0 -0
- /flyte/{_protos → _keyring}/__init__.py +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
|
@@ -1,87 +1,149 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
2
3
|
import shutil
|
|
3
4
|
import subprocess
|
|
4
5
|
import tempfile
|
|
5
|
-
|
|
6
|
+
import typing
|
|
7
|
+
from pathlib import Path, PurePath
|
|
6
8
|
from string import Template
|
|
7
|
-
from typing import ClassVar, Protocol, cast
|
|
9
|
+
from typing import ClassVar, Optional, Protocol, cast
|
|
8
10
|
|
|
9
11
|
import aiofiles
|
|
10
12
|
import click
|
|
11
13
|
|
|
14
|
+
from flyte import Secret
|
|
12
15
|
from flyte._image import (
|
|
13
16
|
AptPackages,
|
|
14
17
|
Commands,
|
|
15
18
|
CopyConfig,
|
|
19
|
+
DockerIgnore,
|
|
16
20
|
Env,
|
|
17
21
|
Image,
|
|
18
22
|
Layer,
|
|
23
|
+
PipOption,
|
|
19
24
|
PipPackages,
|
|
25
|
+
PoetryProject,
|
|
26
|
+
PythonWheels,
|
|
20
27
|
Requirements,
|
|
21
28
|
UVProject,
|
|
29
|
+
UVScript,
|
|
22
30
|
WorkDir,
|
|
23
31
|
_DockerLines,
|
|
32
|
+
_ensure_tuple,
|
|
24
33
|
)
|
|
34
|
+
from flyte._internal.imagebuild.image_builder import (
|
|
35
|
+
DockerAPIImageChecker,
|
|
36
|
+
ImageBuilder,
|
|
37
|
+
ImageChecker,
|
|
38
|
+
LocalDockerCommandImageChecker,
|
|
39
|
+
LocalPodmanCommandImageChecker,
|
|
40
|
+
)
|
|
41
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
|
|
25
42
|
from flyte._logging import logger
|
|
26
43
|
|
|
27
44
|
_F_IMG_ID = "_F_IMG_ID"
|
|
45
|
+
FLYTE_DOCKER_BUILDER_CACHE_FROM = "FLYTE_DOCKER_BUILDER_CACHE_FROM"
|
|
46
|
+
FLYTE_DOCKER_BUILDER_CACHE_TO = "FLYTE_DOCKER_BUILDER_CACHE_TO"
|
|
47
|
+
|
|
48
|
+
UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
|
|
49
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
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
|
|
54
|
+
""")
|
|
28
55
|
|
|
29
56
|
UV_LOCK_INSTALL_TEMPLATE = Template("""\
|
|
30
|
-
WORKDIR /root
|
|
31
57
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
88
|
""")
|
|
42
89
|
|
|
43
90
|
UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
44
91
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
$REQUIREMENTS_MOUNT \
|
|
93
|
+
$SECRET_MOUNT \
|
|
94
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
95
|
+
""")
|
|
96
|
+
|
|
97
|
+
UV_WHEEL_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
98
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=wheel \
|
|
99
|
+
--mount=source=/dist,target=/dist,type=bind \
|
|
100
|
+
$SECRET_MOUNT \
|
|
101
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
47
102
|
""")
|
|
48
103
|
|
|
49
104
|
APT_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
50
105
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
|
|
51
|
-
|
|
52
|
-
|
|
106
|
+
$SECRET_MOUNT \
|
|
107
|
+
apt-get update && apt-get install -y --no-install-recommends \
|
|
108
|
+
$APT_PACKAGES
|
|
53
109
|
""")
|
|
54
110
|
|
|
55
111
|
UV_PYTHON_INSTALL_COMMAND = Template("""\
|
|
56
112
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
57
|
-
|
|
113
|
+
$SECRET_MOUNT \
|
|
114
|
+
uv pip install $PIP_INSTALL_ARGS
|
|
58
115
|
""")
|
|
59
116
|
|
|
60
117
|
# uv pip install --python /root/env/bin/python
|
|
61
118
|
# new template
|
|
62
119
|
DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
|
|
63
|
-
#syntax=docker/dockerfile:1.
|
|
64
|
-
FROM ghcr.io/astral-sh/uv:0.
|
|
120
|
+
# syntax=docker/dockerfile:1.10
|
|
121
|
+
FROM ghcr.io/astral-sh/uv:0.8.13 AS uv
|
|
65
122
|
FROM $BASE_IMAGE
|
|
66
123
|
|
|
124
|
+
|
|
67
125
|
USER root
|
|
68
126
|
|
|
127
|
+
|
|
69
128
|
# Copy in uv so that later commands don't have to mount it in
|
|
70
129
|
COPY --from=uv /uv /usr/bin/uv
|
|
71
130
|
|
|
131
|
+
|
|
72
132
|
# Configure default envs
|
|
73
133
|
ENV UV_COMPILE_BYTECODE=1 \
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
UV_LINK_MODE=copy \
|
|
135
|
+
VIRTUALENV=/opt/venv \
|
|
136
|
+
UV_PYTHON=/opt/venv/bin/python \
|
|
137
|
+
PATH="/opt/venv/bin:$$PATH"
|
|
138
|
+
|
|
78
139
|
|
|
79
140
|
# Create a virtualenv with the user specified python version
|
|
80
|
-
RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION
|
|
141
|
+
RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION && uv run --python=$$UV_PYTHON python -m compileall $$VIRTUALENV
|
|
142
|
+
|
|
81
143
|
|
|
82
144
|
# Adds nvidia just in case it exists
|
|
83
145
|
ENV PATH="$$PATH:/usr/local/nvidia/bin:/usr/local/cuda/bin" \
|
|
84
|
-
|
|
146
|
+
LD_LIBRARY_PATH="/usr/local/nvidia/lib64"
|
|
85
147
|
""")
|
|
86
148
|
|
|
87
149
|
# This gets added on to the end of the dockerfile
|
|
@@ -100,36 +162,70 @@ class Handler(Protocol):
|
|
|
100
162
|
class PipAndRequirementsHandler:
|
|
101
163
|
@staticmethod
|
|
102
164
|
async def handle(layer: PipPackages, context_path: Path, dockerfile: str) -> str:
|
|
165
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
166
|
+
|
|
167
|
+
# Set pip_install_args based on the layer type - either a requirements file or a list of packages
|
|
103
168
|
if isinstance(layer, Requirements):
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
169
|
+
if not layer.file.exists():
|
|
170
|
+
raise FileNotFoundError(f"Requirements file {layer.file} does not exist")
|
|
171
|
+
if not layer.file.is_file():
|
|
172
|
+
raise ValueError(f"Requirements file {layer.file} is not a file")
|
|
173
|
+
|
|
174
|
+
# Copy the requirements file to the context path
|
|
175
|
+
requirements_path = copy_files_to_context(layer.file, context_path)
|
|
176
|
+
rel_path = str(requirements_path.relative_to(context_path))
|
|
177
|
+
pip_install_args = layer.get_pip_install_args()
|
|
178
|
+
pip_install_args.extend(["--requirement", "requirements.txt"])
|
|
179
|
+
mount = f"--mount=type=bind,target=requirements.txt,src={rel_path}"
|
|
109
180
|
else:
|
|
181
|
+
mount = ""
|
|
110
182
|
requirements = list(layer.packages) if layer.packages else []
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
183
|
+
reqs = " ".join(requirements)
|
|
184
|
+
pip_install_args = layer.get_pip_install_args()
|
|
185
|
+
pip_install_args.append(reqs)
|
|
186
|
+
|
|
187
|
+
delta = UV_PACKAGE_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
188
|
+
SECRET_MOUNT=secret_mounts,
|
|
189
|
+
REQUIREMENTS_MOUNT=mount,
|
|
190
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args),
|
|
191
|
+
)
|
|
115
192
|
|
|
116
|
-
|
|
117
|
-
if layer.index_url:
|
|
118
|
-
pip_install_args.append(f"--index-url {layer.index_url}")
|
|
193
|
+
dockerfile += delta
|
|
119
194
|
|
|
120
|
-
|
|
121
|
-
pip_install_args.extend([f"--extra-index-url {url}" for url in layer.extra_index_urls])
|
|
195
|
+
return dockerfile
|
|
122
196
|
|
|
123
|
-
if layer.pre:
|
|
124
|
-
pip_install_args.append("--pre")
|
|
125
197
|
|
|
126
|
-
|
|
127
|
-
|
|
198
|
+
class PythonWheelHandler:
|
|
199
|
+
@staticmethod
|
|
200
|
+
async def handle(layer: PythonWheels, context_path: Path, dockerfile: str) -> str:
|
|
201
|
+
shutil.copytree(layer.wheel_dir, context_path / "dist", dirs_exist_ok=True)
|
|
202
|
+
pip_install_args = layer.get_pip_install_args()
|
|
203
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
204
|
+
|
|
205
|
+
# First install: Install the wheel without dependencies using --no-deps
|
|
206
|
+
pip_install_args_no_deps = [
|
|
207
|
+
*pip_install_args,
|
|
208
|
+
*[
|
|
209
|
+
"--find-links",
|
|
210
|
+
"/dist",
|
|
211
|
+
"--no-deps",
|
|
212
|
+
"--no-index",
|
|
213
|
+
"--reinstall",
|
|
214
|
+
layer.package_name,
|
|
215
|
+
],
|
|
216
|
+
]
|
|
128
217
|
|
|
129
|
-
|
|
218
|
+
delta1 = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
219
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args_no_deps), SECRET_MOUNT=secret_mounts
|
|
220
|
+
)
|
|
221
|
+
dockerfile += delta1
|
|
130
222
|
|
|
131
|
-
|
|
132
|
-
|
|
223
|
+
# Second install: Install dependencies from PyPI
|
|
224
|
+
pip_install_args_deps = [*pip_install_args, layer.package_name]
|
|
225
|
+
delta2 = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
226
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args_deps), SECRET_MOUNT=secret_mounts
|
|
227
|
+
)
|
|
228
|
+
dockerfile += delta2
|
|
133
229
|
|
|
134
230
|
return dockerfile
|
|
135
231
|
|
|
@@ -156,9 +252,10 @@ class EnvHandler:
|
|
|
156
252
|
|
|
157
253
|
class AptPackagesHandler:
|
|
158
254
|
@staticmethod
|
|
159
|
-
async def handle(layer: AptPackages,
|
|
255
|
+
async def handle(layer: AptPackages, _: Path, dockerfile: str) -> str:
|
|
160
256
|
packages = layer.packages
|
|
161
|
-
|
|
257
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
258
|
+
delta = APT_INSTALL_COMMAND_TEMPLATE.substitute(APT_PACKAGES=" ".join(packages), SECRET_MOUNT=secret_mounts)
|
|
162
259
|
dockerfile += delta
|
|
163
260
|
|
|
164
261
|
return dockerfile
|
|
@@ -166,64 +263,227 @@ class AptPackagesHandler:
|
|
|
166
263
|
|
|
167
264
|
class UVProjectHandler:
|
|
168
265
|
@staticmethod
|
|
169
|
-
async def handle(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
266
|
+
async def handle(
|
|
267
|
+
layer: UVProject, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
268
|
+
) -> str:
|
|
269
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
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"
|
|
276
|
+
# Only Copy pyproject.yaml and uv.lock.
|
|
277
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
278
|
+
uvlock_dst = copy_files_to_context(layer.uvlock, context_path)
|
|
279
|
+
delta = UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
|
|
280
|
+
UV_LOCK_PATH=uvlock_dst.relative_to(context_path),
|
|
281
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
282
|
+
PIP_INSTALL_ARGS=pip_install_args,
|
|
283
|
+
SECRET_MOUNT=secret_mounts,
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
# Copy the entire project.
|
|
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():
|
|
293
|
+
shutil.copy(layer.uvlock, pyproject_dst)
|
|
294
|
+
if not pyproject_context_path.exists():
|
|
295
|
+
shutil.copy(layer.pyproject, pyproject_dst)
|
|
296
|
+
|
|
297
|
+
delta = UV_LOCK_INSTALL_TEMPLATE.substitute(
|
|
298
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
299
|
+
PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
|
|
300
|
+
SECRET_MOUNT=secret_mounts,
|
|
301
|
+
)
|
|
302
|
+
|
|
179
303
|
dockerfile += delta
|
|
304
|
+
return dockerfile
|
|
180
305
|
|
|
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
|
|
181
344
|
return dockerfile
|
|
182
345
|
|
|
183
346
|
|
|
347
|
+
class DockerIgnoreHandler:
|
|
348
|
+
@staticmethod
|
|
349
|
+
async def handle(layer: DockerIgnore, context_path: Path, _: str):
|
|
350
|
+
shutil.copy(layer.path, context_path)
|
|
351
|
+
|
|
352
|
+
|
|
184
353
|
class CopyConfigHandler:
|
|
185
354
|
@staticmethod
|
|
186
|
-
async def handle(
|
|
355
|
+
async def handle(
|
|
356
|
+
layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
357
|
+
) -> str:
|
|
187
358
|
# Copy the source config file or directory to the context path
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
359
|
+
if layer.src.is_absolute() or ".." in str(layer.src):
|
|
360
|
+
rel_path = PurePath(*layer.src.parts[1:])
|
|
361
|
+
dst_path = context_path / "_flyte_abs_context" / rel_path
|
|
362
|
+
else:
|
|
363
|
+
dst_path = context_path / layer.src
|
|
364
|
+
|
|
365
|
+
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
366
|
+
abs_path = layer.src.absolute()
|
|
367
|
+
|
|
368
|
+
if layer.src.is_file():
|
|
192
369
|
# Copy the file
|
|
193
|
-
shutil.copy(abs_path,
|
|
194
|
-
elif layer.
|
|
370
|
+
shutil.copy(abs_path, dst_path)
|
|
371
|
+
elif layer.src.is_dir():
|
|
195
372
|
# Copy the entire directory
|
|
196
|
-
shutil.copytree(
|
|
373
|
+
shutil.copytree(
|
|
374
|
+
abs_path, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*docker_ignore_patterns)
|
|
375
|
+
)
|
|
197
376
|
else:
|
|
198
|
-
|
|
377
|
+
logger.error(f"Source path not exists: {layer.src}")
|
|
378
|
+
return dockerfile
|
|
199
379
|
|
|
200
380
|
# Add a copy command to the dockerfile
|
|
201
|
-
dockerfile += f"\nCOPY {
|
|
202
|
-
|
|
381
|
+
dockerfile += f"\nCOPY {dst_path.relative_to(context_path)} {layer.dst}\n"
|
|
203
382
|
return dockerfile
|
|
204
383
|
|
|
205
384
|
|
|
206
385
|
class CommandsHandler:
|
|
207
386
|
@staticmethod
|
|
208
|
-
async def handle(layer: Commands,
|
|
387
|
+
async def handle(layer: Commands, _: Path, dockerfile: str) -> str:
|
|
209
388
|
# Append raw commands to the dockerfile
|
|
389
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
210
390
|
for command in layer.commands:
|
|
211
|
-
dockerfile += f"\nRUN {command}\n"
|
|
391
|
+
dockerfile += f"\nRUN {secret_mounts} {command}\n"
|
|
212
392
|
|
|
213
393
|
return dockerfile
|
|
214
394
|
|
|
215
395
|
|
|
216
396
|
class WorkDirHandler:
|
|
217
397
|
@staticmethod
|
|
218
|
-
async def handle(layer: WorkDir,
|
|
398
|
+
async def handle(layer: WorkDir, _: Path, dockerfile: str) -> str:
|
|
219
399
|
# cd to the workdir
|
|
220
400
|
dockerfile += f"\nWORKDIR {layer.workdir}\n"
|
|
221
401
|
|
|
222
402
|
return dockerfile
|
|
223
403
|
|
|
224
404
|
|
|
225
|
-
|
|
405
|
+
def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
|
|
406
|
+
commands = []
|
|
407
|
+
|
|
408
|
+
def _get_secret_command(secret: str | Secret) -> typing.List[str]:
|
|
409
|
+
if isinstance(secret, str):
|
|
410
|
+
secret = Secret(key=secret)
|
|
411
|
+
secret_id = hash(secret)
|
|
412
|
+
secret_env_key = "_".join([k.upper() for k in filter(None, (secret.group, secret.key))])
|
|
413
|
+
if os.getenv(secret_env_key):
|
|
414
|
+
return ["--secret", f"id={secret_id},env={secret_env_key}"]
|
|
415
|
+
secret_file_name = "_".join(list(filter(None, (secret.group, secret.key))))
|
|
416
|
+
secret_file_path = f"/etc/secrets/{secret_file_name}"
|
|
417
|
+
if not os.path.exists(secret_file_path):
|
|
418
|
+
raise FileNotFoundError(f"Secret not found in Env Var {secret_env_key} or file {secret_file_path}")
|
|
419
|
+
return ["--secret", f"id={secret_id},src={secret_file_path}"]
|
|
420
|
+
|
|
421
|
+
for layer in layers:
|
|
422
|
+
if isinstance(layer, (PipOption, AptPackages, Commands)):
|
|
423
|
+
if layer.secret_mounts:
|
|
424
|
+
for secret_mount in layer.secret_mounts:
|
|
425
|
+
commands.extend(_get_secret_command(secret_mount))
|
|
426
|
+
return commands
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) -> str:
|
|
430
|
+
if secrets is None:
|
|
431
|
+
return ""
|
|
432
|
+
secret_mounts_layer = ""
|
|
433
|
+
for s in secrets:
|
|
434
|
+
secret = Secret(key=s) if isinstance(s, str) else s
|
|
435
|
+
secret_id = hash(secret)
|
|
436
|
+
if secret.mount:
|
|
437
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},target={secret.mount}"
|
|
438
|
+
elif secret.as_env_var:
|
|
439
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},env={secret.as_env_var}"
|
|
440
|
+
else:
|
|
441
|
+
secret_default_env_key = "_".join(list(filter(None, (secret.group, secret.key))))
|
|
442
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},env={secret_default_env_key}"
|
|
443
|
+
|
|
444
|
+
return secret_mounts_layer
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
async def _process_layer(
|
|
448
|
+
layer: Layer, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
|
|
449
|
+
) -> str:
|
|
226
450
|
match layer:
|
|
451
|
+
case PythonWheels():
|
|
452
|
+
# Handle Python wheels
|
|
453
|
+
dockerfile = await PythonWheelHandler.handle(layer, context_path, dockerfile)
|
|
454
|
+
|
|
455
|
+
case UVScript():
|
|
456
|
+
# Handle UV script
|
|
457
|
+
from flyte._utils import parse_uv_script_file
|
|
458
|
+
|
|
459
|
+
header = parse_uv_script_file(layer.script)
|
|
460
|
+
if header.dependencies:
|
|
461
|
+
pip = PipPackages(
|
|
462
|
+
packages=_ensure_tuple(header.dependencies) if header.dependencies else None,
|
|
463
|
+
secret_mounts=layer.secret_mounts,
|
|
464
|
+
index_url=layer.index_url,
|
|
465
|
+
extra_args=layer.extra_args,
|
|
466
|
+
pre=layer.pre,
|
|
467
|
+
extra_index_urls=layer.extra_index_urls,
|
|
468
|
+
)
|
|
469
|
+
dockerfile = await PipAndRequirementsHandler.handle(pip, context_path, dockerfile)
|
|
470
|
+
if header.pyprojects:
|
|
471
|
+
# To get the version of the project.
|
|
472
|
+
dockerfile = await AptPackagesHandler.handle(AptPackages(packages=("git",)), context_path, dockerfile)
|
|
473
|
+
|
|
474
|
+
for project_path in header.pyprojects:
|
|
475
|
+
uv_project = UVProject(
|
|
476
|
+
pyproject=Path(project_path) / "pyproject.toml",
|
|
477
|
+
uvlock=Path(project_path) / "uv.lock",
|
|
478
|
+
project_install_mode="install_project",
|
|
479
|
+
secret_mounts=layer.secret_mounts,
|
|
480
|
+
pre=layer.pre,
|
|
481
|
+
extra_args=layer.extra_args,
|
|
482
|
+
)
|
|
483
|
+
dockerfile = await UVProjectHandler.handle(
|
|
484
|
+
uv_project, context_path, dockerfile, docker_ignore_patterns
|
|
485
|
+
)
|
|
486
|
+
|
|
227
487
|
case Requirements() | PipPackages():
|
|
228
488
|
# Handle pip packages and requirements
|
|
229
489
|
dockerfile = await PipAndRequirementsHandler.handle(layer, context_path, dockerfile)
|
|
@@ -234,16 +494,28 @@ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> s
|
|
|
234
494
|
|
|
235
495
|
case UVProject():
|
|
236
496
|
# Handle UV project
|
|
237
|
-
dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile)
|
|
497
|
+
dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
498
|
+
|
|
499
|
+
case PoetryProject():
|
|
500
|
+
# Handle Poetry project
|
|
501
|
+
dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
502
|
+
|
|
503
|
+
case PoetryProject():
|
|
504
|
+
# Handle Poetry project
|
|
505
|
+
dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
238
506
|
|
|
239
507
|
case CopyConfig():
|
|
240
508
|
# Handle local files and folders
|
|
241
|
-
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile)
|
|
509
|
+
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
|
|
242
510
|
|
|
243
511
|
case Commands():
|
|
244
512
|
# Handle commands
|
|
245
513
|
dockerfile = await CommandsHandler.handle(layer, context_path, dockerfile)
|
|
246
514
|
|
|
515
|
+
case DockerIgnore():
|
|
516
|
+
# Handle dockerignore
|
|
517
|
+
await DockerIgnoreHandler.handle(layer, context_path, dockerfile)
|
|
518
|
+
|
|
247
519
|
case WorkDir():
|
|
248
520
|
# Handle workdir
|
|
249
521
|
dockerfile = await WorkDirHandler.handle(layer, context_path, dockerfile)
|
|
@@ -262,21 +534,25 @@ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> s
|
|
|
262
534
|
return dockerfile
|
|
263
535
|
|
|
264
536
|
|
|
265
|
-
class DockerImageBuilder:
|
|
537
|
+
class DockerImageBuilder(ImageBuilder):
|
|
266
538
|
"""Image builder using Docker and buildkit."""
|
|
267
539
|
|
|
268
540
|
builder_type: ClassVar = "docker"
|
|
269
541
|
_builder_name: ClassVar = "flytex"
|
|
270
542
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
raise ValueError("Image is a default image and should already be built")
|
|
543
|
+
def get_checkers(self) -> Optional[typing.List[typing.Type[ImageChecker]]]:
|
|
544
|
+
# Can get a public token for docker.io but ghcr requires a pat, so harder to get the manifest anonymously
|
|
545
|
+
return [LocalDockerCommandImageChecker, LocalPodmanCommandImageChecker, DockerAPIImageChecker]
|
|
275
546
|
|
|
547
|
+
async def build_image(self, image: Image, dry_run: bool = False) -> str:
|
|
276
548
|
if image.dockerfile:
|
|
277
549
|
# If a dockerfile is provided, use it directly
|
|
278
550
|
return await self._build_from_dockerfile(image, push=True)
|
|
279
551
|
|
|
552
|
+
if len(image._layers) == 0:
|
|
553
|
+
logger.warning("No layers to build, returning the image URI as is.")
|
|
554
|
+
return image.uri
|
|
555
|
+
|
|
280
556
|
return await self._build_image(
|
|
281
557
|
image,
|
|
282
558
|
push=True,
|
|
@@ -287,18 +563,30 @@ class DockerImageBuilder:
|
|
|
287
563
|
"""
|
|
288
564
|
Build the image from a provided Dockerfile.
|
|
289
565
|
"""
|
|
566
|
+
assert image.dockerfile # for mypy
|
|
567
|
+
await DockerImageBuilder._ensure_buildx_builder()
|
|
568
|
+
|
|
290
569
|
command = [
|
|
291
570
|
"docker",
|
|
571
|
+
"buildx",
|
|
292
572
|
"build",
|
|
573
|
+
"--builder",
|
|
574
|
+
DockerImageBuilder._builder_name,
|
|
575
|
+
"-f",
|
|
576
|
+
str(image.dockerfile),
|
|
293
577
|
"--tag",
|
|
294
578
|
f"{image.uri}",
|
|
295
579
|
"--platform",
|
|
296
580
|
",".join(image.platform),
|
|
297
|
-
|
|
581
|
+
str(image.dockerfile.parent.absolute()), # Use the parent directory of the Dockerfile as the context
|
|
298
582
|
]
|
|
299
583
|
|
|
300
584
|
if image.registry and push:
|
|
301
585
|
command.append("--push")
|
|
586
|
+
else:
|
|
587
|
+
command.append("--load")
|
|
588
|
+
|
|
589
|
+
command.extend(_get_secret_commands(layers=image._layers))
|
|
302
590
|
|
|
303
591
|
concat_command = " ".join(command)
|
|
304
592
|
logger.debug(f"Build command: {concat_command}")
|
|
@@ -353,6 +641,7 @@ class DockerImageBuilder:
|
|
|
353
641
|
- start from the base image
|
|
354
642
|
- use python to create a default venv and export variables
|
|
355
643
|
|
|
644
|
+
|
|
356
645
|
Then for the layers
|
|
357
646
|
- for each layer
|
|
358
647
|
- find the appropriate layer handler
|
|
@@ -374,8 +663,11 @@ class DockerImageBuilder:
|
|
|
374
663
|
PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
|
|
375
664
|
)
|
|
376
665
|
|
|
666
|
+
# Get .dockerignore file patterns first
|
|
667
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
668
|
+
|
|
377
669
|
for layer in image._layers:
|
|
378
|
-
dockerfile = await _process_layer(layer, tmp_path, dockerfile)
|
|
670
|
+
dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_patterns)
|
|
379
671
|
|
|
380
672
|
dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
|
|
381
673
|
|
|
@@ -393,11 +685,22 @@ class DockerImageBuilder:
|
|
|
393
685
|
f"{image.uri}",
|
|
394
686
|
"--platform",
|
|
395
687
|
",".join(image.platform),
|
|
396
|
-
"--push" if push else "--load",
|
|
397
688
|
]
|
|
398
689
|
|
|
690
|
+
cache_from = os.getenv(FLYTE_DOCKER_BUILDER_CACHE_FROM)
|
|
691
|
+
cache_to = os.getenv(FLYTE_DOCKER_BUILDER_CACHE_TO)
|
|
692
|
+
if cache_from and cache_to:
|
|
693
|
+
command[3:3] = [
|
|
694
|
+
f"--cache-from={cache_from}",
|
|
695
|
+
f"--cache-to={cache_to}",
|
|
696
|
+
]
|
|
697
|
+
|
|
399
698
|
if image.registry and push:
|
|
400
699
|
command.append("--push")
|
|
700
|
+
else:
|
|
701
|
+
command.append("--load")
|
|
702
|
+
|
|
703
|
+
command.extend(_get_secret_commands(layers=image._layers))
|
|
401
704
|
command.append(tmp_dir)
|
|
402
705
|
|
|
403
706
|
concat_command = " ".join(command)
|
|
@@ -411,6 +714,10 @@ class DockerImageBuilder:
|
|
|
411
714
|
else:
|
|
412
715
|
click.secho(f"Run command: {concat_command} ", fg="blue")
|
|
413
716
|
|
|
414
|
-
|
|
717
|
+
try:
|
|
718
|
+
await asyncio.to_thread(subprocess.run, command, check=True)
|
|
719
|
+
except subprocess.CalledProcessError as e:
|
|
720
|
+
logger.error(f"Failed to build image: {e}")
|
|
721
|
+
raise RuntimeError(f"Failed to build image: {e}")
|
|
415
722
|
|
|
416
723
|
return image.uri
|