moru 0.1.0__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.
- moru/__init__.py +174 -0
- moru/api/__init__.py +164 -0
- moru/api/client/__init__.py +8 -0
- moru/api/client/api/__init__.py +1 -0
- moru/api/client/api/sandboxes/__init__.py +1 -0
- moru/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- moru/api/client/api/sandboxes/get_sandboxes.py +176 -0
- moru/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
- moru/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
- moru/api/client/api/sandboxes/post_sandboxes.py +172 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
- moru/api/client/api/templates/__init__.py +1 -0
- moru/api/client/api/templates/delete_templates_template_id.py +157 -0
- moru/api/client/api/templates/get_templates.py +172 -0
- moru/api/client/api/templates/get_templates_template_id.py +195 -0
- moru/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
- moru/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
- moru/api/client/api/templates/patch_templates_template_id.py +183 -0
- moru/api/client/api/templates/post_templates.py +172 -0
- moru/api/client/api/templates/post_templates_template_id.py +181 -0
- moru/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
- moru/api/client/api/templates/post_v2_templates.py +172 -0
- moru/api/client/api/templates/post_v3_templates.py +172 -0
- moru/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
- moru/api/client/client.py +286 -0
- moru/api/client/errors.py +16 -0
- moru/api/client/models/__init__.py +123 -0
- moru/api/client/models/aws_registry.py +85 -0
- moru/api/client/models/aws_registry_type.py +8 -0
- moru/api/client/models/build_log_entry.py +89 -0
- moru/api/client/models/build_status_reason.py +95 -0
- moru/api/client/models/connect_sandbox.py +59 -0
- moru/api/client/models/created_access_token.py +100 -0
- moru/api/client/models/created_team_api_key.py +166 -0
- moru/api/client/models/disk_metrics.py +91 -0
- moru/api/client/models/error.py +67 -0
- moru/api/client/models/gcp_registry.py +69 -0
- moru/api/client/models/gcp_registry_type.py +8 -0
- moru/api/client/models/general_registry.py +77 -0
- moru/api/client/models/general_registry_type.py +8 -0
- moru/api/client/models/identifier_masking_details.py +83 -0
- moru/api/client/models/listed_sandbox.py +154 -0
- moru/api/client/models/log_level.py +11 -0
- moru/api/client/models/max_team_metric.py +78 -0
- moru/api/client/models/mcp_type_0.py +44 -0
- moru/api/client/models/new_access_token.py +59 -0
- moru/api/client/models/new_sandbox.py +172 -0
- moru/api/client/models/new_team_api_key.py +59 -0
- moru/api/client/models/node.py +155 -0
- moru/api/client/models/node_detail.py +165 -0
- moru/api/client/models/node_metrics.py +122 -0
- moru/api/client/models/node_status.py +11 -0
- moru/api/client/models/node_status_change.py +79 -0
- moru/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- moru/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- moru/api/client/models/resumed_sandbox.py +68 -0
- moru/api/client/models/sandbox.py +145 -0
- moru/api/client/models/sandbox_detail.py +183 -0
- moru/api/client/models/sandbox_log.py +70 -0
- moru/api/client/models/sandbox_log_entry.py +93 -0
- moru/api/client/models/sandbox_log_entry_fields.py +44 -0
- moru/api/client/models/sandbox_logs.py +91 -0
- moru/api/client/models/sandbox_metric.py +118 -0
- moru/api/client/models/sandbox_network_config.py +92 -0
- moru/api/client/models/sandbox_state.py +9 -0
- moru/api/client/models/sandboxes_with_metrics.py +59 -0
- moru/api/client/models/team.py +83 -0
- moru/api/client/models/team_api_key.py +158 -0
- moru/api/client/models/team_metric.py +86 -0
- moru/api/client/models/team_user.py +68 -0
- moru/api/client/models/template.py +217 -0
- moru/api/client/models/template_build.py +139 -0
- moru/api/client/models/template_build_file_upload.py +70 -0
- moru/api/client/models/template_build_info.py +126 -0
- moru/api/client/models/template_build_request.py +115 -0
- moru/api/client/models/template_build_request_v2.py +88 -0
- moru/api/client/models/template_build_request_v3.py +88 -0
- moru/api/client/models/template_build_start_v2.py +184 -0
- moru/api/client/models/template_build_status.py +11 -0
- moru/api/client/models/template_legacy.py +207 -0
- moru/api/client/models/template_request_response_v3.py +83 -0
- moru/api/client/models/template_step.py +91 -0
- moru/api/client/models/template_update_request.py +59 -0
- moru/api/client/models/template_with_builds.py +148 -0
- moru/api/client/models/update_team_api_key.py +59 -0
- moru/api/client/py.typed +1 -0
- moru/api/client/types.py +54 -0
- moru/api/client_async/__init__.py +50 -0
- moru/api/client_sync/__init__.py +52 -0
- moru/api/metadata.py +14 -0
- moru/connection_config.py +217 -0
- moru/envd/api.py +59 -0
- moru/envd/filesystem/filesystem_connect.py +193 -0
- moru/envd/filesystem/filesystem_pb2.py +76 -0
- moru/envd/filesystem/filesystem_pb2.pyi +233 -0
- moru/envd/process/process_connect.py +155 -0
- moru/envd/process/process_pb2.py +92 -0
- moru/envd/process/process_pb2.pyi +304 -0
- moru/envd/rpc.py +61 -0
- moru/envd/versions.py +6 -0
- moru/exceptions.py +95 -0
- moru/sandbox/commands/command_handle.py +69 -0
- moru/sandbox/commands/main.py +39 -0
- moru/sandbox/filesystem/filesystem.py +94 -0
- moru/sandbox/filesystem/watch_handle.py +60 -0
- moru/sandbox/main.py +210 -0
- moru/sandbox/mcp.py +1120 -0
- moru/sandbox/network.py +8 -0
- moru/sandbox/sandbox_api.py +210 -0
- moru/sandbox/signature.py +45 -0
- moru/sandbox/utils.py +34 -0
- moru/sandbox_async/commands/command.py +336 -0
- moru/sandbox_async/commands/command_handle.py +196 -0
- moru/sandbox_async/commands/pty.py +240 -0
- moru/sandbox_async/filesystem/filesystem.py +531 -0
- moru/sandbox_async/filesystem/watch_handle.py +62 -0
- moru/sandbox_async/main.py +734 -0
- moru/sandbox_async/paginator.py +69 -0
- moru/sandbox_async/sandbox_api.py +325 -0
- moru/sandbox_async/utils.py +7 -0
- moru/sandbox_sync/commands/command.py +328 -0
- moru/sandbox_sync/commands/command_handle.py +150 -0
- moru/sandbox_sync/commands/pty.py +230 -0
- moru/sandbox_sync/filesystem/filesystem.py +518 -0
- moru/sandbox_sync/filesystem/watch_handle.py +69 -0
- moru/sandbox_sync/main.py +726 -0
- moru/sandbox_sync/paginator.py +69 -0
- moru/sandbox_sync/sandbox_api.py +308 -0
- moru/template/consts.py +30 -0
- moru/template/dockerfile_parser.py +275 -0
- moru/template/logger.py +232 -0
- moru/template/main.py +1360 -0
- moru/template/readycmd.py +138 -0
- moru/template/types.py +105 -0
- moru/template/utils.py +320 -0
- moru/template_async/build_api.py +202 -0
- moru/template_async/main.py +366 -0
- moru/template_sync/build_api.py +199 -0
- moru/template_sync/main.py +371 -0
- moru-0.1.0.dist-info/METADATA +63 -0
- moru-0.1.0.dist-info/RECORD +152 -0
- moru-0.1.0.dist-info/WHEEL +4 -0
- moru-0.1.0.dist-info/licenses/LICENSE +9 -0
- moru_connect/__init__.py +1 -0
- moru_connect/client.py +493 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from types import TracebackType
|
|
3
|
+
from typing import Callable, Literal, Optional, List, Union
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from moru.api import handle_api_exception
|
|
8
|
+
from moru.api.client.api.templates import (
|
|
9
|
+
post_v3_templates,
|
|
10
|
+
get_templates_template_id_files_hash,
|
|
11
|
+
post_v_2_templates_template_id_builds_build_id,
|
|
12
|
+
get_templates_template_id_builds_build_id_status,
|
|
13
|
+
)
|
|
14
|
+
from moru.api.client.client import AuthenticatedClient
|
|
15
|
+
from moru.api.client.models import (
|
|
16
|
+
TemplateBuildRequestV3,
|
|
17
|
+
TemplateBuildStartV2,
|
|
18
|
+
TemplateBuildFileUpload,
|
|
19
|
+
TemplateBuild,
|
|
20
|
+
Error,
|
|
21
|
+
)
|
|
22
|
+
from moru.exceptions import BuildException, FileUploadException
|
|
23
|
+
from moru.template.logger import LogEntry
|
|
24
|
+
from moru.template.types import TemplateType
|
|
25
|
+
from moru.template.utils import get_build_step_index, tar_file_stream
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def request_build(
|
|
29
|
+
client: AuthenticatedClient, name: str, cpu_count: int, memory_mb: int
|
|
30
|
+
):
|
|
31
|
+
res = await post_v3_templates.asyncio_detailed(
|
|
32
|
+
client=client,
|
|
33
|
+
body=TemplateBuildRequestV3(
|
|
34
|
+
alias=name,
|
|
35
|
+
cpu_count=cpu_count,
|
|
36
|
+
memory_mb=memory_mb,
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if res.status_code >= 300:
|
|
41
|
+
raise handle_api_exception(res, BuildException)
|
|
42
|
+
|
|
43
|
+
if isinstance(res.parsed, Error):
|
|
44
|
+
raise BuildException(f"API error: {res.parsed.message}")
|
|
45
|
+
|
|
46
|
+
if res.parsed is None:
|
|
47
|
+
raise BuildException("Failed to request build")
|
|
48
|
+
|
|
49
|
+
return res.parsed
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def get_file_upload_link(
|
|
53
|
+
client: AuthenticatedClient,
|
|
54
|
+
template_id: str,
|
|
55
|
+
files_hash: str,
|
|
56
|
+
stack_trace: Optional[TracebackType] = None,
|
|
57
|
+
) -> TemplateBuildFileUpload:
|
|
58
|
+
res = await get_templates_template_id_files_hash.asyncio_detailed(
|
|
59
|
+
template_id=template_id,
|
|
60
|
+
hash_=files_hash,
|
|
61
|
+
client=client,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if res.status_code >= 300:
|
|
65
|
+
raise handle_api_exception(res, FileUploadException, stack_trace)
|
|
66
|
+
|
|
67
|
+
if isinstance(res.parsed, Error):
|
|
68
|
+
raise FileUploadException(f"API error: {res.parsed.message}").with_traceback(
|
|
69
|
+
stack_trace
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if res.parsed is None:
|
|
73
|
+
raise FileUploadException("Failed to get file upload link").with_traceback(
|
|
74
|
+
stack_trace
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return res.parsed
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def upload_file(
|
|
81
|
+
api_client: AuthenticatedClient,
|
|
82
|
+
file_name: str,
|
|
83
|
+
context_path: str,
|
|
84
|
+
url: str,
|
|
85
|
+
ignore_patterns: List[str],
|
|
86
|
+
resolve_symlinks: bool,
|
|
87
|
+
stack_trace: Optional[TracebackType],
|
|
88
|
+
):
|
|
89
|
+
try:
|
|
90
|
+
tar_buffer = tar_file_stream(
|
|
91
|
+
file_name, context_path, ignore_patterns, resolve_symlinks
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
client = api_client.get_async_httpx_client()
|
|
95
|
+
response = await client.put(url, content=tar_buffer.getvalue())
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
except httpx.HTTPStatusError as e:
|
|
98
|
+
raise FileUploadException(f"Failed to upload file: {e}").with_traceback(
|
|
99
|
+
stack_trace
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise FileUploadException(f"Failed to upload file: {e}").with_traceback(
|
|
103
|
+
stack_trace
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def trigger_build(
|
|
108
|
+
client: AuthenticatedClient,
|
|
109
|
+
template_id: str,
|
|
110
|
+
build_id: str,
|
|
111
|
+
template: TemplateType,
|
|
112
|
+
) -> None:
|
|
113
|
+
# Convert template dict to TemplateBuildStartV2 model using from_dict
|
|
114
|
+
template_data = TemplateBuildStartV2.from_dict(template)
|
|
115
|
+
|
|
116
|
+
res = await post_v_2_templates_template_id_builds_build_id.asyncio_detailed(
|
|
117
|
+
template_id=template_id,
|
|
118
|
+
build_id=build_id,
|
|
119
|
+
client=client,
|
|
120
|
+
body=template_data,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if res.status_code >= 300:
|
|
124
|
+
raise handle_api_exception(res, BuildException)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def get_build_status(
|
|
128
|
+
client: AuthenticatedClient, template_id: str, build_id: str, logs_offset: int
|
|
129
|
+
) -> TemplateBuild:
|
|
130
|
+
res = await get_templates_template_id_builds_build_id_status.asyncio_detailed(
|
|
131
|
+
template_id=template_id,
|
|
132
|
+
build_id=build_id,
|
|
133
|
+
client=client,
|
|
134
|
+
logs_offset=logs_offset,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if res.status_code >= 300:
|
|
138
|
+
raise handle_api_exception(res, BuildException)
|
|
139
|
+
|
|
140
|
+
if isinstance(res.parsed, Error):
|
|
141
|
+
raise BuildException(f"API error: {res.parsed.message}")
|
|
142
|
+
|
|
143
|
+
if res.parsed is None:
|
|
144
|
+
raise BuildException("Failed to get build status")
|
|
145
|
+
|
|
146
|
+
return res.parsed
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def wait_for_build_finish(
|
|
150
|
+
client: AuthenticatedClient,
|
|
151
|
+
template_id: str,
|
|
152
|
+
build_id: str,
|
|
153
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
154
|
+
logs_refresh_frequency: float = 0.2,
|
|
155
|
+
stack_traces: List[Union[TracebackType, None]] = [],
|
|
156
|
+
):
|
|
157
|
+
logs_offset = 0
|
|
158
|
+
status: Literal["building", "waiting", "ready", "error"] = "building"
|
|
159
|
+
|
|
160
|
+
while status in ["building", "waiting"]:
|
|
161
|
+
build_status = await get_build_status(
|
|
162
|
+
client, template_id, build_id, logs_offset
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
logs_offset += len(build_status.log_entries)
|
|
166
|
+
|
|
167
|
+
for log_entry in build_status.log_entries:
|
|
168
|
+
if on_build_logs:
|
|
169
|
+
on_build_logs(
|
|
170
|
+
LogEntry(
|
|
171
|
+
timestamp=log_entry.timestamp,
|
|
172
|
+
level=log_entry.level.value,
|
|
173
|
+
message=log_entry.message,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
status = build_status.status.value
|
|
178
|
+
|
|
179
|
+
if status == "ready":
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
elif status == "waiting":
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
elif status == "error":
|
|
186
|
+
traceback = None
|
|
187
|
+
if build_status.reason and build_status.reason.step:
|
|
188
|
+
# Find the corresponding stack trace for the failed step
|
|
189
|
+
step_index = get_build_step_index(
|
|
190
|
+
build_status.reason.step, len(stack_traces)
|
|
191
|
+
)
|
|
192
|
+
if step_index < len(stack_traces):
|
|
193
|
+
traceback = stack_traces[step_index]
|
|
194
|
+
|
|
195
|
+
raise BuildException(
|
|
196
|
+
build_status.reason.message if build_status.reason else "Build failed"
|
|
197
|
+
).with_traceback(traceback)
|
|
198
|
+
|
|
199
|
+
# Wait for a short period before checking the status again
|
|
200
|
+
await asyncio.sleep(logs_refresh_frequency)
|
|
201
|
+
|
|
202
|
+
raise BuildException("Unknown build error occurred.")
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
from moru.api.client.client import AuthenticatedClient
|
|
6
|
+
from moru.connection_config import ConnectionConfig
|
|
7
|
+
from moru.template.consts import RESOLVE_SYMLINKS
|
|
8
|
+
from moru.template.logger import LogEntry, LogEntryEnd, LogEntryStart
|
|
9
|
+
from moru.template.main import TemplateBase, TemplateClass
|
|
10
|
+
from moru.template.types import BuildInfo, InstructionType
|
|
11
|
+
from moru.template.utils import read_dockerignore
|
|
12
|
+
|
|
13
|
+
from .build_api import (
|
|
14
|
+
get_build_status,
|
|
15
|
+
get_file_upload_link,
|
|
16
|
+
request_build,
|
|
17
|
+
trigger_build,
|
|
18
|
+
upload_file,
|
|
19
|
+
wait_for_build_finish,
|
|
20
|
+
)
|
|
21
|
+
from moru.api.client_async import get_api_client
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncTemplate(TemplateBase):
|
|
25
|
+
"""
|
|
26
|
+
Asynchronous template builder for Moru sandboxes.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
async def _build(
|
|
31
|
+
template: TemplateClass,
|
|
32
|
+
api_client: AuthenticatedClient,
|
|
33
|
+
alias: str,
|
|
34
|
+
cpu_count: int = 2,
|
|
35
|
+
memory_mb: int = 1024,
|
|
36
|
+
skip_cache: bool = False,
|
|
37
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
38
|
+
) -> BuildInfo:
|
|
39
|
+
"""
|
|
40
|
+
Internal implementation of the template build process
|
|
41
|
+
|
|
42
|
+
:param template: The template to build
|
|
43
|
+
:param api_client: Authenticated API client
|
|
44
|
+
:param alias: Alias name for the template
|
|
45
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
46
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
47
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
48
|
+
:param on_build_logs: Callback function to receive build logs during the build process
|
|
49
|
+
"""
|
|
50
|
+
if skip_cache:
|
|
51
|
+
template._template._force = True
|
|
52
|
+
|
|
53
|
+
# Create template
|
|
54
|
+
if on_build_logs:
|
|
55
|
+
on_build_logs(
|
|
56
|
+
LogEntry(
|
|
57
|
+
timestamp=datetime.now(),
|
|
58
|
+
level="info",
|
|
59
|
+
message=f"Requesting build for template: {alias}",
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
response = await request_build(
|
|
64
|
+
api_client,
|
|
65
|
+
name=alias,
|
|
66
|
+
cpu_count=cpu_count,
|
|
67
|
+
memory_mb=memory_mb,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
template_id = response.template_id
|
|
71
|
+
build_id = response.build_id
|
|
72
|
+
|
|
73
|
+
if on_build_logs:
|
|
74
|
+
on_build_logs(
|
|
75
|
+
LogEntry(
|
|
76
|
+
timestamp=datetime.now(),
|
|
77
|
+
level="info",
|
|
78
|
+
message=f"Template created with ID: {template_id}, Build ID: {build_id}",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
instructions_with_hashes = template._template._instructions_with_hashes()
|
|
83
|
+
|
|
84
|
+
# Upload files
|
|
85
|
+
for index, file_upload in enumerate(instructions_with_hashes):
|
|
86
|
+
if file_upload["type"] != InstructionType.COPY:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
args = file_upload.get("args", [])
|
|
90
|
+
src = args[0] if len(args) > 0 else None
|
|
91
|
+
force_upload = file_upload.get("forceUpload")
|
|
92
|
+
files_hash = file_upload.get("filesHash", None)
|
|
93
|
+
resolve_symlinks = file_upload.get("resolveSymlinks", RESOLVE_SYMLINKS)
|
|
94
|
+
|
|
95
|
+
if src is None or files_hash is None:
|
|
96
|
+
raise ValueError("Source path and files hash are required")
|
|
97
|
+
|
|
98
|
+
stack_trace = None
|
|
99
|
+
if index + 1 < len(template._template._stack_traces):
|
|
100
|
+
stack_trace = template._template._stack_traces[index + 1]
|
|
101
|
+
|
|
102
|
+
file_info = await get_file_upload_link(
|
|
103
|
+
api_client, template_id, files_hash, stack_trace
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (force_upload and file_info.url) or (
|
|
107
|
+
file_info.present is False and file_info.url
|
|
108
|
+
):
|
|
109
|
+
await upload_file(
|
|
110
|
+
api_client,
|
|
111
|
+
src,
|
|
112
|
+
template._template._file_context_path,
|
|
113
|
+
file_info.url,
|
|
114
|
+
[
|
|
115
|
+
*template._template._file_ignore_patterns,
|
|
116
|
+
*read_dockerignore(template._template._file_context_path),
|
|
117
|
+
],
|
|
118
|
+
resolve_symlinks,
|
|
119
|
+
stack_trace,
|
|
120
|
+
)
|
|
121
|
+
if on_build_logs:
|
|
122
|
+
on_build_logs(
|
|
123
|
+
LogEntry(
|
|
124
|
+
timestamp=datetime.now(),
|
|
125
|
+
level="info",
|
|
126
|
+
message=f"Uploaded '{src}'",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
if on_build_logs:
|
|
131
|
+
on_build_logs(
|
|
132
|
+
LogEntry(
|
|
133
|
+
timestamp=datetime.now(),
|
|
134
|
+
level="info",
|
|
135
|
+
message=f"Skipping upload of '{src}', already cached",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if on_build_logs:
|
|
140
|
+
on_build_logs(
|
|
141
|
+
LogEntry(
|
|
142
|
+
timestamp=datetime.now(),
|
|
143
|
+
level="info",
|
|
144
|
+
message="All file uploads completed",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Start build
|
|
149
|
+
if on_build_logs:
|
|
150
|
+
on_build_logs(
|
|
151
|
+
LogEntry(
|
|
152
|
+
timestamp=datetime.now(),
|
|
153
|
+
level="info",
|
|
154
|
+
message="Starting building...",
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
await trigger_build(
|
|
159
|
+
api_client,
|
|
160
|
+
template_id,
|
|
161
|
+
build_id,
|
|
162
|
+
template._template._serialize(instructions_with_hashes),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return BuildInfo(
|
|
166
|
+
alias=alias,
|
|
167
|
+
template_id=template_id,
|
|
168
|
+
build_id=build_id,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
async def build(
|
|
173
|
+
template: TemplateClass,
|
|
174
|
+
alias: str,
|
|
175
|
+
cpu_count: int = 2,
|
|
176
|
+
memory_mb: int = 1024,
|
|
177
|
+
skip_cache: bool = False,
|
|
178
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
179
|
+
api_key: Optional[str] = None,
|
|
180
|
+
domain: Optional[str] = None,
|
|
181
|
+
) -> BuildInfo:
|
|
182
|
+
"""
|
|
183
|
+
Build and deploy a template to Moru infrastructure.
|
|
184
|
+
|
|
185
|
+
:param template: The template to build
|
|
186
|
+
:param alias: Alias name for the template
|
|
187
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
188
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
189
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
190
|
+
:param on_build_logs: Callback function to receive build logs during the build process
|
|
191
|
+
:param api_key: Moru API key for authentication
|
|
192
|
+
:param domain: Domain of the Moru API
|
|
193
|
+
|
|
194
|
+
Example
|
|
195
|
+
```python
|
|
196
|
+
from moru import AsyncTemplate
|
|
197
|
+
|
|
198
|
+
template = (
|
|
199
|
+
AsyncTemplate()
|
|
200
|
+
.from_python_image('3')
|
|
201
|
+
.copy('requirements.txt', '/home/user/')
|
|
202
|
+
.run_cmd('pip install -r /home/user/requirements.txt')
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
await AsyncTemplate.build(
|
|
206
|
+
template,
|
|
207
|
+
alias='my-python-env',
|
|
208
|
+
cpu_count=2,
|
|
209
|
+
memory_mb=1024
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
if on_build_logs:
|
|
215
|
+
on_build_logs(
|
|
216
|
+
LogEntryStart(
|
|
217
|
+
timestamp=datetime.now(),
|
|
218
|
+
message="Build started",
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
223
|
+
config = ConnectionConfig(
|
|
224
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
225
|
+
)
|
|
226
|
+
api_client = get_api_client(
|
|
227
|
+
config,
|
|
228
|
+
require_api_key=True,
|
|
229
|
+
require_access_token=False,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
data = await AsyncTemplate._build(
|
|
233
|
+
template,
|
|
234
|
+
api_client,
|
|
235
|
+
alias,
|
|
236
|
+
cpu_count,
|
|
237
|
+
memory_mb,
|
|
238
|
+
skip_cache,
|
|
239
|
+
on_build_logs,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if on_build_logs:
|
|
243
|
+
on_build_logs(
|
|
244
|
+
LogEntry(
|
|
245
|
+
timestamp=datetime.now(),
|
|
246
|
+
level="info",
|
|
247
|
+
message="Waiting for logs...",
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
await wait_for_build_finish(
|
|
252
|
+
api_client,
|
|
253
|
+
data.template_id,
|
|
254
|
+
data.build_id,
|
|
255
|
+
on_build_logs,
|
|
256
|
+
logs_refresh_frequency=TemplateBase._logs_refresh_frequency,
|
|
257
|
+
stack_traces=template._template._stack_traces,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return data
|
|
261
|
+
finally:
|
|
262
|
+
if on_build_logs:
|
|
263
|
+
on_build_logs(
|
|
264
|
+
LogEntryEnd(
|
|
265
|
+
timestamp=datetime.now(),
|
|
266
|
+
message="Build finished",
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
async def build_in_background(
|
|
272
|
+
template: TemplateClass,
|
|
273
|
+
alias: str,
|
|
274
|
+
cpu_count: int = 2,
|
|
275
|
+
memory_mb: int = 1024,
|
|
276
|
+
skip_cache: bool = False,
|
|
277
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
278
|
+
api_key: Optional[str] = None,
|
|
279
|
+
domain: Optional[str] = None,
|
|
280
|
+
) -> BuildInfo:
|
|
281
|
+
"""
|
|
282
|
+
Build and deploy a template to Moru infrastructure without waiting for completion.
|
|
283
|
+
|
|
284
|
+
:param template: The template to build
|
|
285
|
+
:param alias: Alias name for the template
|
|
286
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
287
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
288
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
289
|
+
:param api_key: Moru API key for authentication
|
|
290
|
+
:param domain: Domain of the Moru API
|
|
291
|
+
:return: BuildInfo containing the template ID and build ID
|
|
292
|
+
|
|
293
|
+
Example
|
|
294
|
+
```python
|
|
295
|
+
from moru import AsyncTemplate
|
|
296
|
+
|
|
297
|
+
template = (
|
|
298
|
+
AsyncTemplate()
|
|
299
|
+
.from_python_image('3')
|
|
300
|
+
.run_cmd('echo "test"')
|
|
301
|
+
.set_start_cmd('echo "Hello"', 'sleep 1')
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
build_info = await AsyncTemplate.build_in_background(
|
|
305
|
+
template,
|
|
306
|
+
alias='my-python-env',
|
|
307
|
+
cpu_count=2,
|
|
308
|
+
memory_mb=1024
|
|
309
|
+
)
|
|
310
|
+
```
|
|
311
|
+
"""
|
|
312
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
313
|
+
config = ConnectionConfig(
|
|
314
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
315
|
+
)
|
|
316
|
+
api_client = get_api_client(
|
|
317
|
+
config,
|
|
318
|
+
require_api_key=True,
|
|
319
|
+
require_access_token=False,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return await AsyncTemplate._build(
|
|
323
|
+
template,
|
|
324
|
+
api_client,
|
|
325
|
+
alias,
|
|
326
|
+
cpu_count,
|
|
327
|
+
memory_mb,
|
|
328
|
+
skip_cache,
|
|
329
|
+
on_build_logs,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
async def get_build_status(
|
|
334
|
+
build_info: BuildInfo,
|
|
335
|
+
logs_offset: int = 0,
|
|
336
|
+
api_key: Optional[str] = None,
|
|
337
|
+
domain: Optional[str] = None,
|
|
338
|
+
):
|
|
339
|
+
"""
|
|
340
|
+
Get the status of a build.
|
|
341
|
+
|
|
342
|
+
:param build_info: Build identifiers returned from build_in_background
|
|
343
|
+
:param logs_offset: Offset for fetching logs
|
|
344
|
+
:param api_key: Moru API key for authentication
|
|
345
|
+
:param domain: Domain of the Moru API
|
|
346
|
+
:return: TemplateBuild containing the build status and logs
|
|
347
|
+
|
|
348
|
+
Example
|
|
349
|
+
```python
|
|
350
|
+
from moru import AsyncTemplate
|
|
351
|
+
|
|
352
|
+
build_info = await AsyncTemplate.build_in_background(template, alias='my-template')
|
|
353
|
+
status = await AsyncTemplate.get_build_status(build_info, logs_offset=0)
|
|
354
|
+
```
|
|
355
|
+
"""
|
|
356
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
357
|
+
config = ConnectionConfig(
|
|
358
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
359
|
+
)
|
|
360
|
+
api_client = get_api_client(config)
|
|
361
|
+
return await get_build_status(
|
|
362
|
+
api_client,
|
|
363
|
+
build_info.template_id,
|
|
364
|
+
build_info.build_id,
|
|
365
|
+
logs_offset,
|
|
366
|
+
)
|