flyte 2.0.0b13__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/debug.py +38 -0
- flyte/_bin/runtime.py +62 -8
- flyte/_cache/cache.py +4 -2
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +12 -4
- flyte/_code_bundle/_packaging.py +13 -9
- flyte/_code_bundle/_utils.py +18 -10
- flyte/_code_bundle/bundle.py +17 -9
- flyte/_constants.py +1 -0
- flyte/_context.py +4 -1
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +235 -61
- flyte/_environment.py +20 -6
- flyte/_excepthook.py +1 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +178 -81
- flyte/_initialize.py +132 -51
- flyte/_interface.py +39 -2
- flyte/_internal/controllers/__init__.py +4 -5
- flyte/_internal/controllers/_local_controller.py +70 -29
- flyte/_internal/controllers/_trace.py +1 -1
- flyte/_internal/controllers/remote/__init__.py +0 -2
- flyte/_internal/controllers/remote/_action.py +14 -16
- flyte/_internal/controllers/remote/_client.py +1 -1
- flyte/_internal/controllers/remote/_controller.py +68 -70
- flyte/_internal/controllers/remote/_core.py +127 -99
- flyte/_internal/controllers/remote/_informer.py +19 -10
- flyte/_internal/controllers/remote/_service_protocol.py +7 -7
- flyte/_internal/imagebuild/docker_builder.py +181 -69
- flyte/_internal/imagebuild/image_builder.py +0 -5
- flyte/_internal/imagebuild/remote_builder.py +155 -64
- flyte/_internal/imagebuild/utils.py +51 -2
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +110 -21
- flyte/_internal/runtime/entrypoints.py +27 -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 +34 -19
- flyte/_internal/runtime/taskrunner.py +22 -4
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +201 -39
- flyte/_map.py +111 -14
- flyte/_module.py +70 -0
- flyte/_pod.py +4 -3
- flyte/_resources.py +213 -31
- flyte/_run.py +110 -39
- flyte/_task.py +75 -16
- flyte/_task_environment.py +105 -29
- flyte/_task_plugins.py +4 -2
- flyte/_trace.py +5 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +2 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/coro_management.py +2 -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 +3 -6
- flyte/cli/_common.py +78 -7
- flyte/cli/_create.py +182 -4
- flyte/cli/_delete.py +23 -1
- flyte/cli/_deploy.py +63 -16
- flyte/cli/_get.py +79 -34
- flyte/cli/_params.py +26 -10
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +151 -26
- 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 +10 -6
- flyte/config/_internal.py +1 -0
- flyte/config/_reader.py +29 -8
- 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 +22 -2
- flyte/extend.py +8 -1
- flyte/extras/_container.py +6 -1
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +2 -0
- flyte/io/_dataframe/__init__.py +2 -0
- flyte/io/_dataframe/basic_dfs.py +17 -8
- flyte/io/_dataframe/dataframe.py +98 -132
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +582 -139
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +74 -15
- flyte/remote/__init__.py +6 -1
- flyte/remote/_action.py +34 -26
- flyte/remote/_client/_protocols.py +39 -4
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/pkce.py +1 -1
- 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 +6 -6
- flyte/remote/_logs.py +3 -3
- flyte/remote/_run.py +64 -8
- flyte/remote/_secret.py +26 -17
- flyte/remote/_task.py +75 -33
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/_report.py +1 -1
- flyte/storage/__init__.py +6 -1
- flyte/storage/_config.py +5 -1
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_storage.py +200 -103
- flyte/types/__init__.py +16 -0
- flyte/types/_interface.py +2 -2
- flyte/types/_pickle.py +35 -8
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +40 -70
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b30.data/scripts/debug.py +38 -0
- {flyte-2.0.0b13.data → flyte-2.0.0b30.data}/scripts/runtime.py +62 -8
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/METADATA +11 -3
- flyte-2.0.0b30.dist-info/RECORD +192 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +3 -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 -93
- flyte/_protos/common/identifier_pb2.pyi +0 -110
- 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 -59
- flyte/_protos/imagebuilder/definition_pb2.pyi +0 -140
- 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 -109
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -166
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -121
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -327
- 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 -79
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -81
- 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.0b13.dist-info/RECORD +0 -239
- /flyte/{_protos → _debug}/__init__.py +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import gzip
|
|
1
2
|
import os
|
|
2
3
|
import shutil
|
|
4
|
+
import tarfile
|
|
3
5
|
import tempfile
|
|
4
6
|
import typing
|
|
5
7
|
from datetime import datetime, timezone
|
|
@@ -7,19 +9,23 @@ from pathlib import Path
|
|
|
7
9
|
from typing import TYPE_CHECKING, Optional, Tuple, cast
|
|
8
10
|
from uuid import uuid4
|
|
9
11
|
|
|
10
|
-
import
|
|
12
|
+
import aiofiles
|
|
11
13
|
|
|
12
14
|
import flyte
|
|
13
15
|
import flyte.errors
|
|
14
16
|
from flyte import Image, remote
|
|
17
|
+
from flyte._code_bundle._utils import tar_strip_file_attributes
|
|
15
18
|
from flyte._image import (
|
|
19
|
+
_BASE_REGISTRY,
|
|
16
20
|
AptPackages,
|
|
17
21
|
Architecture,
|
|
18
22
|
Commands,
|
|
19
23
|
CopyConfig,
|
|
20
24
|
DockerIgnore,
|
|
21
25
|
Env,
|
|
26
|
+
PipOption,
|
|
22
27
|
PipPackages,
|
|
28
|
+
PoetryProject,
|
|
23
29
|
PythonWheels,
|
|
24
30
|
Requirements,
|
|
25
31
|
UVProject,
|
|
@@ -27,15 +33,16 @@ from flyte._image import (
|
|
|
27
33
|
WorkDir,
|
|
28
34
|
)
|
|
29
35
|
from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
|
|
30
|
-
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
|
|
37
|
+
from flyte._internal.runtime.task_serde import get_security_context
|
|
31
38
|
from flyte._logging import logger
|
|
39
|
+
from flyte._secret import Secret
|
|
32
40
|
from flyte.remote import ActionOutputs, Run
|
|
33
41
|
|
|
34
42
|
if TYPE_CHECKING:
|
|
35
|
-
from
|
|
43
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
|
|
36
44
|
|
|
37
45
|
IMAGE_TASK_NAME = "build-image"
|
|
38
|
-
OPTIMIZE_TASK_NAME = "optimize_task"
|
|
39
46
|
IMAGE_TASK_PROJECT = "system"
|
|
40
47
|
IMAGE_TASK_DOMAIN = "production"
|
|
41
48
|
|
|
@@ -63,10 +70,11 @@ class RemoteImageChecker(ImageChecker):
|
|
|
63
70
|
image_name = f"{repository.split('/')[-1]}:{tag}"
|
|
64
71
|
|
|
65
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
|
+
|
|
66
77
|
from flyte._initialize import _get_init_config
|
|
67
|
-
from flyte._protos.imagebuilder import definition_pb2 as image_definition__pb2
|
|
68
|
-
from flyte._protos.imagebuilder import payload_pb2 as image_payload__pb2
|
|
69
|
-
from flyte._protos.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
|
|
70
78
|
|
|
71
79
|
cfg = _get_init_config()
|
|
72
80
|
if cfg is None:
|
|
@@ -78,10 +86,10 @@ class RemoteImageChecker(ImageChecker):
|
|
|
78
86
|
raise ValueError("remote client should not be None")
|
|
79
87
|
cls._images_client = image_service_pb2_grpc.ImageServiceStub(cfg.client._channel)
|
|
80
88
|
resp = await cls._images_client.GetImage(req)
|
|
81
|
-
logger.warning(
|
|
89
|
+
logger.warning(f"[blue]Image {resp.image.fqin} found. Skip building.[/blue]")
|
|
82
90
|
return resp.image.fqin
|
|
83
91
|
except Exception:
|
|
84
|
-
logger.warning(
|
|
92
|
+
logger.warning(f"[blue]Image {image_name} was not found or has expired.[/blue]", extra={"highlight": False})
|
|
85
93
|
return None
|
|
86
94
|
|
|
87
95
|
|
|
@@ -91,49 +99,45 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
91
99
|
return [RemoteImageChecker]
|
|
92
100
|
|
|
93
101
|
async def build_image(self, image: Image, dry_run: bool = False) -> str:
|
|
94
|
-
from
|
|
102
|
+
from flyteidl2.workflow import run_definition_pb2
|
|
95
103
|
|
|
96
104
|
image_name = f"{image.name}:{image._final_tag}"
|
|
97
105
|
spec, context = await _validate_configuration(image)
|
|
98
106
|
|
|
99
107
|
start = datetime.now(timezone.utc)
|
|
100
|
-
entity = remote.Task.get(
|
|
108
|
+
entity = await remote.Task.get(
|
|
101
109
|
name=IMAGE_TASK_NAME,
|
|
102
110
|
project=IMAGE_TASK_PROJECT,
|
|
103
111
|
domain=IMAGE_TASK_DOMAIN,
|
|
104
112
|
auto_version="latest",
|
|
105
|
-
)
|
|
113
|
+
).override.aio(secrets=_get_build_secrets_from_image(image))
|
|
114
|
+
|
|
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()
|
|
106
124
|
run = cast(
|
|
107
125
|
Run,
|
|
108
|
-
await flyte.with_runcontext(project=
|
|
109
|
-
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
|
|
110
128
|
),
|
|
111
129
|
)
|
|
112
|
-
logger.warning(
|
|
130
|
+
logger.warning(f"⏳ Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
|
|
113
131
|
|
|
114
|
-
logger.warning(click.style("⏳ Waiting for build to finish at: " + click.style(run.url, fg="cyan"), bold=True))
|
|
115
132
|
await run.wait.aio(quiet=True)
|
|
116
133
|
run_details = await run.details.aio()
|
|
117
134
|
|
|
118
135
|
elapsed = str(datetime.now(timezone.utc) - start).split(".")[0]
|
|
119
136
|
|
|
120
137
|
if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
|
|
121
|
-
logger.warning(
|
|
122
|
-
try:
|
|
123
|
-
entity = remote.Task.get(
|
|
124
|
-
name=OPTIMIZE_TASK_NAME,
|
|
125
|
-
project=IMAGE_TASK_PROJECT,
|
|
126
|
-
domain=IMAGE_TASK_DOMAIN,
|
|
127
|
-
auto_version="latest",
|
|
128
|
-
)
|
|
129
|
-
await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
|
|
130
|
-
entity, spec=spec, context=context, target_image=image_name
|
|
131
|
-
)
|
|
132
|
-
except Exception as e:
|
|
133
|
-
# Ignore the error if optimize is not enabled in the backend.
|
|
134
|
-
logger.warning(f"Failed to run optimize task with error: {e}")
|
|
138
|
+
logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
|
|
135
139
|
else:
|
|
136
|
-
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {
|
|
140
|
+
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {run.url}")
|
|
137
141
|
|
|
138
142
|
outputs = await run_details.outputs()
|
|
139
143
|
return _get_fully_qualified_image_name(outputs)
|
|
@@ -157,17 +161,29 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
|
157
161
|
|
|
158
162
|
if any(context_path.iterdir()):
|
|
159
163
|
# If there are files in the context directory, upload it
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
tar_path = tmp_path / "context.tar"
|
|
165
|
+
with tarfile.open(tar_path, "w", dereference=False) as tar:
|
|
166
|
+
files: typing.List[str] = os.listdir(context_path)
|
|
167
|
+
for ws_file in files:
|
|
168
|
+
tar.add(
|
|
169
|
+
os.path.join(context_path, ws_file),
|
|
170
|
+
recursive=True,
|
|
171
|
+
arcname=ws_file,
|
|
172
|
+
filter=tar_strip_file_attributes,
|
|
168
173
|
)
|
|
174
|
+
context_dst = Path(f"{tar_path!s}.gz")
|
|
175
|
+
with gzip.GzipFile(filename=context_dst, mode="wb", mtime=0) as gzipped:
|
|
176
|
+
async with aiofiles.open(tar_path, "rb") as tar_file:
|
|
177
|
+
content = await tar_file.read()
|
|
178
|
+
gzipped.write(content)
|
|
179
|
+
|
|
180
|
+
context_size = tar_path.stat().st_size
|
|
181
|
+
if context_size > 5 * 1024 * 1024:
|
|
182
|
+
logger.warning(
|
|
183
|
+
f"[yellow]Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
|
|
184
|
+
"Upload and build speed will be impacted.[/yellow]",
|
|
169
185
|
)
|
|
170
|
-
_, context_url = await remote.upload_file.aio(
|
|
186
|
+
_, context_url = await remote.upload_file.aio(context_dst)
|
|
171
187
|
else:
|
|
172
188
|
context_url = ""
|
|
173
189
|
|
|
@@ -175,13 +191,36 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
|
175
191
|
|
|
176
192
|
|
|
177
193
|
def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2.ImageSpec":
|
|
178
|
-
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
|
+
)
|
|
179
200
|
|
|
180
201
|
layers = []
|
|
181
202
|
for layer in image._layers:
|
|
203
|
+
secret_mounts = None
|
|
204
|
+
pip_options = image_definition_pb2.PipOptions()
|
|
205
|
+
|
|
206
|
+
if isinstance(layer, PipOption):
|
|
207
|
+
pip_options = image_definition_pb2.PipOptions(
|
|
208
|
+
index_url=layer.index_url,
|
|
209
|
+
extra_index_urls=layer.extra_index_urls,
|
|
210
|
+
pre=layer.pre,
|
|
211
|
+
extra_args=layer.extra_args,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if hasattr(layer, "secret_mounts"):
|
|
215
|
+
sc = get_security_context(layer.secret_mounts)
|
|
216
|
+
secret_mounts = sc.secrets if sc else None
|
|
217
|
+
|
|
182
218
|
if isinstance(layer, AptPackages):
|
|
183
219
|
apt_layer = image_definition_pb2.Layer(
|
|
184
|
-
apt_packages=image_definition_pb2.AptPackages(
|
|
220
|
+
apt_packages=image_definition_pb2.AptPackages(
|
|
221
|
+
packages=layer.packages,
|
|
222
|
+
secret_mounts=secret_mounts,
|
|
223
|
+
),
|
|
185
224
|
)
|
|
186
225
|
layers.append(apt_layer)
|
|
187
226
|
elif isinstance(layer, PythonWheels):
|
|
@@ -189,12 +228,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
189
228
|
wheel_layer = image_definition_pb2.Layer(
|
|
190
229
|
python_wheels=image_definition_pb2.PythonWheels(
|
|
191
230
|
dir=str(dst_path.relative_to(context_path)),
|
|
192
|
-
options=
|
|
193
|
-
|
|
194
|
-
extra_index_urls=layer.extra_index_urls,
|
|
195
|
-
pre=layer.pre,
|
|
196
|
-
extra_args=layer.extra_args,
|
|
197
|
-
),
|
|
231
|
+
options=pip_options,
|
|
232
|
+
secret_mounts=secret_mounts,
|
|
198
233
|
)
|
|
199
234
|
)
|
|
200
235
|
layers.append(wheel_layer)
|
|
@@ -204,12 +239,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
204
239
|
requirements_layer = image_definition_pb2.Layer(
|
|
205
240
|
requirements=image_definition_pb2.Requirements(
|
|
206
241
|
file=str(dst_path.relative_to(context_path)),
|
|
207
|
-
options=
|
|
208
|
-
|
|
209
|
-
extra_index_urls=layer.extra_index_urls,
|
|
210
|
-
pre=layer.pre,
|
|
211
|
-
extra_args=layer.extra_args,
|
|
212
|
-
),
|
|
242
|
+
options=pip_options,
|
|
243
|
+
secret_mounts=secret_mounts,
|
|
213
244
|
)
|
|
214
245
|
)
|
|
215
246
|
layers.append(requirements_layer)
|
|
@@ -226,12 +257,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
226
257
|
pip_layer = image_definition_pb2.Layer(
|
|
227
258
|
pip_packages=image_definition_pb2.PipPackages(
|
|
228
259
|
packages=packages,
|
|
229
|
-
options=
|
|
230
|
-
|
|
231
|
-
extra_index_urls=layer.extra_index_urls,
|
|
232
|
-
pre=layer.pre,
|
|
233
|
-
extra_args=layer.extra_args,
|
|
234
|
-
),
|
|
260
|
+
options=pip_options,
|
|
261
|
+
secret_mounts=secret_mounts,
|
|
235
262
|
)
|
|
236
263
|
)
|
|
237
264
|
layers.append(pip_layer)
|
|
@@ -239,18 +266,60 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
239
266
|
for line in layer.pyproject.read_text().splitlines():
|
|
240
267
|
if "tool.uv.index" in line:
|
|
241
268
|
raise ValueError("External sources are not supported in pyproject.toml")
|
|
242
|
-
|
|
269
|
+
|
|
270
|
+
if layer.project_install_mode == "dependencies_only":
|
|
271
|
+
# Copy pyproject itself
|
|
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"
|
|
280
|
+
else:
|
|
281
|
+
# Copy the entire project
|
|
282
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
283
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
|
|
243
284
|
|
|
244
285
|
uv_layer = image_definition_pb2.Layer(
|
|
245
286
|
uv_project=image_definition_pb2.UVProject(
|
|
246
|
-
pyproject=str(
|
|
247
|
-
uvlock=str(layer.uvlock.
|
|
287
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
288
|
+
uvlock=str(copy_files_to_context(layer.uvlock, context_path).relative_to(context_path)),
|
|
289
|
+
options=pip_options,
|
|
290
|
+
secret_mounts=secret_mounts,
|
|
248
291
|
)
|
|
249
292
|
)
|
|
250
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)
|
|
251
317
|
elif isinstance(layer, Commands):
|
|
252
318
|
commands_layer = image_definition_pb2.Layer(
|
|
253
|
-
commands=image_definition_pb2.Commands(
|
|
319
|
+
commands=image_definition_pb2.Commands(
|
|
320
|
+
cmd=list(layer.commands),
|
|
321
|
+
secret_mounts=secret_mounts,
|
|
322
|
+
)
|
|
254
323
|
)
|
|
255
324
|
layers.append(commands_layer)
|
|
256
325
|
elif isinstance(layer, DockerIgnore):
|
|
@@ -287,3 +356,25 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
287
356
|
|
|
288
357
|
def _get_fully_qualified_image_name(outputs: ActionOutputs) -> str:
|
|
289
358
|
return outputs.pb2.literals[0].value.scalar.primitive.string_value
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]:
|
|
362
|
+
secrets = []
|
|
363
|
+
DEFAULT_SECRET_DIR = Path("/etc/flyte/secrets")
|
|
364
|
+
for layer in image._layers:
|
|
365
|
+
if isinstance(layer, (PipOption, Commands, AptPackages)) and layer.secret_mounts is not None:
|
|
366
|
+
for secret_mount in layer.secret_mounts:
|
|
367
|
+
# Mount all the image secrets to a default directory that will be passed to the BuildKit server.
|
|
368
|
+
if isinstance(secret_mount, Secret):
|
|
369
|
+
secrets.append(Secret(key=secret_mount.key, group=secret_mount.group, mount=DEFAULT_SECRET_DIR))
|
|
370
|
+
elif isinstance(secret_mount, str):
|
|
371
|
+
secrets.append(Secret(key=secret_mount, mount=DEFAULT_SECRET_DIR))
|
|
372
|
+
else:
|
|
373
|
+
raise ValueError(f"Unsupported secret_mount type: {type(secret_mount)}")
|
|
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
|
+
)
|
|
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,7 +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
|
-
|
|
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))
|
|
27
33
|
else:
|
|
28
34
|
shutil.copy(src, dst_path)
|
|
29
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
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import os
|
|
3
1
|
import pathlib
|
|
4
|
-
import sys
|
|
5
2
|
from typing import Tuple
|
|
6
3
|
|
|
4
|
+
from flyte._module import extract_obj_module
|
|
7
5
|
from flyte._task import AsyncFunctionTaskTemplate, TaskTemplate
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path
|
|
8
|
+
def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path) -> Tuple[str, str]:
|
|
11
9
|
"""
|
|
12
10
|
Extract the task module from the task template.
|
|
13
11
|
|
|
@@ -15,40 +13,9 @@ def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path | None =
|
|
|
15
13
|
:param source_dir: The source directory to use for relative paths.
|
|
16
14
|
:return: A tuple containing the entity name, module
|
|
17
15
|
"""
|
|
18
|
-
entity_name = task.name
|
|
19
16
|
if isinstance(task, AsyncFunctionTaskTemplate):
|
|
20
|
-
entity_module = inspect.getmodule(task.func)
|
|
21
|
-
if entity_module is None:
|
|
22
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
23
|
-
|
|
24
|
-
fp = entity_module.__file__
|
|
25
|
-
if fp is None:
|
|
26
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
27
|
-
|
|
28
|
-
file_path = pathlib.Path(fp)
|
|
29
|
-
# Get the relative path to the current directory
|
|
30
|
-
# Will raise ValueError if the file is not in the source directory
|
|
31
|
-
relative_path = file_path.relative_to(str(source_dir))
|
|
32
|
-
|
|
33
|
-
if relative_path == pathlib.Path("."):
|
|
34
|
-
entity_module_name = entity_module.__name__
|
|
35
|
-
else:
|
|
36
|
-
# Replace file separators with dots and remove the '.py' extension
|
|
37
|
-
dotted_path = os.path.splitext(str(relative_path))[0].replace(os.sep, ".")
|
|
38
|
-
entity_module_name = dotted_path
|
|
39
|
-
|
|
40
17
|
entity_name = task.func.__name__
|
|
18
|
+
entity_module_name = extract_obj_module(task.func, source_dir)
|
|
19
|
+
return entity_name, entity_module_name
|
|
41
20
|
else:
|
|
42
|
-
raise NotImplementedError(f"Task module {
|
|
43
|
-
|
|
44
|
-
if entity_module_name == "__main__":
|
|
45
|
-
"""
|
|
46
|
-
This case is for the case in which the task is run from the main module.
|
|
47
|
-
"""
|
|
48
|
-
fp = sys.modules["__main__"].__file__
|
|
49
|
-
if fp is None:
|
|
50
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
51
|
-
main_path = pathlib.Path(fp)
|
|
52
|
-
entity_module_name = main_path.stem
|
|
53
|
-
|
|
54
|
-
return entity_name, entity_module_name
|
|
21
|
+
raise NotImplementedError(f"Task module {task.name} not implemented.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List
|
|
4
4
|
|
|
5
5
|
from flyte._internal.resolvers._task_module import extract_task_module
|
|
6
6
|
from flyte._internal.resolvers.common import Resolver
|
|
@@ -23,6 +23,6 @@ class DefaultTaskResolver(Resolver):
|
|
|
23
23
|
task_def = getattr(task_module, task_name)
|
|
24
24
|
return task_def
|
|
25
25
|
|
|
26
|
-
def loader_args(self, task: TaskTemplate, root_dir:
|
|
26
|
+
def loader_args(self, task: TaskTemplate, root_dir: Path) -> List[str]: # type:ignore
|
|
27
27
|
t, m = extract_task_module(task, root_dir)
|
|
28
28
|
return ["mod", m, "instance", t]
|