meshagent-cli 0.34.0__tar.gz → 0.35.3__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.
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/PKG-INFO +13 -13
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli.py +2 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/containers.py +40 -104
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/containers_test.py +55 -0
- meshagent_cli-0.35.3/meshagent/cli/image.py +1365 -0
- meshagent_cli-0.35.3/meshagent/cli/image_test.py +1439 -0
- meshagent_cli-0.35.3/meshagent/cli/oci_archive.py +1209 -0
- meshagent_cli-0.35.3/meshagent/cli/oci_archive_test.py +220 -0
- meshagent_cli-0.35.3/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/PKG-INFO +13 -13
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/SOURCES.txt +4 -0
- meshagent_cli-0.35.3/meshagent_cli.egg-info/requires.txt +29 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/pyproject.toml +12 -12
- meshagent_cli-0.34.0/meshagent/cli/version.py +0 -1
- meshagent_cli-0.34.0/meshagent_cli.egg-info/requires.txt +0 -29
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/README.md +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/api_keys.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/call.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_secrets.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/codex.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/common_options.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/database.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/developer_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helper_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helpers.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/host.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/mailbot.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/mailboxes.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/meeting_transcriber.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/memory.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/memory_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/multi.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/oauth2.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/port.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/port_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/process.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/process_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/queue.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/queue_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/room.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/room_services.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/rooms.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/routes.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/scheduled_tasks.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/scheduled_tasks_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/services.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/services_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sessions_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/storage_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sync.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sync_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/task_runner.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/__init__.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup_splash_frames.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webhook.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webserver.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webserver_test.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/worker.py +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.3
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -19,21 +19,21 @@ Requires-Dist: rich~=14.3.0
|
|
|
19
19
|
Requires-Dist: textual<2.0,>=0.50
|
|
20
20
|
Requires-Dist: prompt-toolkit~=3.0.52
|
|
21
21
|
Provides-Extra: all
|
|
22
|
-
Requires-Dist: meshagent-agents[all]~=0.
|
|
23
|
-
Requires-Dist: meshagent-api[all]~=0.
|
|
24
|
-
Requires-Dist: meshagent-computers~=0.
|
|
25
|
-
Requires-Dist: meshagent-openai~=0.
|
|
26
|
-
Requires-Dist: meshagent-anthropic~=0.
|
|
27
|
-
Requires-Dist: meshagent-codex~=0.
|
|
28
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
29
|
-
Requires-Dist: meshagent-tools~=0.
|
|
22
|
+
Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "all"
|
|
23
|
+
Requires-Dist: meshagent-api[all]~=0.35.3; extra == "all"
|
|
24
|
+
Requires-Dist: meshagent-computers~=0.35.3; extra == "all"
|
|
25
|
+
Requires-Dist: meshagent-openai~=0.35.3; extra == "all"
|
|
26
|
+
Requires-Dist: meshagent-anthropic~=0.35.3; extra == "all"
|
|
27
|
+
Requires-Dist: meshagent-codex~=0.35.3; extra == "all"
|
|
28
|
+
Requires-Dist: meshagent-mcp~=0.35.3; extra == "all"
|
|
29
|
+
Requires-Dist: meshagent-tools~=0.35.3; extra == "all"
|
|
30
30
|
Requires-Dist: supabase-auth~=2.28.0; extra == "all"
|
|
31
31
|
Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
|
|
32
32
|
Provides-Extra: mcp-service
|
|
33
|
-
Requires-Dist: meshagent-agents[all]~=0.
|
|
34
|
-
Requires-Dist: meshagent-api~=0.
|
|
35
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
36
|
-
Requires-Dist: meshagent-tools~=0.
|
|
33
|
+
Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "mcp-service"
|
|
34
|
+
Requires-Dist: meshagent-api~=0.35.3; extra == "mcp-service"
|
|
35
|
+
Requires-Dist: meshagent-mcp~=0.35.3; extra == "mcp-service"
|
|
36
|
+
Requires-Dist: meshagent-tools~=0.35.3; extra == "mcp-service"
|
|
37
37
|
Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
|
|
38
38
|
|
|
39
39
|
# [Meshagent](https://www.meshagent.com)
|
|
@@ -29,6 +29,7 @@ from meshagent.cli import helpers
|
|
|
29
29
|
from meshagent.cli import meeting_transcriber
|
|
30
30
|
from meshagent.cli import rooms
|
|
31
31
|
from meshagent.cli import room
|
|
32
|
+
from meshagent.cli import image
|
|
32
33
|
from meshagent.cli import port
|
|
33
34
|
from meshagent.cli import webserver
|
|
34
35
|
from meshagent.cli import codex
|
|
@@ -99,6 +100,7 @@ app.add_typer(task_runner.app, name="task-runner")
|
|
|
99
100
|
app.add_typer(worker.app, name="worker")
|
|
100
101
|
|
|
101
102
|
app.add_typer(room.app, name="room")
|
|
103
|
+
app.add_typer(image.app, name="image")
|
|
102
104
|
|
|
103
105
|
|
|
104
106
|
def _run_async(coro):
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import io
|
|
6
6
|
import os
|
|
7
|
+
import re
|
|
7
8
|
import tarfile
|
|
8
9
|
import time
|
|
9
10
|
from pathlib import Path
|
|
@@ -45,6 +46,9 @@ import sys
|
|
|
45
46
|
|
|
46
47
|
app = async_typer.AsyncTyper(help="Manage containers and images inside a room")
|
|
47
48
|
_LOG_STREAM_SETTLE_TIMEOUT_SECONDS = 1.0
|
|
49
|
+
_CRI_LOG_LINE_PATTERN = re.compile(
|
|
50
|
+
r"^(?P<timestamp>\S+)\s+(?P<stream>stdout|stderr)\s+(?P<flags>[FP])\s(?P<message>.*)$"
|
|
51
|
+
)
|
|
48
52
|
|
|
49
53
|
# -------------------------
|
|
50
54
|
# Helpers
|
|
@@ -189,6 +193,24 @@ async def _stream_container_job_logs_and_wait_for_exit(
|
|
|
189
193
|
return exit_code
|
|
190
194
|
|
|
191
195
|
|
|
196
|
+
async def _stream_build_job_logs_and_wait_for_exit(
|
|
197
|
+
*,
|
|
198
|
+
client: RoomClient,
|
|
199
|
+
build_id: str,
|
|
200
|
+
) -> int:
|
|
201
|
+
stream = client.containers.get_build_logs(build_id=build_id, follow=True)
|
|
202
|
+
try:
|
|
203
|
+
exit_code = await _drain_stream_plain(stream, show_progress=False)
|
|
204
|
+
except Exception:
|
|
205
|
+
await asyncio.gather(stream.cancel(), return_exceptions=True)
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
if exit_code is None:
|
|
209
|
+
raise RuntimeError("build log stream closed before an exit code was returned")
|
|
210
|
+
|
|
211
|
+
return exit_code
|
|
212
|
+
|
|
213
|
+
|
|
192
214
|
class DockerIgnore:
|
|
193
215
|
def __init__(self, dockerignore_path: str):
|
|
194
216
|
"""
|
|
@@ -253,11 +275,27 @@ async def _make_targz_from_dir(path: Path) -> bytes:
|
|
|
253
275
|
|
|
254
276
|
|
|
255
277
|
async def _drain_stream_plain(stream, *, show_progress: bool = True):
|
|
278
|
+
def _format_line(line: str) -> str:
|
|
279
|
+
has_newline = line.endswith("\n")
|
|
280
|
+
line_without_newline = line[:-1] if has_newline else line
|
|
281
|
+
match = _CRI_LOG_LINE_PATTERN.match(line_without_newline)
|
|
282
|
+
if match is None:
|
|
283
|
+
return line
|
|
284
|
+
|
|
285
|
+
message = match.group("message")
|
|
286
|
+
if has_newline:
|
|
287
|
+
return f"{message}\n"
|
|
288
|
+
return message
|
|
289
|
+
|
|
256
290
|
async def _logs():
|
|
257
291
|
async for line in stream.logs():
|
|
258
|
-
# Server emits plain lines; print as-is
|
|
259
292
|
if line is not None:
|
|
260
|
-
|
|
293
|
+
formatted_line = _format_line(line)
|
|
294
|
+
sys.stdout.write(
|
|
295
|
+
formatted_line
|
|
296
|
+
if formatted_line.endswith("\n")
|
|
297
|
+
else f"{formatted_line}\n"
|
|
298
|
+
)
|
|
261
299
|
sys.stdout.flush()
|
|
262
300
|
|
|
263
301
|
async def _prog():
|
|
@@ -778,108 +816,6 @@ app.add_typer(images_app, name="image")
|
|
|
778
816
|
app.add_typer(images_app, name="images", hidden=True)
|
|
779
817
|
|
|
780
818
|
|
|
781
|
-
@images_app.async_command("build", help="Build a container image inside a room.")
|
|
782
|
-
async def build_container(
|
|
783
|
-
*,
|
|
784
|
-
project_id: ProjectIdOption,
|
|
785
|
-
room: RoomOption,
|
|
786
|
-
tag: Annotated[
|
|
787
|
-
str, typer.Option(..., help="Image tag to build, e.g. repo/name:tag")
|
|
788
|
-
],
|
|
789
|
-
context_path: Annotated[
|
|
790
|
-
str,
|
|
791
|
-
typer.Option(
|
|
792
|
-
...,
|
|
793
|
-
"--context-path",
|
|
794
|
-
help="Build context path inside one of the mounted paths (absolute path)",
|
|
795
|
-
),
|
|
796
|
-
],
|
|
797
|
-
dockerfile_path: Annotated[
|
|
798
|
-
Optional[str],
|
|
799
|
-
typer.Option(
|
|
800
|
-
"--dockerfile-path",
|
|
801
|
-
help="Optional Dockerfile path inside one of the mounted paths (absolute path)",
|
|
802
|
-
),
|
|
803
|
-
] = None,
|
|
804
|
-
mount_room_path: Annotated[
|
|
805
|
-
List[str],
|
|
806
|
-
typer.Option(
|
|
807
|
-
"--mount-room-path",
|
|
808
|
-
help=(
|
|
809
|
-
"Room storage mount '<source>:<mount>[:ro|rw]'. "
|
|
810
|
-
"Example '/src:/workspace'"
|
|
811
|
-
),
|
|
812
|
-
),
|
|
813
|
-
] = [],
|
|
814
|
-
mount_project_path: Annotated[
|
|
815
|
-
List[str],
|
|
816
|
-
typer.Option(
|
|
817
|
-
"--mount-project-path",
|
|
818
|
-
help=(
|
|
819
|
-
"Project storage mount '<source>:<mount>[:ro|rw]'. "
|
|
820
|
-
"Example '/shared:/project:ro'"
|
|
821
|
-
),
|
|
822
|
-
),
|
|
823
|
-
] = [],
|
|
824
|
-
mount_image: Annotated[
|
|
825
|
-
List[str],
|
|
826
|
-
typer.Option(
|
|
827
|
-
"--mount-image",
|
|
828
|
-
help=(
|
|
829
|
-
"Image mount '<image>=<mount>[:ro|rw]'. "
|
|
830
|
-
"Example 'alpine:latest=/toolchain:ro'"
|
|
831
|
-
),
|
|
832
|
-
),
|
|
833
|
-
] = [],
|
|
834
|
-
private: Annotated[
|
|
835
|
-
bool,
|
|
836
|
-
typer.Option(
|
|
837
|
-
"--private/--public",
|
|
838
|
-
help="Whether the build container is private to the participant",
|
|
839
|
-
),
|
|
840
|
-
] = False,
|
|
841
|
-
cred: Annotated[
|
|
842
|
-
List[str],
|
|
843
|
-
typer.Option(
|
|
844
|
-
"--cred",
|
|
845
|
-
help="Docker creds (username,password) or (registry,username,password)",
|
|
846
|
-
),
|
|
847
|
-
] = [],
|
|
848
|
-
):
|
|
849
|
-
mount_spec = _parse_image_operation_mounts(
|
|
850
|
-
mount_room_path=mount_room_path,
|
|
851
|
-
mount_project_path=mount_project_path,
|
|
852
|
-
mount_image=mount_image,
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
if not context_path.startswith("/"):
|
|
856
|
-
raise typer.BadParameter("--context-path must be an absolute path")
|
|
857
|
-
if dockerfile_path is not None and not dockerfile_path.startswith("/"):
|
|
858
|
-
raise typer.BadParameter("--dockerfile-path must be an absolute path")
|
|
859
|
-
|
|
860
|
-
account_client, client = await _with_client(
|
|
861
|
-
project_id=project_id,
|
|
862
|
-
room=room,
|
|
863
|
-
)
|
|
864
|
-
try:
|
|
865
|
-
container_id = await client.containers.build(
|
|
866
|
-
tag=tag,
|
|
867
|
-
mounts=[mount_spec],
|
|
868
|
-
context_path=context_path,
|
|
869
|
-
dockerfile_path=dockerfile_path,
|
|
870
|
-
private=private,
|
|
871
|
-
credentials=_parse_creds(cred),
|
|
872
|
-
)
|
|
873
|
-
exit_code = await _stream_container_job_logs_and_wait_for_exit(
|
|
874
|
-
client=client, container_id=container_id
|
|
875
|
-
)
|
|
876
|
-
if exit_code != 0:
|
|
877
|
-
raise typer.Exit(code=exit_code)
|
|
878
|
-
finally:
|
|
879
|
-
await client.__aexit__(None, None, None)
|
|
880
|
-
await account_client.close()
|
|
881
|
-
|
|
882
|
-
|
|
883
819
|
@images_app.async_command("list", help="List container images available in a room.")
|
|
884
820
|
async def images_list(
|
|
885
821
|
*,
|
|
@@ -56,6 +56,21 @@ class _FakeBuildStream:
|
|
|
56
56
|
return _done().__await__()
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
class _FakeBuildContainers:
|
|
60
|
+
def __init__(self, *, stream: _FakeBuildStream) -> None:
|
|
61
|
+
self._stream = stream
|
|
62
|
+
self.build_log_calls: list[tuple[str, bool]] = []
|
|
63
|
+
|
|
64
|
+
def get_build_logs(self, *, build_id: str, follow: bool = True) -> _FakeBuildStream:
|
|
65
|
+
self.build_log_calls.append((build_id, follow))
|
|
66
|
+
return self._stream
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _FakeBuildClient:
|
|
70
|
+
def __init__(self, *, stream: _FakeBuildStream) -> None:
|
|
71
|
+
self.containers = _FakeBuildContainers(stream=stream)
|
|
72
|
+
|
|
73
|
+
|
|
59
74
|
@pytest.mark.asyncio
|
|
60
75
|
async def test_stream_container_job_logs_and_wait_for_exit_cancels_follow_stream(
|
|
61
76
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -123,3 +138,43 @@ async def test_drain_stream_plain_does_not_double_space_newline_terminated_logs(
|
|
|
123
138
|
|
|
124
139
|
assert result == "image-1"
|
|
125
140
|
assert capsys.readouterr().out == "step 1\nstep 2\n"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_drain_stream_plain_strips_cri_log_prefixes(
|
|
145
|
+
capsys: pytest.CaptureFixture[str],
|
|
146
|
+
) -> None:
|
|
147
|
+
stream = _FakeBuildStream(
|
|
148
|
+
lines=[
|
|
149
|
+
"2026-03-30T04:21:56.562896627Z stderr F step 1\n",
|
|
150
|
+
"2026-03-30T04:21:57.000000000Z stdout F step 2\n",
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
result = await containers._drain_stream_plain(stream, show_progress=False)
|
|
155
|
+
|
|
156
|
+
assert result == "image-1"
|
|
157
|
+
assert capsys.readouterr().out == "step 1\nstep 2\n"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@pytest.mark.asyncio
|
|
161
|
+
async def test_stream_build_job_logs_and_wait_for_exit_uses_build_log_stream(
|
|
162
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
163
|
+
) -> None:
|
|
164
|
+
stream = _FakeBuildStream(lines=["line 1\n"], result=0)
|
|
165
|
+
client = _FakeBuildClient(stream=stream)
|
|
166
|
+
|
|
167
|
+
async def _fake_drain(log_stream, *, show_progress: bool):
|
|
168
|
+
assert log_stream is stream
|
|
169
|
+
assert show_progress is False
|
|
170
|
+
return await log_stream
|
|
171
|
+
|
|
172
|
+
monkeypatch.setattr(containers, "_drain_stream_plain", _fake_drain)
|
|
173
|
+
|
|
174
|
+
exit_code = await containers._stream_build_job_logs_and_wait_for_exit(
|
|
175
|
+
client=client,
|
|
176
|
+
build_id="build-1",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
assert exit_code == 0
|
|
180
|
+
assert client.containers.build_log_calls == [("build-1", True)]
|