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
flyte/_custom_context.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from flyte._context import ctx
|
|
6
|
+
|
|
7
|
+
from ._context import internal_ctx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_custom_context() -> dict[str, str]:
|
|
11
|
+
"""
|
|
12
|
+
Get the current input context. This can be used within a task to retrieve
|
|
13
|
+
context metadata that was passed to the action.
|
|
14
|
+
|
|
15
|
+
Context will automatically propagate to sub-actions.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
import flyte
|
|
20
|
+
|
|
21
|
+
env = flyte.TaskEnvironment(name="...")
|
|
22
|
+
|
|
23
|
+
@env.task
|
|
24
|
+
def t1():
|
|
25
|
+
# context can be retrieved with `get_custom_context`
|
|
26
|
+
ctx = flyte.get_custom_context()
|
|
27
|
+
print(ctx) # {'project': '...', 'entity': '...'}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
:return: Dictionary of context key-value pairs
|
|
31
|
+
"""
|
|
32
|
+
tctx = ctx()
|
|
33
|
+
if tctx is None or tctx.custom_context is None:
|
|
34
|
+
return {}
|
|
35
|
+
return tctx.custom_context
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@contextmanager
|
|
39
|
+
def custom_context(**context: str):
|
|
40
|
+
"""
|
|
41
|
+
Synchronous context manager to set input context for tasks spawned within this block.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
import flyte
|
|
46
|
+
|
|
47
|
+
env = flyte.TaskEnvironment(name="...")
|
|
48
|
+
|
|
49
|
+
@env.task
|
|
50
|
+
def t1():
|
|
51
|
+
ctx = flyte.get_custom_context()
|
|
52
|
+
print(ctx)
|
|
53
|
+
|
|
54
|
+
@env.task
|
|
55
|
+
def main():
|
|
56
|
+
# context can be passed via a context manager
|
|
57
|
+
with flyte.custom_context(project="my-project"):
|
|
58
|
+
t1() # will have {'project': 'my-project'} as context
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
:param context: Key-value pairs to set as input context
|
|
62
|
+
"""
|
|
63
|
+
ctx = internal_ctx()
|
|
64
|
+
if ctx.data.task_context is None:
|
|
65
|
+
yield
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
tctx = ctx.data.task_context
|
|
69
|
+
new_tctx = tctx.replace(custom_context={**tctx.custom_context, **context})
|
|
70
|
+
|
|
71
|
+
with ctx.replace_task_context(new_tctx):
|
|
72
|
+
yield
|
|
73
|
+
# Exit the context and restore the previous context
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
# Where the code-server tar and plugins are downloaded to
|
|
5
|
+
EXECUTABLE_NAME = "code-server"
|
|
6
|
+
DOWNLOAD_DIR = Path.home() / ".code-server"
|
|
7
|
+
HOURS_TO_SECONDS = 60 * 60
|
|
8
|
+
DEFAULT_UP_SECONDS = 10 * HOURS_TO_SECONDS # 10 hours
|
|
9
|
+
DEFAULT_CODE_SERVER_REMOTE_PATHS = {
|
|
10
|
+
"amd64": "https://github.com/coder/code-server/releases/download/v4.106.3/code-server-4.106.3-linux-amd64.tar.gz",
|
|
11
|
+
"arm64": "https://github.com/coder/code-server/releases/download/v4.106.3/code-server-4.106.3-linux-arm64.tar.gz",
|
|
12
|
+
}
|
|
13
|
+
DEFAULT_CODE_SERVER_EXTENSIONS = [
|
|
14
|
+
"https://raw.githubusercontent.com/flyteorg/flytetools/master/flytekitplugins/flyin/ms-python.python-2023.20.0.vsix",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# Duration to pause the checking of the heartbeat file until the next one
|
|
18
|
+
HEARTBEAT_CHECK_SECONDS = 60
|
|
19
|
+
|
|
20
|
+
# The path is hardcoded by code-server
|
|
21
|
+
# https://coder.com/docs/code-server/latest/FAQ#what-is-the-heartbeat-file
|
|
22
|
+
HEARTBEAT_PATH = os.path.expanduser("~/.local/share/code-server/heartbeat")
|
|
23
|
+
|
|
24
|
+
INTERACTIVE_DEBUGGING_FILE_NAME = "flyteinteractive_interactive_entrypoint.py"
|
|
25
|
+
RESUME_TASK_FILE_NAME = "flyteinteractive_resume_task.py"
|
|
26
|
+
# Config keys to store in task template
|
|
27
|
+
VSCODE_TYPE_KEY = "flyteinteractive_type"
|
|
28
|
+
VSCODE_PORT_KEY = "flyteinteractive_port"
|
|
29
|
+
|
|
30
|
+
TASK_FUNCTION_SOURCE_PATH = "TASK_FUNCTION_SOURCE_PATH"
|
|
31
|
+
|
|
32
|
+
# Default max idle seconds to terminate the flyteinteractive server
|
|
33
|
+
HOURS_TO_SECONDS = 60 * 60
|
|
34
|
+
MAX_IDLE_SECONDS = 10 * HOURS_TO_SECONDS # 10 hours
|
|
35
|
+
|
|
36
|
+
# Subprocess constants
|
|
37
|
+
EXIT_CODE_SUCCESS = 0
|
flyte/_debug/utils.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from flyte._debug.constants import EXIT_CODE_SUCCESS
|
|
4
|
+
from flyte._logging import logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def execute_command(cmd: str):
|
|
8
|
+
"""
|
|
9
|
+
Execute a command in the shell.
|
|
10
|
+
"""
|
|
11
|
+
process = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
|
12
|
+
logger.info(f"cmd: {cmd}")
|
|
13
|
+
stdout, stderr = await process.communicate()
|
|
14
|
+
if process.returncode != EXIT_CODE_SUCCESS:
|
|
15
|
+
raise RuntimeError(f"Command {cmd} failed with error: {stderr!r}")
|
|
16
|
+
logger.info(f"stdout: {stdout!r}")
|
|
17
|
+
logger.info(f"stderr: {stderr!r}")
|
flyte/_debug/vscode.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tarfile
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
import aiofiles
|
|
15
|
+
import click
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from flyte import storage
|
|
19
|
+
from flyte._debug.constants import (
|
|
20
|
+
DEFAULT_CODE_SERVER_EXTENSIONS,
|
|
21
|
+
DEFAULT_CODE_SERVER_REMOTE_PATHS,
|
|
22
|
+
DOWNLOAD_DIR,
|
|
23
|
+
EXECUTABLE_NAME,
|
|
24
|
+
EXIT_CODE_SUCCESS,
|
|
25
|
+
HEARTBEAT_PATH,
|
|
26
|
+
MAX_IDLE_SECONDS,
|
|
27
|
+
)
|
|
28
|
+
from flyte._debug.utils import (
|
|
29
|
+
execute_command,
|
|
30
|
+
)
|
|
31
|
+
from flyte._internal.runtime.rusty import download_tgz
|
|
32
|
+
from flyte._logging import logger
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def download_file(url: str, target_dir: str) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Downloads a file from a given URL using HTTPX and saves it locally.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
url (str): The URL of the file to download.
|
|
41
|
+
target_dir (str): The directory where the file should be saved. Defaults to current directory.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
filename = os.path.join(target_dir, os.path.basename(url))
|
|
45
|
+
if url.startswith("http"):
|
|
46
|
+
response = httpx.get(url, follow_redirects=True)
|
|
47
|
+
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
|
48
|
+
async with aiofiles.open(filename, "wb") as f:
|
|
49
|
+
await f.write(response.content)
|
|
50
|
+
else:
|
|
51
|
+
await storage.get(url, filename)
|
|
52
|
+
logger.info(f"File '{filename}' downloaded successfully from '{url}'.")
|
|
53
|
+
return filename
|
|
54
|
+
|
|
55
|
+
except httpx.RequestError as e:
|
|
56
|
+
raise RuntimeError(f"An error occurred while requesting '{url}': {e}")
|
|
57
|
+
except httpx.HTTPStatusError as e:
|
|
58
|
+
raise RuntimeError(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise RuntimeError(f"An unexpected error occurred: {e}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_default_extensions() -> List[str]:
|
|
64
|
+
extensions = os.getenv("_F_CS_E")
|
|
65
|
+
if extensions is not None:
|
|
66
|
+
return extensions.split(",")
|
|
67
|
+
return DEFAULT_CODE_SERVER_EXTENSIONS
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_code_server_info() -> str:
|
|
71
|
+
"""
|
|
72
|
+
Returns the code server information based on the system's architecture.
|
|
73
|
+
|
|
74
|
+
This function checks the system's architecture and returns the corresponding
|
|
75
|
+
code server information from the provided dictionary. The function currently
|
|
76
|
+
supports AMD64 and ARM64 architectures.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
str: The code server information corresponding to the system's architecture.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If the system's architecture is not AMD64 or ARM64.
|
|
83
|
+
"""
|
|
84
|
+
code_server_path = os.getenv("_F_CS_RP")
|
|
85
|
+
if code_server_path is not None:
|
|
86
|
+
return code_server_path
|
|
87
|
+
|
|
88
|
+
machine_info = platform.machine()
|
|
89
|
+
logger.info(f"machine type: {machine_info}")
|
|
90
|
+
code_server_info_dict = DEFAULT_CODE_SERVER_REMOTE_PATHS
|
|
91
|
+
|
|
92
|
+
if "aarch64" == machine_info:
|
|
93
|
+
return code_server_info_dict["arm64"]
|
|
94
|
+
elif "x86_64" == machine_info:
|
|
95
|
+
return code_server_info_dict["amd64"]
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"Automatic download is only supported on AMD64 and ARM64 architectures."
|
|
99
|
+
" If you are using a different architecture, please visit the code-server official website to"
|
|
100
|
+
" manually download the appropriate version for your image."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_installed_extensions() -> List[str]:
|
|
105
|
+
"""
|
|
106
|
+
Get the list of installed extensions.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List[str]: The list of installed extensions.
|
|
110
|
+
"""
|
|
111
|
+
installed_extensions = subprocess.run(
|
|
112
|
+
["code-server", "--list-extensions"], check=False, capture_output=True, text=True
|
|
113
|
+
)
|
|
114
|
+
if installed_extensions.returncode != EXIT_CODE_SUCCESS:
|
|
115
|
+
logger.info(f"Command code-server --list-extensions failed with error: {installed_extensions.stderr}")
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
return installed_extensions.stdout.splitlines()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_extension_installed(extension: str, installed_extensions: List[str]) -> bool:
|
|
122
|
+
return any(installed_extension in extension for installed_extension in installed_extensions)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def download_vscode():
|
|
126
|
+
"""
|
|
127
|
+
Download vscode server and extension from remote to local and add the directory of binary executable to $PATH.
|
|
128
|
+
"""
|
|
129
|
+
# If the code server already exists in the container, skip downloading
|
|
130
|
+
executable_path = shutil.which(EXECUTABLE_NAME)
|
|
131
|
+
if executable_path is not None or os.path.exists(DOWNLOAD_DIR):
|
|
132
|
+
logger.info(f"Code server binary already exists at {executable_path}")
|
|
133
|
+
logger.info("Skipping downloading code server...")
|
|
134
|
+
else:
|
|
135
|
+
logger.info("Code server is not in $PATH, start downloading code server...")
|
|
136
|
+
# Create DOWNLOAD_DIR if not exist
|
|
137
|
+
logger.info(f"DOWNLOAD_DIR: {DOWNLOAD_DIR}")
|
|
138
|
+
os.makedirs(DOWNLOAD_DIR)
|
|
139
|
+
|
|
140
|
+
logger.info(f"Start downloading files to {DOWNLOAD_DIR}")
|
|
141
|
+
# Download remote file to local
|
|
142
|
+
code_server_remote_path = get_code_server_info()
|
|
143
|
+
code_server_tar_path = await download_file(code_server_remote_path, str(DOWNLOAD_DIR))
|
|
144
|
+
|
|
145
|
+
# Extract the tarball
|
|
146
|
+
with tarfile.open(code_server_tar_path, "r:gz") as tar:
|
|
147
|
+
tar.extractall(path=DOWNLOAD_DIR)
|
|
148
|
+
|
|
149
|
+
if os.path.exists(DOWNLOAD_DIR):
|
|
150
|
+
code_server_dir_name = os.path.basename(get_code_server_info()).removesuffix(".tar.gz")
|
|
151
|
+
code_server_bin_dir = os.path.join(DOWNLOAD_DIR, code_server_dir_name, "bin")
|
|
152
|
+
# Add the directory of code-server binary to $PATH
|
|
153
|
+
os.environ["PATH"] = code_server_bin_dir + os.pathsep + os.environ["PATH"]
|
|
154
|
+
|
|
155
|
+
# If the extension already exists in the container, skip downloading
|
|
156
|
+
installed_extensions = get_installed_extensions()
|
|
157
|
+
coros = []
|
|
158
|
+
|
|
159
|
+
for extension in get_default_extensions():
|
|
160
|
+
if not is_extension_installed(extension, installed_extensions):
|
|
161
|
+
coros.append(download_file(extension, str(DOWNLOAD_DIR)))
|
|
162
|
+
extension_paths = await asyncio.gather(*coros)
|
|
163
|
+
|
|
164
|
+
coros = []
|
|
165
|
+
for p in extension_paths:
|
|
166
|
+
logger.info(f"Execute extension installation command to install extension {p}")
|
|
167
|
+
coros.append(execute_command(f"code-server --install-extension {p}"))
|
|
168
|
+
|
|
169
|
+
await asyncio.gather(*coros)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def prepare_launch_json(ctx: click.Context, pid: int):
|
|
173
|
+
"""
|
|
174
|
+
Generate the launch.json and settings.json for users to easily launch interactive debugging and task resumption.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
virtual_venv = os.getenv("VIRTUAL_ENV", str(Path(sys.executable).parent.parent))
|
|
178
|
+
if virtual_venv is None:
|
|
179
|
+
raise RuntimeError("VIRTUAL_ENV is not found in environment variables.")
|
|
180
|
+
|
|
181
|
+
run_name = ctx.params["run_name"]
|
|
182
|
+
name = ctx.params["name"]
|
|
183
|
+
# TODO: Executor should pass correct name.
|
|
184
|
+
if run_name.startswith("{{"):
|
|
185
|
+
run_name = os.getenv("RUN_NAME", "")
|
|
186
|
+
if name.startswith("{{"):
|
|
187
|
+
name = os.getenv("ACTION_NAME", "")
|
|
188
|
+
|
|
189
|
+
launch_json = {
|
|
190
|
+
"version": "0.2.0",
|
|
191
|
+
"configurations": [
|
|
192
|
+
{
|
|
193
|
+
"name": "Interactive Debugging",
|
|
194
|
+
"type": "python",
|
|
195
|
+
"request": "launch",
|
|
196
|
+
"program": f"{virtual_venv}/bin/runtime.py",
|
|
197
|
+
"console": "integratedTerminal",
|
|
198
|
+
"justMyCode": True,
|
|
199
|
+
"args": [
|
|
200
|
+
"a0",
|
|
201
|
+
"--inputs",
|
|
202
|
+
ctx.params["inputs"],
|
|
203
|
+
"--outputs-path",
|
|
204
|
+
ctx.params["outputs_path"],
|
|
205
|
+
"--version",
|
|
206
|
+
ctx.params["version"],
|
|
207
|
+
"--run-base-dir",
|
|
208
|
+
ctx.params["run_base_dir"],
|
|
209
|
+
"--name",
|
|
210
|
+
name,
|
|
211
|
+
"--run-name",
|
|
212
|
+
run_name,
|
|
213
|
+
"--project",
|
|
214
|
+
ctx.params["project"],
|
|
215
|
+
"--domain",
|
|
216
|
+
ctx.params["domain"],
|
|
217
|
+
"--org",
|
|
218
|
+
ctx.params["org"],
|
|
219
|
+
"--image-cache",
|
|
220
|
+
ctx.params["image_cache"],
|
|
221
|
+
"--debug",
|
|
222
|
+
"False",
|
|
223
|
+
"--interactive-mode",
|
|
224
|
+
"True",
|
|
225
|
+
"--tgz",
|
|
226
|
+
ctx.params["tgz"],
|
|
227
|
+
"--dest",
|
|
228
|
+
ctx.params["dest"],
|
|
229
|
+
"--resolver",
|
|
230
|
+
ctx.params["resolver"],
|
|
231
|
+
*ctx.params["resolver_args"],
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "Resume Task",
|
|
236
|
+
"type": "python",
|
|
237
|
+
"request": "launch",
|
|
238
|
+
"program": f"{virtual_venv}/bin/debug.py",
|
|
239
|
+
"console": "integratedTerminal",
|
|
240
|
+
"justMyCode": True,
|
|
241
|
+
"args": ["resume", "--pid", str(pid)],
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
vscode_directory = os.path.join(os.getcwd(), ".vscode")
|
|
247
|
+
if not os.path.exists(vscode_directory):
|
|
248
|
+
os.makedirs(vscode_directory)
|
|
249
|
+
|
|
250
|
+
with open(os.path.join(vscode_directory, "launch.json"), "w") as file:
|
|
251
|
+
json.dump(launch_json, file, indent=4)
|
|
252
|
+
|
|
253
|
+
settings_json = {
|
|
254
|
+
"python.defaultInterpreterPath": sys.executable,
|
|
255
|
+
"remote.autoForwardPorts": False,
|
|
256
|
+
"remote.autoForwardPortsFallback": 0,
|
|
257
|
+
}
|
|
258
|
+
with open(os.path.join(vscode_directory, "settings.json"), "w") as file:
|
|
259
|
+
json.dump(settings_json, file, indent=4)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def _start_vscode_server(ctx: click.Context):
|
|
263
|
+
if ctx.params["tgz"] is None:
|
|
264
|
+
await download_vscode()
|
|
265
|
+
else:
|
|
266
|
+
await asyncio.gather(
|
|
267
|
+
download_tgz(ctx.params["dest"], ctx.params["version"], ctx.params["tgz"]), download_vscode()
|
|
268
|
+
)
|
|
269
|
+
code_server_idle_timeout_seconds = os.getenv("CODE_SERVER_IDLE_TIMEOUT_SECONDS", str(MAX_IDLE_SECONDS))
|
|
270
|
+
child_process = multiprocessing.Process(
|
|
271
|
+
target=lambda cmd: asyncio.run(asyncio.run(execute_command(cmd))),
|
|
272
|
+
kwargs={
|
|
273
|
+
"cmd": f"code-server --bind-addr 0.0.0.0:6060 --idle-timeout-seconds {code_server_idle_timeout_seconds}"
|
|
274
|
+
f" --disable-workspace-trust --auth none {os.getcwd()}"
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
child_process.start()
|
|
278
|
+
if child_process.pid is None:
|
|
279
|
+
raise RuntimeError("Failed to start vscode server.")
|
|
280
|
+
|
|
281
|
+
prepare_launch_json(ctx, child_process.pid)
|
|
282
|
+
|
|
283
|
+
start_time = time.time()
|
|
284
|
+
check_interval = 60 # Interval for heartbeat checking in seconds
|
|
285
|
+
last_heartbeat_check = time.time() - check_interval
|
|
286
|
+
|
|
287
|
+
def terminate_process():
|
|
288
|
+
if child_process.is_alive():
|
|
289
|
+
child_process.terminate()
|
|
290
|
+
child_process.join()
|
|
291
|
+
|
|
292
|
+
logger.info("waiting for task to resume...")
|
|
293
|
+
while child_process.is_alive():
|
|
294
|
+
current_time = time.time()
|
|
295
|
+
if current_time - last_heartbeat_check >= check_interval:
|
|
296
|
+
last_heartbeat_check = current_time
|
|
297
|
+
if not os.path.exists(HEARTBEAT_PATH):
|
|
298
|
+
delta = current_time - start_time
|
|
299
|
+
logger.info(f"Code server has not been connected since {delta} seconds ago.")
|
|
300
|
+
logger.info("Please open the browser to connect to the running server.")
|
|
301
|
+
else:
|
|
302
|
+
delta = current_time - os.path.getmtime(HEARTBEAT_PATH)
|
|
303
|
+
logger.info(f"The latest activity on code server is {delta} seconds ago.")
|
|
304
|
+
|
|
305
|
+
# If the time from last connection is longer than max idle seconds, terminate the vscode server.
|
|
306
|
+
if delta > MAX_IDLE_SECONDS:
|
|
307
|
+
logger.info(f"VSCode server is idle for more than {MAX_IDLE_SECONDS} seconds. Terminating...")
|
|
308
|
+
terminate_process()
|
|
309
|
+
sys.exit()
|
|
310
|
+
|
|
311
|
+
await asyncio.sleep(1)
|
|
312
|
+
|
|
313
|
+
logger.info("User has resumed the task.")
|
|
314
|
+
terminate_process()
|
|
315
|
+
return
|