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
flyte/_utils/__init__.py
CHANGED
|
@@ -9,12 +9,13 @@ from .coro_management import run_coros
|
|
|
9
9
|
from .file_handling import filehash_update, update_hasher_for_source
|
|
10
10
|
from .helpers import get_cwd_editable_install
|
|
11
11
|
from .lazy_module import lazy_module
|
|
12
|
-
from .module_loader import load_python_modules
|
|
12
|
+
from .module_loader import adjust_sys_path, load_python_modules
|
|
13
13
|
from .org_discovery import hostname_from_url, org_from_endpoint, sanitize_endpoint
|
|
14
14
|
from .uv_script_parser import parse_uv_script_file
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
"AsyncLRUCache",
|
|
18
|
+
"adjust_sys_path",
|
|
18
19
|
"filehash_update",
|
|
19
20
|
"get_cwd_editable_install",
|
|
20
21
|
"hostname_from_url",
|
flyte/_utils/asyn.py
CHANGED
|
@@ -9,6 +9,8 @@ async def async_add(a: int, b: int) -> int:
|
|
|
9
9
|
result = run_sync(async_add, a=10, b=12)
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
12
14
|
import asyncio
|
|
13
15
|
import atexit
|
|
14
16
|
import functools
|
|
@@ -88,7 +90,7 @@ class _TaskRunner:
|
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
class _AsyncLoopManager:
|
|
91
|
-
def __init__(self):
|
|
93
|
+
def __init__(self: _AsyncLoopManager):
|
|
92
94
|
self._runner_map: dict[str, _TaskRunner] = {}
|
|
93
95
|
|
|
94
96
|
def run_sync(self, coro_func: Callable[..., Awaitable[T]], *args, **kwargs) -> T:
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Helper functions for creating Docker registry credentials for image pull secrets."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
_CONFIG_JSON = "config.json"
|
|
14
|
+
_DEFAULT_CONFIG_PATH = f"~/.docker/{_CONFIG_JSON}"
|
|
15
|
+
_CRED_HELPERS = "credHelpers"
|
|
16
|
+
_CREDS_STORE = "credsStore"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_docker_config(config_path: str | Path | None = None) -> dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Load Docker config from specified path.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
config_path: Path to Docker config file. If None, uses DOCKER_CONFIG env var
|
|
25
|
+
or defaults to ~/.docker/config.json
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary containing Docker config
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
FileNotFoundError: If the config file does not exist
|
|
32
|
+
json.JSONDecodeError: If the config file is not valid JSON
|
|
33
|
+
"""
|
|
34
|
+
if not config_path:
|
|
35
|
+
docker_config_env = os.environ.get("DOCKER_CONFIG")
|
|
36
|
+
if docker_config_env:
|
|
37
|
+
config_path = Path(docker_config_env) / _CONFIG_JSON
|
|
38
|
+
else:
|
|
39
|
+
config_path = Path(_DEFAULT_CONFIG_PATH).expanduser()
|
|
40
|
+
else:
|
|
41
|
+
config_path = Path(config_path).expanduser()
|
|
42
|
+
|
|
43
|
+
with open(config_path) as f:
|
|
44
|
+
return json.load(f)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_credential_helper(config: dict[str, Any], registry: str | None = None) -> str | None:
|
|
48
|
+
"""Get credential helper for registry or global default."""
|
|
49
|
+
if registry and _CRED_HELPERS in config and registry in config[_CRED_HELPERS]:
|
|
50
|
+
return config[_CRED_HELPERS].get(registry)
|
|
51
|
+
return config.get(_CREDS_STORE)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_credentials_from_helper(helper: str, registry: str) -> tuple[str, str] | None:
|
|
55
|
+
"""
|
|
56
|
+
Get credentials from system credential helper.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
helper: Name of the credential helper (e.g., "osxkeychain", "wincred")
|
|
60
|
+
registry: Registry hostname to get credentials for
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Tuple of (username, password) or None if credentials cannot be retrieved
|
|
64
|
+
"""
|
|
65
|
+
helper_cmd = f"docker-credential-{helper}"
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
process = subprocess.Popen(
|
|
69
|
+
[helper_cmd, "get"],
|
|
70
|
+
stdin=subprocess.PIPE,
|
|
71
|
+
stdout=subprocess.PIPE,
|
|
72
|
+
stderr=subprocess.PIPE,
|
|
73
|
+
text=True,
|
|
74
|
+
)
|
|
75
|
+
output, error = process.communicate(input=registry)
|
|
76
|
+
|
|
77
|
+
if process.returncode != 0:
|
|
78
|
+
logger.error(f"Credential helper error: {error}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
creds = json.loads(output)
|
|
82
|
+
return creds.get("Username"), creds.get("Secret")
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
logger.error(f"Credential helper {helper_cmd} not found in PATH")
|
|
85
|
+
return None
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Error getting credentials: {e!s}")
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def create_dockerconfigjson_from_config(
|
|
92
|
+
registries: list[str] | None = None,
|
|
93
|
+
docker_config_path: str | Path | None = None,
|
|
94
|
+
) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Create a dockerconfigjson string from existing Docker config.
|
|
97
|
+
|
|
98
|
+
This function extracts Docker registry credentials from the user's Docker config file
|
|
99
|
+
and creates a JSON string containing only the credentials for the specified registries.
|
|
100
|
+
It handles credentials stored directly in the config file as well as those managed by
|
|
101
|
+
credential helpers.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
registries: List of registries to extract credentials for. If None, all registries
|
|
105
|
+
from the config will be used.
|
|
106
|
+
docker_config_path: Path to the Docker config file. If None, the function will look
|
|
107
|
+
for the config file in the standard locations.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
JSON string in dockerconfigjson format: {"auths": {"registry": {"auth": "..."}}}
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
FileNotFoundError: If Docker config file cannot be found
|
|
114
|
+
ValueError: If no credentials can be extracted
|
|
115
|
+
"""
|
|
116
|
+
config = _load_docker_config(docker_config_path)
|
|
117
|
+
|
|
118
|
+
# Create new config structure with empty auths
|
|
119
|
+
new_config: dict[str, Any] = {"auths": {}}
|
|
120
|
+
|
|
121
|
+
# Use specified registries or all from config
|
|
122
|
+
target_registries = registries or list(config.get("auths", {}).keys())
|
|
123
|
+
|
|
124
|
+
if not target_registries:
|
|
125
|
+
raise ValueError("No registries found in Docker config and none specified")
|
|
126
|
+
|
|
127
|
+
for registry in target_registries:
|
|
128
|
+
registry_config = config.get("auths", {}).get(registry, {})
|
|
129
|
+
if registry_config.get("auth"):
|
|
130
|
+
# Direct auth token exists
|
|
131
|
+
new_config["auths"][registry] = {"auth": registry_config["auth"]}
|
|
132
|
+
else:
|
|
133
|
+
# Try to get credentials from helper
|
|
134
|
+
helper = _get_credential_helper(config, registry)
|
|
135
|
+
if helper:
|
|
136
|
+
creds = _get_credentials_from_helper(helper, registry)
|
|
137
|
+
if creds:
|
|
138
|
+
username, password = creds
|
|
139
|
+
auth_string = f"{username}:{password}"
|
|
140
|
+
new_config["auths"][registry] = {"auth": base64.b64encode(auth_string.encode()).decode()}
|
|
141
|
+
else:
|
|
142
|
+
logger.warning(f"Could not retrieve credentials for {registry} from credential helper")
|
|
143
|
+
else:
|
|
144
|
+
logger.warning(f"No credentials found for {registry}")
|
|
145
|
+
|
|
146
|
+
if not new_config["auths"]:
|
|
147
|
+
raise ValueError(f"No credentials could be extracted for registries: {', '.join(target_registries)}")
|
|
148
|
+
|
|
149
|
+
return json.dumps(new_config)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def create_dockerconfigjson_from_credentials(
|
|
153
|
+
registry: str,
|
|
154
|
+
username: str,
|
|
155
|
+
password: str,
|
|
156
|
+
) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Create a dockerconfigjson string from explicit credentials.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
registry: Registry hostname (e.g., "ghcr.io", "docker.io")
|
|
162
|
+
username: Username or token name for the registry
|
|
163
|
+
password: Password or access token for the registry
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
JSON string in dockerconfigjson format: {"auths": {"registry": {"auth": "..."}}}
|
|
167
|
+
"""
|
|
168
|
+
auth_string = f"{username}:{password}"
|
|
169
|
+
auth_token = base64.b64encode(auth_string.encode()).decode()
|
|
170
|
+
|
|
171
|
+
config = {"auths": {registry: {"auth": auth_token}}}
|
|
172
|
+
|
|
173
|
+
return json.dumps(config)
|
flyte/_utils/module_loader.py
CHANGED
|
@@ -5,9 +5,9 @@ import sys
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import List, Tuple
|
|
7
7
|
|
|
8
|
-
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn, TimeRemainingColumn
|
|
9
|
-
|
|
10
8
|
import flyte.errors
|
|
9
|
+
from flyte._constants import FLYTE_SYS_PATH
|
|
10
|
+
from flyte._logging import logger
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def load_python_modules(path: Path, recursive: bool = False) -> Tuple[List[str], List[Tuple[Path, str]]]:
|
|
@@ -18,6 +18,8 @@ def load_python_modules(path: Path, recursive: bool = False) -> Tuple[List[str],
|
|
|
18
18
|
:param recursive: If True, load modules recursively from subdirectories
|
|
19
19
|
:return: List of loaded module names, and list of file paths that failed to load
|
|
20
20
|
"""
|
|
21
|
+
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn, TimeRemainingColumn
|
|
22
|
+
|
|
21
23
|
loaded_modules = []
|
|
22
24
|
failed_paths = []
|
|
23
25
|
|
|
@@ -87,3 +89,16 @@ def _load_module_from_file(file_path: Path) -> str | None:
|
|
|
87
89
|
|
|
88
90
|
except Exception as e:
|
|
89
91
|
raise flyte.errors.ModuleLoadError(f"Failed to load module from {file_path}: {e}") from e
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def adjust_sys_path():
|
|
95
|
+
"""
|
|
96
|
+
Adjust sys.path to include local sys.path entries under the root directory.
|
|
97
|
+
"""
|
|
98
|
+
if "." not in sys.path or os.getcwd() not in sys.path:
|
|
99
|
+
sys.path.insert(0, ".")
|
|
100
|
+
logger.info(f"Added {os.getcwd()} to sys.path")
|
|
101
|
+
for p in os.environ.get(FLYTE_SYS_PATH, "").split(":"):
|
|
102
|
+
if p and p not in sys.path:
|
|
103
|
+
sys.path.insert(0, p)
|
|
104
|
+
logger.info(f"Added {p} to sys.path")
|
flyte/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
31
|
+
__version__ = version = '2.0.0b30'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b30')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gf1b5e2b2b'
|
flyte/cli/_abort.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import rich_click as click
|
|
2
|
-
from rich.console import Console
|
|
3
2
|
|
|
4
3
|
from flyte.cli import _common as common
|
|
5
4
|
|
|
@@ -23,6 +22,7 @@ def run(cfg: common.CLIConfig, run_name: str, project: str | None = None, domain
|
|
|
23
22
|
cfg.init(project=project, domain=domain)
|
|
24
23
|
r = Run.get(name=run_name)
|
|
25
24
|
if r:
|
|
26
|
-
console =
|
|
27
|
-
|
|
25
|
+
console = common.get_console()
|
|
26
|
+
with console.status(f"Aborting run '{run_name}'...", spinner="dots"):
|
|
27
|
+
r.abort()
|
|
28
28
|
console.print(f"Run '{run_name}' has been aborted.")
|
flyte/cli/_build.py
CHANGED
|
@@ -44,9 +44,7 @@ class BuildEnvCommand(click.Command):
|
|
|
44
44
|
super().__init__(*args, **kwargs)
|
|
45
45
|
|
|
46
46
|
def invoke(self, ctx: click.Context):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
console = Console()
|
|
47
|
+
console = common.get_console()
|
|
50
48
|
console.print(f"Building Environment: {self.obj_name}")
|
|
51
49
|
obj: CLIConfig = ctx.obj
|
|
52
50
|
obj.init()
|
flyte/cli/_common.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
|
+
import json
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
7
|
+
import pathlib
|
|
6
8
|
import sys
|
|
7
9
|
from abc import abstractmethod
|
|
8
10
|
from dataclasses import dataclass, replace
|
|
11
|
+
from functools import lru_cache
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
from types import MappingProxyType, ModuleType
|
|
11
14
|
from typing import Any, Dict, Iterable, List, Literal, Optional
|
|
@@ -19,10 +22,12 @@ from rich.pretty import pretty_repr
|
|
|
19
22
|
from rich.table import Table
|
|
20
23
|
from rich.traceback import Traceback
|
|
21
24
|
|
|
25
|
+
import flyte.config
|
|
22
26
|
import flyte.errors
|
|
27
|
+
from flyte._logging import LogFormat
|
|
23
28
|
from flyte.config import Config
|
|
24
29
|
|
|
25
|
-
OutputFormat = Literal["table", "json", "table-simple"]
|
|
30
|
+
OutputFormat = Literal["table", "json", "table-simple", "json-raw"]
|
|
26
31
|
|
|
27
32
|
PREFERRED_BORDER_COLOR = "dim cyan"
|
|
28
33
|
PREFERRED_ACCENT_COLOR = "bold #FFD700"
|
|
@@ -99,6 +104,7 @@ class CLIConfig:
|
|
|
99
104
|
config: Config
|
|
100
105
|
ctx: click.Context
|
|
101
106
|
log_level: int | None = logging.ERROR
|
|
107
|
+
log_format: LogFormat = "console"
|
|
102
108
|
endpoint: str | None = None
|
|
103
109
|
insecure: bool = False
|
|
104
110
|
org: str | None = None
|
|
@@ -111,13 +117,20 @@ class CLIConfig:
|
|
|
111
117
|
"""
|
|
112
118
|
return replace(self, **kwargs)
|
|
113
119
|
|
|
114
|
-
def init(
|
|
120
|
+
def init(
|
|
121
|
+
self,
|
|
122
|
+
project: str | None = None,
|
|
123
|
+
domain: str | None = None,
|
|
124
|
+
root_dir: str | None = None,
|
|
125
|
+
images: tuple[str, ...] | None = None,
|
|
126
|
+
sync_local_sys_paths: bool = True,
|
|
127
|
+
):
|
|
115
128
|
from flyte.config._config import TaskConfig
|
|
116
129
|
|
|
117
130
|
task_cfg = TaskConfig(
|
|
118
131
|
org=self.org or self.config.task.org,
|
|
119
|
-
project=project
|
|
120
|
-
domain=domain
|
|
132
|
+
project=project if project is not None else self.config.task.project,
|
|
133
|
+
domain=domain if domain is not None else self.config.task.domain,
|
|
121
134
|
)
|
|
122
135
|
|
|
123
136
|
kwargs: Dict[str, Any] = {}
|
|
@@ -131,7 +144,14 @@ class CLIConfig:
|
|
|
131
144
|
|
|
132
145
|
updated_config = self.config.with_params(platform_cfg, task_cfg)
|
|
133
146
|
|
|
134
|
-
flyte.init_from_config(
|
|
147
|
+
flyte.init_from_config(
|
|
148
|
+
updated_config,
|
|
149
|
+
log_level=self.log_level,
|
|
150
|
+
log_format=self.log_format,
|
|
151
|
+
root_dir=pathlib.Path(root_dir) if root_dir else None,
|
|
152
|
+
images=images,
|
|
153
|
+
sync_local_sys_paths=sync_local_sys_paths,
|
|
154
|
+
)
|
|
135
155
|
|
|
136
156
|
|
|
137
157
|
class InvokeBaseMixin:
|
|
@@ -177,7 +197,7 @@ class InvokeBaseMixin:
|
|
|
177
197
|
except Exception as e:
|
|
178
198
|
if ctx.obj and ctx.obj.log_level and ctx.obj.log_level <= logging.DEBUG:
|
|
179
199
|
# If the user has requested verbose output, print the full traceback
|
|
180
|
-
console =
|
|
200
|
+
console = get_console()
|
|
181
201
|
console.print(Traceback.from_exception(type(e), e, e.__traceback__))
|
|
182
202
|
exit(1)
|
|
183
203
|
else:
|
|
@@ -354,7 +374,7 @@ def _table_format(table: Table, vals: Iterable[Any]) -> Table:
|
|
|
354
374
|
if headers is None:
|
|
355
375
|
headers = [k for k, _ in o]
|
|
356
376
|
for h in headers:
|
|
357
|
-
table.add_column(h.capitalize())
|
|
377
|
+
table.add_column(h.capitalize(), no_wrap=True if "name" in h.casefold() else False)
|
|
358
378
|
table.add_row(*[str(v) for _, v in o])
|
|
359
379
|
return table
|
|
360
380
|
|
|
@@ -374,6 +394,7 @@ def format(title: str, vals: Iterable[Any], of: OutputFormat = "table") -> Table
|
|
|
374
394
|
header_style=HEADER_STYLE,
|
|
375
395
|
show_header=True,
|
|
376
396
|
border_style=PREFERRED_BORDER_COLOR,
|
|
397
|
+
expand=True,
|
|
377
398
|
),
|
|
378
399
|
vals,
|
|
379
400
|
)
|
|
@@ -381,6 +402,11 @@ def format(title: str, vals: Iterable[Any], of: OutputFormat = "table") -> Table
|
|
|
381
402
|
if not vals:
|
|
382
403
|
return pretty_repr([])
|
|
383
404
|
return pretty_repr([v.to_dict() for v in vals])
|
|
405
|
+
case "json-raw":
|
|
406
|
+
if not vals:
|
|
407
|
+
return []
|
|
408
|
+
return json.dumps([v.to_dict() for v in vals])
|
|
409
|
+
|
|
384
410
|
raise click.ClickException("Unknown output format. Supported formats are: table, table-simple, json.")
|
|
385
411
|
|
|
386
412
|
|
|
@@ -395,3 +421,48 @@ def get_panel(title: str, renderable: Any, of: OutputFormat = "table") -> Panel:
|
|
|
395
421
|
title=f"[{PREFERRED_ACCENT_COLOR}]{title}[/{PREFERRED_ACCENT_COLOR}]",
|
|
396
422
|
border_style=PREFERRED_BORDER_COLOR,
|
|
397
423
|
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def get_console() -> Console:
|
|
427
|
+
"""
|
|
428
|
+
Get a console that is configured to use colors if the terminal supports it.
|
|
429
|
+
"""
|
|
430
|
+
return Console(color_system="auto", force_terminal=True, width=120)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def parse_images(cfg: Config, values: tuple[str, ...] | None) -> None:
|
|
434
|
+
"""
|
|
435
|
+
Parse image values and update the config.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
cfg: The Config object to write images to
|
|
439
|
+
values: List of image strings in format "imagename=imageuri" or just "imageuri"
|
|
440
|
+
"""
|
|
441
|
+
if values is None:
|
|
442
|
+
return
|
|
443
|
+
for value in values:
|
|
444
|
+
if "=" in value:
|
|
445
|
+
image_name, image_uri = value.split("=", 1)
|
|
446
|
+
cfg.image.image_refs[image_name] = image_uri
|
|
447
|
+
else:
|
|
448
|
+
# If no name specified, use "default" as the name
|
|
449
|
+
cfg.image.image_refs["default"] = value
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@lru_cache()
|
|
453
|
+
def initialize_config(
|
|
454
|
+
ctx: click.Context,
|
|
455
|
+
project: str,
|
|
456
|
+
domain: str,
|
|
457
|
+
root_dir: str | None = None,
|
|
458
|
+
images: tuple[str, ...] | None = None,
|
|
459
|
+
sync_local_sys_paths: bool = True,
|
|
460
|
+
):
|
|
461
|
+
obj: CLIConfig | None = ctx.obj
|
|
462
|
+
if obj is None:
|
|
463
|
+
import flyte.config
|
|
464
|
+
|
|
465
|
+
obj = CLIConfig(flyte.config.auto(), ctx)
|
|
466
|
+
|
|
467
|
+
obj.init(project, domain, root_dir, images, sync_local_sys_paths)
|
|
468
|
+
return obj
|
flyte/cli/_create.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Any, Dict, get_args
|
|
|
3
3
|
|
|
4
4
|
import rich_click as click
|
|
5
5
|
|
|
6
|
+
import flyte
|
|
6
7
|
import flyte.cli._common as common
|
|
7
8
|
from flyte.cli._option import MutuallyExclusiveOption
|
|
8
9
|
from flyte.remote import SecretTypes
|
|
@@ -23,18 +24,49 @@ def create():
|
|
|
23
24
|
prompt="Enter secret value",
|
|
24
25
|
hide_input=True,
|
|
25
26
|
cls=MutuallyExclusiveOption,
|
|
26
|
-
mutually_exclusive=["from_file"],
|
|
27
|
+
mutually_exclusive=["from_file", "from_docker_config", "registry"],
|
|
27
28
|
)
|
|
28
29
|
@click.option(
|
|
29
30
|
"--from-file",
|
|
30
31
|
type=click.Path(exists=True),
|
|
31
32
|
help="Path to the file with the binary secret.",
|
|
32
33
|
cls=MutuallyExclusiveOption,
|
|
33
|
-
mutually_exclusive=["value"],
|
|
34
|
+
mutually_exclusive=["value", "from_docker_config", "registry"],
|
|
34
35
|
)
|
|
35
36
|
@click.option(
|
|
36
37
|
"--type", type=click.Choice(get_args(SecretTypes)), default="regular", help="Type of the secret.", show_default=True
|
|
37
38
|
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--from-docker-config",
|
|
41
|
+
is_flag=True,
|
|
42
|
+
help="Create image pull secret from Docker config file (only for --type image_pull).",
|
|
43
|
+
cls=MutuallyExclusiveOption,
|
|
44
|
+
mutually_exclusive=["value", "from_file", "registry", "username", "password"],
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--docker-config-path",
|
|
48
|
+
type=click.Path(exists=True),
|
|
49
|
+
help="Path to Docker config file (defaults to ~/.docker/config.json or $DOCKER_CONFIG).",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--registries",
|
|
53
|
+
help="Comma-separated list of registries to include (only with --from-docker-config).",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--registry",
|
|
57
|
+
help="Registry hostname (e.g., ghcr.io, docker.io) for explicit credentials (only for --type image_pull).",
|
|
58
|
+
cls=MutuallyExclusiveOption,
|
|
59
|
+
mutually_exclusive=["value", "from_file", "from_docker_config"],
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--username",
|
|
63
|
+
help="Username for the registry (only with --registry).",
|
|
64
|
+
)
|
|
65
|
+
@click.option(
|
|
66
|
+
"--password",
|
|
67
|
+
help="Password for the registry (only with --registry). If not provided, will prompt.",
|
|
68
|
+
hide_input=True,
|
|
69
|
+
)
|
|
38
70
|
@click.pass_obj
|
|
39
71
|
def secret(
|
|
40
72
|
cfg: common.CLIConfig,
|
|
@@ -42,6 +74,12 @@ def secret(
|
|
|
42
74
|
value: str | bytes | None = None,
|
|
43
75
|
from_file: str | None = None,
|
|
44
76
|
type: SecretTypes = "regular",
|
|
77
|
+
from_docker_config: bool = False,
|
|
78
|
+
docker_config_path: str | None = None,
|
|
79
|
+
registries: str | None = None,
|
|
80
|
+
registry: str | None = None,
|
|
81
|
+
username: str | None = None,
|
|
82
|
+
password: str | None = None,
|
|
45
83
|
project: str | None = None,
|
|
46
84
|
domain: str | None = None,
|
|
47
85
|
):
|
|
@@ -72,16 +110,80 @@ def secret(
|
|
|
72
110
|
Other secrets should be specified as `regular`.
|
|
73
111
|
If no type is specified, `regular` is assumed.
|
|
74
112
|
|
|
113
|
+
For image pull secrets, you have several options:
|
|
114
|
+
|
|
115
|
+
1. Interactive mode (prompts for registry, username, password):
|
|
75
116
|
```bash
|
|
76
117
|
$ flyte create secret my_secret --type image_pull
|
|
77
118
|
```
|
|
119
|
+
|
|
120
|
+
2. With explicit credentials:
|
|
121
|
+
```bash
|
|
122
|
+
$ flyte create secret my_secret --type image_pull --registry ghcr.io --username myuser
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
3. Lastly, you can create a secret from your existing Docker installation (i.e., you've run `docker login` in
|
|
126
|
+
the past) and you just want to pull from those credentials. Since you may have logged in to multiple registries,
|
|
127
|
+
you can specify which registries to include. If no registries are specified, all registries are added.
|
|
128
|
+
```bash
|
|
129
|
+
$ flyte create secret my_secret --type image_pull --from-docker-config --registries ghcr.io,docker.io
|
|
130
|
+
```
|
|
78
131
|
"""
|
|
79
132
|
from flyte.remote import Secret
|
|
80
133
|
|
|
134
|
+
# todo: remove this hack when secrets creation more easily distinguishes between org and project/domain level
|
|
135
|
+
# (and domain level) secrets
|
|
136
|
+
project = "" if project is None else project
|
|
137
|
+
domain = "" if domain is None else domain
|
|
81
138
|
cfg.init(project, domain)
|
|
82
|
-
|
|
139
|
+
|
|
140
|
+
# Handle image pull secret creation
|
|
141
|
+
if type == "image_pull":
|
|
142
|
+
if project != "" or domain != "":
|
|
143
|
+
raise click.ClickException("Project and domain must not be set when creating an image pull secret.")
|
|
144
|
+
|
|
145
|
+
if from_docker_config:
|
|
146
|
+
# Mode 3: From Docker config
|
|
147
|
+
from flyte._utils.docker_credentials import create_dockerconfigjson_from_config
|
|
148
|
+
|
|
149
|
+
registry_list = [r.strip() for r in registries.split(",")] if registries else None
|
|
150
|
+
try:
|
|
151
|
+
value = create_dockerconfigjson_from_config(
|
|
152
|
+
registries=registry_list,
|
|
153
|
+
docker_config_path=docker_config_path,
|
|
154
|
+
)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise click.ClickException(f"Failed to create dockerconfigjson from Docker config: {e}") from e
|
|
157
|
+
|
|
158
|
+
elif registry:
|
|
159
|
+
# Mode 2: Explicit credentials
|
|
160
|
+
from flyte._utils.docker_credentials import create_dockerconfigjson_from_credentials
|
|
161
|
+
|
|
162
|
+
if not username:
|
|
163
|
+
username = click.prompt("Username")
|
|
164
|
+
if not password:
|
|
165
|
+
password = click.prompt("Password", hide_input=True)
|
|
166
|
+
|
|
167
|
+
value = create_dockerconfigjson_from_credentials(registry, username, password)
|
|
168
|
+
|
|
169
|
+
else:
|
|
170
|
+
# Mode 1: Interactive prompts
|
|
171
|
+
from flyte._utils.docker_credentials import create_dockerconfigjson_from_credentials
|
|
172
|
+
|
|
173
|
+
registry = click.prompt("Registry (e.g., ghcr.io, docker.io)")
|
|
174
|
+
username = click.prompt("Username")
|
|
175
|
+
password = click.prompt("Password", hide_input=True)
|
|
176
|
+
|
|
177
|
+
value = create_dockerconfigjson_from_credentials(registry, username, password)
|
|
178
|
+
|
|
179
|
+
elif from_file:
|
|
83
180
|
with open(from_file, "rb") as f:
|
|
84
181
|
value = f.read()
|
|
182
|
+
|
|
183
|
+
# Encode string values to bytes
|
|
184
|
+
if isinstance(value, str):
|
|
185
|
+
value = value.encode("utf-8")
|
|
186
|
+
|
|
85
187
|
Secret.create(name=name, value=value, type=type)
|
|
86
188
|
|
|
87
189
|
|
|
@@ -194,3 +296,76 @@ def config(
|
|
|
194
296
|
yaml.dump(d, f)
|
|
195
297
|
|
|
196
298
|
click.echo(f"Config file written to {output_path}")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@create.command(cls=common.CommandBase)
|
|
302
|
+
@click.argument("task_name", type=str, required=True)
|
|
303
|
+
@click.argument("name", type=str, required=True)
|
|
304
|
+
@click.option(
|
|
305
|
+
"--schedule",
|
|
306
|
+
type=str,
|
|
307
|
+
required=True,
|
|
308
|
+
help="Cron schedule for the trigger. Defaults to every minute.",
|
|
309
|
+
show_default=True,
|
|
310
|
+
)
|
|
311
|
+
@click.option(
|
|
312
|
+
"--description",
|
|
313
|
+
type=str,
|
|
314
|
+
default="",
|
|
315
|
+
help="Description of the trigger.",
|
|
316
|
+
show_default=True,
|
|
317
|
+
)
|
|
318
|
+
@click.option(
|
|
319
|
+
"--auto-activate",
|
|
320
|
+
is_flag=True,
|
|
321
|
+
default=True,
|
|
322
|
+
help="Whether the trigger should not be automatically activated. Defaults to True.",
|
|
323
|
+
show_default=True,
|
|
324
|
+
)
|
|
325
|
+
@click.option(
|
|
326
|
+
"--trigger-time-var",
|
|
327
|
+
type=str,
|
|
328
|
+
default="trigger_time",
|
|
329
|
+
help="Variable name for the trigger time in the task inputs. Defaults to 'trigger_time'.",
|
|
330
|
+
show_default=True,
|
|
331
|
+
)
|
|
332
|
+
@click.pass_obj
|
|
333
|
+
def trigger(
|
|
334
|
+
cfg: common.CLIConfig,
|
|
335
|
+
task_name: str,
|
|
336
|
+
name: str,
|
|
337
|
+
schedule: str,
|
|
338
|
+
trigger_time_var: str = "trigger_time",
|
|
339
|
+
auto_activate: bool = True,
|
|
340
|
+
description: str = "",
|
|
341
|
+
project: str | None = None,
|
|
342
|
+
domain: str | None = None,
|
|
343
|
+
):
|
|
344
|
+
"""
|
|
345
|
+
Create a new trigger for a task. The task name and trigger name are required.
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
$ flyte create trigger my_task my_trigger --schedule "0 0 * * *"
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
This will create a trigger that runs every day at midnight.
|
|
354
|
+
"""
|
|
355
|
+
from flyte.remote import Trigger
|
|
356
|
+
|
|
357
|
+
cfg.init(project, domain)
|
|
358
|
+
console = common.get_console()
|
|
359
|
+
|
|
360
|
+
trigger = flyte.Trigger(
|
|
361
|
+
name=name,
|
|
362
|
+
automation=flyte.Cron(schedule),
|
|
363
|
+
description=description,
|
|
364
|
+
auto_activate=auto_activate,
|
|
365
|
+
inputs={trigger_time_var: flyte.TriggerTime}, # Use the trigger time variable in inputs
|
|
366
|
+
env_vars=None,
|
|
367
|
+
interruptible=None,
|
|
368
|
+
)
|
|
369
|
+
with console.status("Creating trigger..."):
|
|
370
|
+
v = Trigger.create(trigger, task_name=task_name)
|
|
371
|
+
console.print(f"[bold green]Trigger {v.name} created successfully![/bold green]")
|