e2b 2.2.0__tar.gz → 2.2.2__tar.gz
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.
- {e2b-2.2.0 → e2b-2.2.2}/PKG-INFO +1 -1
- {e2b-2.2.0 → e2b-2.2.2}/e2b/__init__.py +3 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/consts.py +2 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/dockerfile_parser.py +4 -3
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/main.py +169 -126
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/types.py +8 -5
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/utils.py +35 -7
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template_async/build_api.py +13 -8
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template_async/main.py +4 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template_sync/build_api.py +12 -7
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template_sync/main.py +4 -0
- {e2b-2.2.0 → e2b-2.2.2}/pyproject.toml +1 -1
- {e2b-2.2.0 → e2b-2.2.2}/LICENSE +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/README.md +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_sandboxes.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/get_v2_sandboxes.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/post_sandboxes.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/delete_templates_template_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/get_templates.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/get_templates_template_id_files_hash.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/patch_templates_template_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/post_templates.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/post_templates_template_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/post_v2_templates.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/client.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/errors.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/aws_registry.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/aws_registry_type.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/build_log_entry.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/build_status_reason.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/created_access_token.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/created_team_api_key.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/disk_metrics.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/error.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/gcp_registry.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/gcp_registry_type.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/general_registry.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/general_registry_type.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/identifier_masking_details.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/listed_sandbox.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/log_level.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/new_access_token.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/new_sandbox.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/new_team_api_key.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/node.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/node_detail.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/node_metrics.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/node_status.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/node_status_change.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/resumed_sandbox.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_detail.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_log.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_log_entry.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_log_entry_fields.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_logs.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_metric.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandbox_state.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/sandboxes_with_metrics.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/team.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/team_api_key.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/team_metric.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/team_user.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build_file_upload.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build_request.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build_request_v2.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build_start_v2.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_build_status.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_step.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/template_update_request.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/models/update_team_api_key.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/py.typed +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/client/types.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/api/metadata.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/connection_config.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/api.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/filesystem/filesystem_connect.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/filesystem/filesystem_pb2.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/filesystem/filesystem_pb2.pyi +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/process/process_connect.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/process/process_pb2.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/process/process_pb2.pyi +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/rpc.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/envd/versions.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/exceptions.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/commands/command_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/commands/main.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/filesystem/filesystem.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/filesystem/watch_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/main.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/sandbox_api.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/signature.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox/utils.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/commands/command.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/commands/command_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/commands/pty.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/filesystem/filesystem.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/filesystem/watch_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/main.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/paginator.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/sandbox_api.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_async/utils.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/commands/command.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/commands/command_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/commands/pty.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/filesystem/filesystem.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/filesystem/watch_handle.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/main.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/paginator.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/sandbox_sync/sandbox_api.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/exceptions.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b/template/readycmd.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b_connect/__init__.py +0 -0
- {e2b-2.2.0 → e2b-2.2.2}/e2b_connect/client.py +0 -0
{e2b-2.2.0 → e2b-2.2.2}/PKG-INFO
RENAMED
|
@@ -71,6 +71,8 @@ from .sandbox_sync.paginator import SandboxPaginator
|
|
|
71
71
|
|
|
72
72
|
from .template.main import TemplateBase, TemplateClass
|
|
73
73
|
|
|
74
|
+
from .template.types import CopyItem
|
|
75
|
+
|
|
74
76
|
from .template_sync.main import Template
|
|
75
77
|
from .template_async.main import AsyncTemplate
|
|
76
78
|
|
|
@@ -136,6 +138,7 @@ __all__ = [
|
|
|
136
138
|
"AsyncTemplate",
|
|
137
139
|
"TemplateBase",
|
|
138
140
|
"TemplateClass",
|
|
141
|
+
"CopyItem",
|
|
139
142
|
"wait_for_file",
|
|
140
143
|
"wait_for_url",
|
|
141
144
|
"wait_for_port",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
import tempfile
|
|
4
|
-
from typing import Dict, List, Optional, Protocol, Union
|
|
4
|
+
from typing import Dict, List, Optional, Protocol, Union, Literal
|
|
5
5
|
|
|
6
6
|
from dockerfile_parse import DockerfileParser
|
|
7
7
|
from e2b.template.types import CopyItem
|
|
@@ -24,7 +24,8 @@ class DockerfileParserInterface(Protocol):
|
|
|
24
24
|
self,
|
|
25
25
|
src: Union[str, List[CopyItem]],
|
|
26
26
|
dest: Optional[str] = None,
|
|
27
|
-
force_upload: Optional[
|
|
27
|
+
force_upload: Optional[Literal[True]] = None,
|
|
28
|
+
resolve_symlinks: Optional[bool] = None,
|
|
28
29
|
user: Optional[str] = None,
|
|
29
30
|
mode: Optional[int] = None,
|
|
30
31
|
) -> "DockerfileParserInterface":
|
|
@@ -239,7 +240,7 @@ def _handle_env_instruction(
|
|
|
239
240
|
def _handle_cmd_entrypoint_instruction(
|
|
240
241
|
value: str, template_builder: DockerfileParserInterface
|
|
241
242
|
) -> None:
|
|
242
|
-
"""Handle CMD/ENTRYPOINT instruction - convert to
|
|
243
|
+
"""Handle CMD/ENTRYPOINT instruction - convert to set_start_cmd with 20s timeout"""
|
|
243
244
|
if not value.strip():
|
|
244
245
|
return
|
|
245
246
|
command = value.strip()
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Dict, List, Optional, Union
|
|
3
|
-
from
|
|
2
|
+
from typing import Dict, List, Optional, Union, Literal
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
4
5
|
from httpx import Limits
|
|
5
6
|
|
|
6
|
-
from e2b.template.consts import STACK_TRACE_DEPTH
|
|
7
|
+
from e2b.template.consts import STACK_TRACE_DEPTH, RESOLVE_SYMLINKS
|
|
7
8
|
from e2b.template.dockerfile_parser import parse_dockerfile
|
|
9
|
+
from e2b.template.readycmd import ReadyCmd
|
|
8
10
|
from e2b.template.types import (
|
|
9
11
|
CopyItem,
|
|
10
12
|
Instruction,
|
|
@@ -20,7 +22,7 @@ from e2b.template.utils import (
|
|
|
20
22
|
read_gcp_service_account_json,
|
|
21
23
|
get_caller_frame,
|
|
22
24
|
)
|
|
23
|
-
from
|
|
25
|
+
from types import TracebackType
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class TemplateBuilder:
|
|
@@ -29,62 +31,74 @@ class TemplateBuilder:
|
|
|
29
31
|
|
|
30
32
|
def copy(
|
|
31
33
|
self,
|
|
32
|
-
src: Union[str, List[
|
|
33
|
-
dest:
|
|
34
|
-
force_upload: Optional[
|
|
34
|
+
src: Union[Union[str, Path], List[Union[str, Path]]],
|
|
35
|
+
dest: Union[str, Path],
|
|
36
|
+
force_upload: Optional[Literal[True]] = None,
|
|
35
37
|
user: Optional[str] = None,
|
|
36
38
|
mode: Optional[int] = None,
|
|
39
|
+
resolve_symlinks: Optional[bool] = None,
|
|
37
40
|
) -> "TemplateBuilder":
|
|
38
|
-
if isinstance(src, str)
|
|
39
|
-
# Single copy operation
|
|
40
|
-
if dest is None:
|
|
41
|
-
raise ValueError("dest parameter is required when src is a string")
|
|
42
|
-
copy_items: List[CopyItem] = [
|
|
43
|
-
{
|
|
44
|
-
"src": src,
|
|
45
|
-
"dest": dest,
|
|
46
|
-
"forceUpload": force_upload,
|
|
47
|
-
"user": user,
|
|
48
|
-
"mode": mode,
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
else:
|
|
52
|
-
# Multiple copy operations
|
|
53
|
-
copy_items = src
|
|
41
|
+
srcs = [src] if isinstance(src, (str, Path)) else src
|
|
54
42
|
|
|
55
|
-
for
|
|
43
|
+
for src_item in srcs:
|
|
56
44
|
args = [
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
str(src_item),
|
|
46
|
+
str(dest),
|
|
59
47
|
user or "",
|
|
60
48
|
pad_octal(mode) if mode else "",
|
|
61
49
|
]
|
|
62
50
|
|
|
63
|
-
instruction: Instruction =
|
|
64
|
-
type
|
|
65
|
-
args
|
|
66
|
-
force
|
|
67
|
-
forceUpload
|
|
68
|
-
|
|
51
|
+
instruction: Instruction = {
|
|
52
|
+
"type": InstructionType.COPY,
|
|
53
|
+
"args": args,
|
|
54
|
+
"force": force_upload or self._template._force_next_layer,
|
|
55
|
+
"forceUpload": force_upload,
|
|
56
|
+
"resolveSymlinks": resolve_symlinks,
|
|
57
|
+
}
|
|
58
|
+
|
|
69
59
|
self._template._instructions.append(instruction)
|
|
60
|
+
|
|
70
61
|
self._template._collect_stack_trace()
|
|
71
62
|
return self
|
|
72
63
|
|
|
64
|
+
def copy_items(self, items: List[CopyItem]) -> "TemplateBuilder":
|
|
65
|
+
self._template._run_in_new_stack_trace_context(
|
|
66
|
+
lambda: [
|
|
67
|
+
self.copy(
|
|
68
|
+
item["src"],
|
|
69
|
+
item["dest"],
|
|
70
|
+
item.get("forceUpload"),
|
|
71
|
+
item.get("user"),
|
|
72
|
+
item.get("mode"),
|
|
73
|
+
item.get("resolveSymlinks"),
|
|
74
|
+
)
|
|
75
|
+
for item in items
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
return self
|
|
79
|
+
|
|
73
80
|
def remove(
|
|
74
|
-
self,
|
|
81
|
+
self,
|
|
82
|
+
path: Union[Union[str, Path], List[Union[str, Path]]],
|
|
83
|
+
force: bool = False,
|
|
84
|
+
recursive: bool = False,
|
|
75
85
|
) -> "TemplateBuilder":
|
|
76
|
-
|
|
86
|
+
paths = [path] if isinstance(path, (str, Path)) else path
|
|
87
|
+
args = ["rm"]
|
|
77
88
|
if recursive:
|
|
78
89
|
args.append("-r")
|
|
79
90
|
if force:
|
|
80
91
|
args.append("-f")
|
|
92
|
+
args.extend([str(p) for p in paths])
|
|
81
93
|
|
|
82
94
|
return self._template._run_in_new_stack_trace_context(
|
|
83
95
|
lambda: self.run_cmd(" ".join(args))
|
|
84
96
|
)
|
|
85
97
|
|
|
86
|
-
def rename(
|
|
87
|
-
|
|
98
|
+
def rename(
|
|
99
|
+
self, src: Union[str, Path], dest: Union[str, Path], force: bool = False
|
|
100
|
+
) -> "TemplateBuilder":
|
|
101
|
+
args = ["mv", str(src), str(dest)]
|
|
88
102
|
if force:
|
|
89
103
|
args.append("-f")
|
|
90
104
|
|
|
@@ -93,21 +107,24 @@ class TemplateBuilder:
|
|
|
93
107
|
)
|
|
94
108
|
|
|
95
109
|
def make_dir(
|
|
96
|
-
self,
|
|
110
|
+
self,
|
|
111
|
+
path: Union[Union[str, Path], List[Union[str, Path]]],
|
|
112
|
+
mode: Optional[int] = None,
|
|
97
113
|
) -> "TemplateBuilder":
|
|
98
|
-
if isinstance(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
args = ["mkdir", "-p", *paths]
|
|
114
|
+
path_list = [path] if isinstance(path, (str, Path)) else path
|
|
115
|
+
args = ["mkdir", "-p"]
|
|
102
116
|
if mode:
|
|
103
117
|
args.append(f"-m {pad_octal(mode)}")
|
|
118
|
+
args.extend([str(p) for p in path_list])
|
|
104
119
|
|
|
105
120
|
return self._template._run_in_new_stack_trace_context(
|
|
106
121
|
lambda: self.run_cmd(" ".join(args))
|
|
107
122
|
)
|
|
108
123
|
|
|
109
|
-
def make_symlink(
|
|
110
|
-
|
|
124
|
+
def make_symlink(
|
|
125
|
+
self, src: Union[str, Path], dest: Union[str, Path]
|
|
126
|
+
) -> "TemplateBuilder":
|
|
127
|
+
args = ["ln", "-s", str(src), str(dest)]
|
|
111
128
|
return self._template._run_in_new_stack_trace_context(
|
|
112
129
|
lambda: self.run_cmd(" ".join(args))
|
|
113
130
|
)
|
|
@@ -121,34 +138,34 @@ class TemplateBuilder:
|
|
|
121
138
|
if user:
|
|
122
139
|
args.append(user)
|
|
123
140
|
|
|
124
|
-
instruction: Instruction =
|
|
125
|
-
type
|
|
126
|
-
args
|
|
127
|
-
force
|
|
128
|
-
forceUpload
|
|
129
|
-
|
|
141
|
+
instruction: Instruction = {
|
|
142
|
+
"type": InstructionType.RUN,
|
|
143
|
+
"args": args,
|
|
144
|
+
"force": self._template._force_next_layer,
|
|
145
|
+
"forceUpload": None,
|
|
146
|
+
}
|
|
130
147
|
self._template._instructions.append(instruction)
|
|
131
148
|
self._template._collect_stack_trace()
|
|
132
149
|
return self
|
|
133
150
|
|
|
134
|
-
def set_workdir(self, workdir: str) -> "TemplateBuilder":
|
|
135
|
-
instruction: Instruction =
|
|
136
|
-
type
|
|
137
|
-
args
|
|
138
|
-
force
|
|
139
|
-
forceUpload
|
|
140
|
-
|
|
151
|
+
def set_workdir(self, workdir: Union[str, Path]) -> "TemplateBuilder":
|
|
152
|
+
instruction: Instruction = {
|
|
153
|
+
"type": InstructionType.WORKDIR,
|
|
154
|
+
"args": [str(workdir)],
|
|
155
|
+
"force": self._template._force_next_layer,
|
|
156
|
+
"forceUpload": None,
|
|
157
|
+
}
|
|
141
158
|
self._template._instructions.append(instruction)
|
|
142
159
|
self._template._collect_stack_trace()
|
|
143
160
|
return self
|
|
144
161
|
|
|
145
162
|
def set_user(self, user: str) -> "TemplateBuilder":
|
|
146
|
-
instruction: Instruction =
|
|
147
|
-
type
|
|
148
|
-
args
|
|
149
|
-
force
|
|
150
|
-
forceUpload
|
|
151
|
-
|
|
163
|
+
instruction: Instruction = {
|
|
164
|
+
"type": InstructionType.USER,
|
|
165
|
+
"args": [user],
|
|
166
|
+
"force": self._template._force_next_layer,
|
|
167
|
+
"forceUpload": None,
|
|
168
|
+
}
|
|
152
169
|
self._template._instructions.append(instruction)
|
|
153
170
|
self._template._collect_stack_trace()
|
|
154
171
|
return self
|
|
@@ -204,18 +221,18 @@ class TemplateBuilder:
|
|
|
204
221
|
def git_clone(
|
|
205
222
|
self,
|
|
206
223
|
url: str,
|
|
207
|
-
path: Optional[str] = None,
|
|
224
|
+
path: Optional[Union[str, Path]] = None,
|
|
208
225
|
branch: Optional[str] = None,
|
|
209
226
|
depth: Optional[int] = None,
|
|
210
227
|
) -> "TemplateBuilder":
|
|
211
228
|
args = ["git", "clone", url]
|
|
212
|
-
if path:
|
|
213
|
-
args.append(path)
|
|
214
229
|
if branch:
|
|
215
230
|
args.append(f"--branch {branch}")
|
|
216
231
|
args.append("--single-branch")
|
|
217
232
|
if depth:
|
|
218
233
|
args.append(f"--depth {depth}")
|
|
234
|
+
if path:
|
|
235
|
+
args.append(str(path))
|
|
219
236
|
return self._template._run_in_new_stack_trace_context(
|
|
220
237
|
lambda: self.run_cmd(" ".join(args))
|
|
221
238
|
)
|
|
@@ -224,12 +241,12 @@ class TemplateBuilder:
|
|
|
224
241
|
if len(envs) == 0:
|
|
225
242
|
return self
|
|
226
243
|
|
|
227
|
-
instruction: Instruction =
|
|
228
|
-
type
|
|
229
|
-
args
|
|
230
|
-
force
|
|
231
|
-
forceUpload
|
|
232
|
-
|
|
244
|
+
instruction: Instruction = {
|
|
245
|
+
"type": InstructionType.ENV,
|
|
246
|
+
"args": [item for key, value in envs.items() for item in [key, value]],
|
|
247
|
+
"force": self._template._force_next_layer,
|
|
248
|
+
"forceUpload": None,
|
|
249
|
+
}
|
|
233
250
|
self._template._instructions.append(instruction)
|
|
234
251
|
self._template._collect_stack_trace()
|
|
235
252
|
return self
|
|
@@ -274,8 +291,8 @@ class TemplateBase:
|
|
|
274
291
|
|
|
275
292
|
def __init__(
|
|
276
293
|
self,
|
|
277
|
-
file_context_path: Optional[str] = None,
|
|
278
|
-
|
|
294
|
+
file_context_path: Optional[Union[str, Path]] = None,
|
|
295
|
+
file_ignore_patterns: Optional[List[str]] = None,
|
|
279
296
|
):
|
|
280
297
|
self._default_base_image: str = "e2bdev/base"
|
|
281
298
|
self._base_image: Optional[str] = self._default_base_image
|
|
@@ -289,11 +306,13 @@ class TemplateBase:
|
|
|
289
306
|
self._force_next_layer: bool = False
|
|
290
307
|
self._instructions: List[Instruction] = []
|
|
291
308
|
# If no file_context_path is provided, use the caller's directory
|
|
292
|
-
self._file_context_path
|
|
293
|
-
file_context_path
|
|
309
|
+
self._file_context_path = (
|
|
310
|
+
file_context_path.as_posix()
|
|
311
|
+
if isinstance(file_context_path, Path)
|
|
312
|
+
else (file_context_path or get_caller_directory(STACK_TRACE_DEPTH) or ".")
|
|
294
313
|
)
|
|
295
|
-
self.
|
|
296
|
-
self._stack_traces: List[TracebackType] = []
|
|
314
|
+
self._file_ignore_patterns: List[str] = file_ignore_patterns or []
|
|
315
|
+
self._stack_traces: List[Union[TracebackType, None]] = []
|
|
297
316
|
self._stack_traces_enabled: bool = True
|
|
298
317
|
|
|
299
318
|
def skip_cache(self) -> "TemplateBase":
|
|
@@ -302,7 +321,7 @@ class TemplateBase:
|
|
|
302
321
|
return self
|
|
303
322
|
|
|
304
323
|
def _collect_stack_trace(
|
|
305
|
-
self, stack_traces_depth:
|
|
324
|
+
self, stack_traces_depth: int = STACK_TRACE_DEPTH
|
|
306
325
|
) -> "TemplateBase":
|
|
307
326
|
"""Collect stack trace if enabled"""
|
|
308
327
|
if not self._stack_traces_enabled:
|
|
@@ -369,15 +388,22 @@ class TemplateBase:
|
|
|
369
388
|
)
|
|
370
389
|
|
|
371
390
|
def from_image(
|
|
372
|
-
self,
|
|
391
|
+
self,
|
|
392
|
+
image: str,
|
|
393
|
+
username: Optional[str] = None,
|
|
394
|
+
password: Optional[str] = None,
|
|
373
395
|
) -> TemplateBuilder:
|
|
374
396
|
"""Private method to set base image without adding stack trace"""
|
|
375
|
-
self._base_image =
|
|
397
|
+
self._base_image = image
|
|
376
398
|
self._base_template = None
|
|
377
399
|
|
|
378
400
|
# Set the registry config if provided
|
|
379
|
-
if
|
|
380
|
-
self._registry_config =
|
|
401
|
+
if username and password:
|
|
402
|
+
self._registry_config = {
|
|
403
|
+
"type": "registry",
|
|
404
|
+
"username": username,
|
|
405
|
+
"password": password,
|
|
406
|
+
}
|
|
381
407
|
|
|
382
408
|
# If we should force the next layer and it's a FROM command, invalidate whole template
|
|
383
409
|
if self._force_next_layer:
|
|
@@ -418,20 +444,6 @@ class TemplateBase:
|
|
|
418
444
|
self._collect_stack_trace()
|
|
419
445
|
return builder
|
|
420
446
|
|
|
421
|
-
def from_registry(
|
|
422
|
-
self, image: str, username: str, password: str
|
|
423
|
-
) -> TemplateBuilder:
|
|
424
|
-
return self._run_in_new_stack_trace_context(
|
|
425
|
-
lambda: self.from_image(
|
|
426
|
-
image,
|
|
427
|
-
registry_config={
|
|
428
|
-
"type": "registry",
|
|
429
|
-
"username": username,
|
|
430
|
-
"password": password,
|
|
431
|
-
},
|
|
432
|
-
)
|
|
433
|
-
)
|
|
434
|
-
|
|
435
447
|
def from_aws_registry(
|
|
436
448
|
self,
|
|
437
449
|
image: str,
|
|
@@ -439,32 +451,44 @@ class TemplateBase:
|
|
|
439
451
|
secret_access_key: str,
|
|
440
452
|
region: str,
|
|
441
453
|
) -> TemplateBuilder:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
454
|
+
self._base_image = image
|
|
455
|
+
self._base_template = None
|
|
456
|
+
|
|
457
|
+
# Set the registry config if provided
|
|
458
|
+
self._registry_config = {
|
|
459
|
+
"type": "aws",
|
|
460
|
+
"awsAccessKeyId": access_key_id,
|
|
461
|
+
"awsSecretAccessKey": secret_access_key,
|
|
462
|
+
"awsRegion": region,
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# If we should force the next layer and it's a FROM command, invalidate whole template
|
|
466
|
+
if self._force_next_layer:
|
|
467
|
+
self._force = True
|
|
468
|
+
|
|
469
|
+
self._collect_stack_trace()
|
|
470
|
+
return TemplateBuilder(self)
|
|
453
471
|
|
|
454
472
|
def from_gcp_registry(
|
|
455
473
|
self, image: str, service_account_json: Union[str, dict]
|
|
456
474
|
) -> TemplateBuilder:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
475
|
+
self._base_image = image
|
|
476
|
+
self._base_template = None
|
|
477
|
+
|
|
478
|
+
# Set the registry config if provided
|
|
479
|
+
self._registry_config = {
|
|
480
|
+
"type": "gcp",
|
|
481
|
+
"serviceAccountJson": read_gcp_service_account_json(
|
|
482
|
+
self._file_context_path, service_account_json
|
|
483
|
+
),
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# If we should force the next layer and it's a FROM command, invalidate whole template
|
|
487
|
+
if self._force_next_layer:
|
|
488
|
+
self._force = True
|
|
489
|
+
|
|
490
|
+
self._collect_stack_trace()
|
|
491
|
+
return TemplateBuilder(self)
|
|
468
492
|
|
|
469
493
|
@staticmethod
|
|
470
494
|
def to_json(template: "TemplateClass") -> str:
|
|
@@ -504,12 +528,13 @@ class TemplateBase:
|
|
|
504
528
|
steps: List[Instruction] = []
|
|
505
529
|
|
|
506
530
|
for index, instruction in enumerate(self._instructions):
|
|
507
|
-
step: Instruction =
|
|
508
|
-
type
|
|
509
|
-
args
|
|
510
|
-
force
|
|
511
|
-
forceUpload
|
|
512
|
-
|
|
531
|
+
step: Instruction = {
|
|
532
|
+
"type": instruction["type"],
|
|
533
|
+
"args": instruction["args"],
|
|
534
|
+
"force": instruction["force"],
|
|
535
|
+
"forceUpload": instruction.get("forceUpload"),
|
|
536
|
+
"resolveSymlinks": instruction.get("resolveSymlinks"),
|
|
537
|
+
}
|
|
513
538
|
|
|
514
539
|
if instruction["type"] == InstructionType.COPY:
|
|
515
540
|
stack_trace = None
|
|
@@ -527,9 +552,10 @@ class TemplateBase:
|
|
|
527
552
|
dest,
|
|
528
553
|
self._file_context_path,
|
|
529
554
|
[
|
|
530
|
-
*self.
|
|
555
|
+
*self._file_ignore_patterns,
|
|
531
556
|
*read_dockerignore(self._file_context_path),
|
|
532
557
|
],
|
|
558
|
+
instruction.get("resolveSymlinks", RESOLVE_SYMLINKS),
|
|
533
559
|
stack_trace,
|
|
534
560
|
)
|
|
535
561
|
|
|
@@ -538,8 +564,25 @@ class TemplateBase:
|
|
|
538
564
|
return steps
|
|
539
565
|
|
|
540
566
|
def _serialize(self, steps: List[Instruction]) -> TemplateType:
|
|
567
|
+
_steps: List[Instruction] = []
|
|
568
|
+
|
|
569
|
+
for _, instruction in enumerate(steps):
|
|
570
|
+
step: Instruction = {
|
|
571
|
+
"type": instruction.get("type"),
|
|
572
|
+
"args": instruction.get("args"),
|
|
573
|
+
"force": instruction.get("force"),
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if instruction.get("filesHash") is not None:
|
|
577
|
+
step["filesHash"] = instruction["filesHash"]
|
|
578
|
+
|
|
579
|
+
if instruction.get("forceUpload") is not None:
|
|
580
|
+
step["forceUpload"] = instruction["forceUpload"]
|
|
581
|
+
|
|
582
|
+
_steps.append(step)
|
|
583
|
+
|
|
541
584
|
template_data: TemplateType = {
|
|
542
|
-
"steps":
|
|
585
|
+
"steps": _steps,
|
|
543
586
|
"force": self._force,
|
|
544
587
|
}
|
|
545
588
|
|
|
@@ -4,10 +4,11 @@ from dataclasses import dataclass
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from typing import Literal
|
|
6
6
|
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from e2b.template.utils import strip_ansi_escape_codes
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class InstructionType(Enum):
|
|
11
|
+
class InstructionType(str, Enum):
|
|
11
12
|
COPY = "COPY"
|
|
12
13
|
ENV = "ENV"
|
|
13
14
|
RUN = "RUN"
|
|
@@ -16,19 +17,21 @@ class InstructionType(Enum):
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class CopyItem(TypedDict):
|
|
19
|
-
src: str
|
|
20
|
-
dest: str
|
|
21
|
-
forceUpload: NotRequired[Optional[
|
|
20
|
+
src: Union[Union[str, Path], List[Union[str, Path]]]
|
|
21
|
+
dest: Union[str, Path]
|
|
22
|
+
forceUpload: NotRequired[Optional[Literal[True]]]
|
|
22
23
|
user: NotRequired[Optional[str]]
|
|
23
24
|
mode: NotRequired[Optional[int]]
|
|
25
|
+
resolveSymlinks: NotRequired[Optional[bool]]
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class Instruction(TypedDict):
|
|
27
29
|
type: InstructionType
|
|
28
30
|
args: List[str]
|
|
29
31
|
force: bool
|
|
30
|
-
forceUpload: NotRequired[Optional[
|
|
32
|
+
forceUpload: NotRequired[Optional[Literal[True]]]
|
|
31
33
|
filesHash: NotRequired[Optional[str]]
|
|
34
|
+
resolveSymlinks: NotRequired[Optional[bool]]
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
@dataclass
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import os
|
|
3
3
|
import json
|
|
4
|
+
import stat
|
|
4
5
|
from glob import glob
|
|
5
6
|
import fnmatch
|
|
6
7
|
import re
|
|
@@ -30,8 +31,9 @@ def calculate_files_hash(
|
|
|
30
31
|
src: str,
|
|
31
32
|
dest: str,
|
|
32
33
|
context_path: str,
|
|
33
|
-
ignore_patterns:
|
|
34
|
-
|
|
34
|
+
ignore_patterns: List[str],
|
|
35
|
+
resolve_symlinks: bool,
|
|
36
|
+
stack_trace: Optional[TracebackType],
|
|
35
37
|
) -> str:
|
|
36
38
|
src_path = os.path.join(context_path, src)
|
|
37
39
|
hash_obj = hashlib.sha256()
|
|
@@ -52,12 +54,38 @@ def calculate_files_hash(
|
|
|
52
54
|
if len(files) == 0:
|
|
53
55
|
raise ValueError(f"No files found in {src_path}").with_traceback(stack_trace)
|
|
54
56
|
|
|
57
|
+
def hash_stats(stat_info: os.stat_result) -> None:
|
|
58
|
+
hash_obj.update(str(stat_info.st_mode).encode())
|
|
59
|
+
hash_obj.update(str(stat_info.st_uid).encode())
|
|
60
|
+
hash_obj.update(str(stat_info.st_gid).encode())
|
|
61
|
+
hash_obj.update(str(stat_info.st_size).encode())
|
|
62
|
+
hash_obj.update(str(stat_info.st_mtime).encode())
|
|
63
|
+
|
|
55
64
|
for file in files:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
# Add a relative path to hash calculation
|
|
66
|
+
relative_path = os.path.relpath(file, context_path)
|
|
67
|
+
hash_obj.update(relative_path.encode())
|
|
68
|
+
|
|
69
|
+
# Add stat information to hash calculation
|
|
70
|
+
if os.path.islink(file):
|
|
71
|
+
stats = os.lstat(file)
|
|
72
|
+
should_follow = resolve_symlinks and (
|
|
73
|
+
os.path.isfile(file) or os.path.isdir(file)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if not should_follow:
|
|
77
|
+
hash_stats(stats)
|
|
78
|
+
|
|
79
|
+
content = os.readlink(file)
|
|
80
|
+
hash_obj.update(content.encode())
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
stats = os.stat(file)
|
|
84
|
+
hash_stats(stats)
|
|
85
|
+
|
|
86
|
+
if stat.S_ISREG(stats.st_mode):
|
|
87
|
+
with open(file, "rb") as f:
|
|
88
|
+
hash_obj.update(f.read())
|
|
61
89
|
|
|
62
90
|
return hash_obj.hexdigest()
|
|
63
91
|
|