meshagent-cli 0.42.0__tar.gz → 0.42.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.
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/PKG-INFO +16 -16
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/image.py +205 -2
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/image_test.py +167 -11
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/local_settings_test.py +2 -2
- meshagent_cli-0.42.2/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/PKG-INFO +16 -16
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/requires.txt +15 -15
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/pyproject.toml +15 -15
- meshagent_cli-0.42.0/meshagent/cli/version.py +0 -1
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/LICENSE +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/README.md +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agent_cli_options_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agent_package_cli.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agent_package_cli_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agents.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/agents_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/api_keys.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/api_keys_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/ask.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/ask_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/async_typer_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/auth_async_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/auth_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/call.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/cli.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/cli_secrets.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/cli_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/common_options.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/config.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/config_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/containers.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/containers_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create_project_templates/__init__.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create_project_templates/python/backend-agent/server.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create_project_templates/python/contact-form/server.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create_project_templates/python/webserver/server.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/create_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/dataset.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/dataset_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/developer_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/doctor.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/doctor_dockerfiles/__init__.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/doctor_templates/__init__.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/doctor_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/feeds.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/helper_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/helpers.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/host.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/launch.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/launch_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/llm.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/llm_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/local_settings.py +4 -4
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/mailbot.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/mailbot_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/mailboxes.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/meeting_transcriber.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/memory.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/memory_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/meshagent_images.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/meshagent_images_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/multi.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/oauth2.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/oauth2_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/oci_archive.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/oci_archive_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/port.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/port_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/preamble_rules.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/preamble_rules_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/process.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/process_live_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/process_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/projects_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/queue.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/queue_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/registry.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/registry_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/room.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/room_connect.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/room_connect_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/room_services.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/rooms.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/rooms_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/root_commands.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/root_commands_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/routes.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/scheduled_tasks.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/scheduled_tasks_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/services.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/services_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/sessions_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/storage_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/subscriptions.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/sync.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/sync_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/task_runner.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/task_runner_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tool_call_summary.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tool_call_summary_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tool_integrations.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tool_integrations_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/__init__.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/auth_switch.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/auth_switch_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/create.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/deploy_room.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/deploy_room_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/project_activate.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/project_activate_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/setup.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/setup_splash_frames.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/tui/setup_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/version_check.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/version_check_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/webhook.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/webserver.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/webserver_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/worker.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent/cli/worker_test.py +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/SOURCES.txt +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.42.0 → meshagent_cli-0.42.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.42.
|
|
3
|
+
Version: 0.42.2
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -22,7 +22,7 @@ Requires-Dist: openpyxl~=3.1
|
|
|
22
22
|
Requires-Dist: xlsxwriter~=3.2
|
|
23
23
|
Requires-Dist: pathspec<2,>=1.0.3
|
|
24
24
|
Requires-Dist: zstandard~=0.25.0
|
|
25
|
-
Requires-Dist: meshagent-llm-proxy==0.42.
|
|
25
|
+
Requires-Dist: meshagent-llm-proxy==0.42.2
|
|
26
26
|
Requires-Dist: rich~=14.3.0
|
|
27
27
|
Requires-Dist: sounddevice~=0.5
|
|
28
28
|
Requires-Dist: textual<9.0,>=8.2.3
|
|
@@ -30,23 +30,23 @@ Requires-Dist: prompt-toolkit~=3.0.52
|
|
|
30
30
|
Requires-Dist: ascii-magic~=2.3
|
|
31
31
|
Requires-Dist: pillow~=11.3.0
|
|
32
32
|
Provides-Extra: all
|
|
33
|
-
Requires-Dist: meshagent-agents[all]==0.42.
|
|
34
|
-
Requires-Dist: meshagent-api[all]==0.42.
|
|
35
|
-
Requires-Dist: meshagent-commoncrawl==0.42.
|
|
36
|
-
Requires-Dist: meshagent-scrapy==0.42.
|
|
37
|
-
Requires-Dist: meshagent-computers==0.42.
|
|
38
|
-
Requires-Dist: meshagent-openai==0.42.
|
|
39
|
-
Requires-Dist: meshagent-anthropic==0.42.
|
|
40
|
-
Requires-Dist: meshagent-otel==0.42.
|
|
41
|
-
Requires-Dist: meshagent-mcp==0.42.
|
|
42
|
-
Requires-Dist: meshagent-tools==0.42.
|
|
33
|
+
Requires-Dist: meshagent-agents[all]==0.42.2; extra == "all"
|
|
34
|
+
Requires-Dist: meshagent-api[all]==0.42.2; extra == "all"
|
|
35
|
+
Requires-Dist: meshagent-commoncrawl==0.42.2; extra == "all"
|
|
36
|
+
Requires-Dist: meshagent-scrapy==0.42.2; extra == "all"
|
|
37
|
+
Requires-Dist: meshagent-computers==0.42.2; extra == "all"
|
|
38
|
+
Requires-Dist: meshagent-openai==0.42.2; extra == "all"
|
|
39
|
+
Requires-Dist: meshagent-anthropic==0.42.2; extra == "all"
|
|
40
|
+
Requires-Dist: meshagent-otel==0.42.2; extra == "all"
|
|
41
|
+
Requires-Dist: meshagent-mcp==0.42.2; extra == "all"
|
|
42
|
+
Requires-Dist: meshagent-tools==0.42.2; extra == "all"
|
|
43
43
|
Requires-Dist: supabase-auth~=2.28.0; extra == "all"
|
|
44
44
|
Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
|
|
45
45
|
Provides-Extra: mcp-service
|
|
46
|
-
Requires-Dist: meshagent-agents[all]==0.42.
|
|
47
|
-
Requires-Dist: meshagent-api==0.42.
|
|
48
|
-
Requires-Dist: meshagent-mcp==0.42.
|
|
49
|
-
Requires-Dist: meshagent-tools==0.42.
|
|
46
|
+
Requires-Dist: meshagent-agents[all]==0.42.2; extra == "mcp-service"
|
|
47
|
+
Requires-Dist: meshagent-api==0.42.2; extra == "mcp-service"
|
|
48
|
+
Requires-Dist: meshagent-mcp==0.42.2; extra == "mcp-service"
|
|
49
|
+
Requires-Dist: meshagent-tools==0.42.2; extra == "mcp-service"
|
|
50
50
|
Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
|
|
51
51
|
Dynamic: license-file
|
|
52
52
|
|
|
@@ -66,6 +66,7 @@ from meshagent.api.registry_auth import DEFAULT_REGISTRY_HOST, DEFAULT_REGISTRY_
|
|
|
66
66
|
from meshagent.api.room_server_client import (
|
|
67
67
|
DockerSecret,
|
|
68
68
|
LogStream,
|
|
69
|
+
PublishedBuildImage,
|
|
69
70
|
RoomException,
|
|
70
71
|
ServiceRuntimeState,
|
|
71
72
|
)
|
|
@@ -2264,6 +2265,159 @@ async def _delete_built_image_from_room_cache(
|
|
|
2264
2265
|
raise
|
|
2265
2266
|
|
|
2266
2267
|
|
|
2268
|
+
def _service_image_refs(service_spec: ServiceSpec | None) -> list[str]:
|
|
2269
|
+
if service_spec is None or service_spec.container is None:
|
|
2270
|
+
return []
|
|
2271
|
+
|
|
2272
|
+
image_refs: list[str] = []
|
|
2273
|
+
if service_spec.container.image is not None:
|
|
2274
|
+
image_refs.append(service_spec.container.image)
|
|
2275
|
+
storage = service_spec.container.storage
|
|
2276
|
+
if storage is not None and storage.images is not None:
|
|
2277
|
+
image_refs.extend(image_mount.image for image_mount in storage.images)
|
|
2278
|
+
|
|
2279
|
+
deduped: list[str] = []
|
|
2280
|
+
seen: set[str] = set()
|
|
2281
|
+
for image_ref in image_refs:
|
|
2282
|
+
if image_ref not in seen:
|
|
2283
|
+
deduped.append(image_ref)
|
|
2284
|
+
seen.add(image_ref)
|
|
2285
|
+
return deduped
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
def _is_built_service_image_ref(
|
|
2289
|
+
*,
|
|
2290
|
+
image_ref: str,
|
|
2291
|
+
parsed_tag: _ParsedImageTag,
|
|
2292
|
+
project_registry: str,
|
|
2293
|
+
) -> bool:
|
|
2294
|
+
repository_ref = f"{project_registry}/{parsed_tag.repository}"
|
|
2295
|
+
return image_ref.startswith(f"{repository_ref}:") or image_ref.startswith(
|
|
2296
|
+
f"{repository_ref}@"
|
|
2297
|
+
)
|
|
2298
|
+
|
|
2299
|
+
|
|
2300
|
+
def _built_service_image_refs(
|
|
2301
|
+
*,
|
|
2302
|
+
service_spec: ServiceSpec | None,
|
|
2303
|
+
parsed_tag: _ParsedImageTag,
|
|
2304
|
+
project_registry: str,
|
|
2305
|
+
) -> list[str]:
|
|
2306
|
+
built_refs: list[str] = []
|
|
2307
|
+
for image_ref in _service_image_refs(service_spec):
|
|
2308
|
+
if not _is_built_service_image_ref(
|
|
2309
|
+
image_ref=image_ref,
|
|
2310
|
+
parsed_tag=parsed_tag,
|
|
2311
|
+
project_registry=project_registry,
|
|
2312
|
+
):
|
|
2313
|
+
continue
|
|
2314
|
+
built_refs.append(image_ref)
|
|
2315
|
+
return built_refs
|
|
2316
|
+
|
|
2317
|
+
|
|
2318
|
+
async def _delete_replaced_built_service_images(
|
|
2319
|
+
*,
|
|
2320
|
+
client: RoomClient,
|
|
2321
|
+
image_refs: list[str],
|
|
2322
|
+
) -> None:
|
|
2323
|
+
for image_ref in image_refs:
|
|
2324
|
+
try:
|
|
2325
|
+
await asyncio.wait_for(
|
|
2326
|
+
client.containers.delete_image(image=image_ref),
|
|
2327
|
+
timeout=_DEPLOY_CACHE_CLEANUP_TIMEOUT_SECONDS,
|
|
2328
|
+
)
|
|
2329
|
+
except TimeoutError as exc:
|
|
2330
|
+
raise RuntimeError(
|
|
2331
|
+
"timed out cleaning up replaced service image after deploy"
|
|
2332
|
+
) from exc
|
|
2333
|
+
except RoomException as exc:
|
|
2334
|
+
if exc.code in {ErrorCode.NOT_FOUND, ErrorCode.CONTAINER_NOT_FOUND}:
|
|
2335
|
+
continue
|
|
2336
|
+
raise
|
|
2337
|
+
|
|
2338
|
+
|
|
2339
|
+
def _select_published_build_image(
|
|
2340
|
+
*,
|
|
2341
|
+
published_images: list[PublishedBuildImage],
|
|
2342
|
+
parsed_tag: _ParsedImageTag,
|
|
2343
|
+
) -> PublishedBuildImage:
|
|
2344
|
+
for published_image in published_images:
|
|
2345
|
+
if published_image.tag == parsed_tag.value:
|
|
2346
|
+
return published_image
|
|
2347
|
+
if len(published_images) > 0:
|
|
2348
|
+
return published_images[0]
|
|
2349
|
+
raise RuntimeError("build completed without a published image digest")
|
|
2350
|
+
|
|
2351
|
+
|
|
2352
|
+
async def _resolve_completed_build_image(
|
|
2353
|
+
*,
|
|
2354
|
+
client: RoomClient,
|
|
2355
|
+
build_id: str,
|
|
2356
|
+
parsed_tag: _ParsedImageTag,
|
|
2357
|
+
) -> PublishedBuildImage:
|
|
2358
|
+
builds = await client.containers.list_builds()
|
|
2359
|
+
for build in builds:
|
|
2360
|
+
if build.id != build_id:
|
|
2361
|
+
continue
|
|
2362
|
+
return _select_published_build_image(
|
|
2363
|
+
published_images=build.published_images,
|
|
2364
|
+
parsed_tag=parsed_tag,
|
|
2365
|
+
)
|
|
2366
|
+
raise RuntimeError(f"completed build was not found: {build_id}")
|
|
2367
|
+
|
|
2368
|
+
|
|
2369
|
+
def _deploy_plan_with_published_image(
|
|
2370
|
+
*,
|
|
2371
|
+
deploy_plan: _ServiceDeployPlan,
|
|
2372
|
+
parsed_tag: _ParsedImageTag,
|
|
2373
|
+
published_image: PublishedBuildImage,
|
|
2374
|
+
) -> _ServiceDeployPlan:
|
|
2375
|
+
container = deploy_plan.spec.container
|
|
2376
|
+
if container is None:
|
|
2377
|
+
raise RuntimeError("deploy plan does not contain a container spec")
|
|
2378
|
+
container_updates: dict[str, object] = {}
|
|
2379
|
+
if container.image == parsed_tag.value:
|
|
2380
|
+
container_updates["image"] = published_image.resolved_ref
|
|
2381
|
+
if container.storage is not None and container.storage.images is not None:
|
|
2382
|
+
image_mounts = []
|
|
2383
|
+
storage_updated = False
|
|
2384
|
+
for image_mount in container.storage.images:
|
|
2385
|
+
if image_mount.image == parsed_tag.value:
|
|
2386
|
+
image_mounts.append(
|
|
2387
|
+
image_mount.model_copy(
|
|
2388
|
+
update={"image": published_image.resolved_ref}
|
|
2389
|
+
)
|
|
2390
|
+
)
|
|
2391
|
+
storage_updated = True
|
|
2392
|
+
else:
|
|
2393
|
+
image_mounts.append(image_mount)
|
|
2394
|
+
if storage_updated:
|
|
2395
|
+
container_updates["storage"] = container.storage.model_copy(
|
|
2396
|
+
update={"images": image_mounts}
|
|
2397
|
+
)
|
|
2398
|
+
updated_container = (
|
|
2399
|
+
container.model_copy(update=container_updates)
|
|
2400
|
+
if container_updates
|
|
2401
|
+
else container
|
|
2402
|
+
)
|
|
2403
|
+
return _ServiceDeployPlan(
|
|
2404
|
+
spec=deploy_plan.spec.model_copy(update={"container": updated_container}),
|
|
2405
|
+
service_id_annotation=deploy_plan.service_id_annotation,
|
|
2406
|
+
)
|
|
2407
|
+
|
|
2408
|
+
|
|
2409
|
+
def _format_published_image_summary(published_image: PublishedBuildImage) -> str:
|
|
2410
|
+
stats = published_image.stats
|
|
2411
|
+
if stats is None:
|
|
2412
|
+
return f"{published_image.resolved_ref}"
|
|
2413
|
+
return (
|
|
2414
|
+
f"{published_image.resolved_ref} "
|
|
2415
|
+
f"({stats.layer_count} layers, "
|
|
2416
|
+
f"{_format_transfer_size(stats.total_layer_size_bytes)} layers, "
|
|
2417
|
+
f"{_format_transfer_size(stats.total_size_bytes)} total)"
|
|
2418
|
+
)
|
|
2419
|
+
|
|
2420
|
+
|
|
2267
2421
|
def _update_request_validation_annotations(
|
|
2268
2422
|
*,
|
|
2269
2423
|
annotations: dict[str, str],
|
|
@@ -3641,7 +3795,7 @@ async def _run_image_build_stage(
|
|
|
3641
3795
|
add_latest_tag: bool = False,
|
|
3642
3796
|
status_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
3643
3797
|
log_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
3644
|
-
) ->
|
|
3798
|
+
) -> PublishedBuildImage:
|
|
3645
3799
|
del arch
|
|
3646
3800
|
build_inputs = _resolve_build_stage_inputs(
|
|
3647
3801
|
context_path=context_path,
|
|
@@ -3737,6 +3891,22 @@ async def _run_image_build_stage(
|
|
|
3737
3891
|
plain_message=f"Image build failed: exit code {exit_code}",
|
|
3738
3892
|
)
|
|
3739
3893
|
raise typer.Exit(code=exit_code)
|
|
3894
|
+
published_image = await _resolve_completed_build_image(
|
|
3895
|
+
client=client,
|
|
3896
|
+
build_id=build_id,
|
|
3897
|
+
parsed_tag=parsed_tag,
|
|
3898
|
+
)
|
|
3899
|
+
await _emit_deploy_status(
|
|
3900
|
+
status_handler,
|
|
3901
|
+
rich_message=(
|
|
3902
|
+
"[green]Published image:[/] "
|
|
3903
|
+
f"{_format_published_image_summary(published_image)}"
|
|
3904
|
+
),
|
|
3905
|
+
plain_message=(
|
|
3906
|
+
f"Published image: {_format_published_image_summary(published_image)}"
|
|
3907
|
+
),
|
|
3908
|
+
)
|
|
3909
|
+
return published_image
|
|
3740
3910
|
finally:
|
|
3741
3911
|
if context_archive_temp_dir is not None:
|
|
3742
3912
|
context_archive_temp_dir.cleanup()
|
|
@@ -4549,9 +4719,11 @@ async def deploy_image(
|
|
|
4549
4719
|
status_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
4550
4720
|
log_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
4551
4721
|
) -> _AppliedDeployPlanResult:
|
|
4722
|
+
nonlocal deploy_plan
|
|
4723
|
+
replaced_image_refs: list[str] = []
|
|
4552
4724
|
if pack is not None:
|
|
4553
4725
|
assert project_registry is not None
|
|
4554
|
-
await _run_image_build_stage(
|
|
4726
|
+
published_image = await _run_image_build_stage(
|
|
4555
4727
|
resolved_project_id=resolved_project_id,
|
|
4556
4728
|
resolved_room=resolved_room,
|
|
4557
4729
|
parsed_tag=parsed_tag,
|
|
@@ -4568,6 +4740,16 @@ async def deploy_image(
|
|
|
4568
4740
|
status_handler=status_handler,
|
|
4569
4741
|
log_handler=log_handler,
|
|
4570
4742
|
)
|
|
4743
|
+
deploy_plan = _deploy_plan_with_published_image(
|
|
4744
|
+
deploy_plan=deploy_plan,
|
|
4745
|
+
parsed_tag=parsed_tag,
|
|
4746
|
+
published_image=published_image,
|
|
4747
|
+
)
|
|
4748
|
+
replaced_image_refs = _built_service_image_refs(
|
|
4749
|
+
service_spec=existing_service,
|
|
4750
|
+
parsed_tag=parsed_tag,
|
|
4751
|
+
project_registry=project_registry,
|
|
4752
|
+
)
|
|
4571
4753
|
await _emit_deploy_status(
|
|
4572
4754
|
status_handler,
|
|
4573
4755
|
rich_message="[green]Image build complete[/]",
|
|
@@ -4621,7 +4803,27 @@ async def deploy_image(
|
|
|
4621
4803
|
rich_message=f"[green]Saved deploy values:[/] {deploy_values_file}",
|
|
4622
4804
|
plain_message=f"Saved deploy values: {deploy_values_file}",
|
|
4623
4805
|
)
|
|
4806
|
+
|
|
4807
|
+
async def _cleanup_replaced_service_images() -> None:
|
|
4808
|
+
if len(replaced_image_refs) == 0:
|
|
4809
|
+
return
|
|
4810
|
+
await _emit_deploy_status(
|
|
4811
|
+
status_handler,
|
|
4812
|
+
rich_message="[cyan]Cleaning up replaced service image[/]",
|
|
4813
|
+
plain_message="Cleaning up replaced service image...",
|
|
4814
|
+
)
|
|
4815
|
+
await _delete_replaced_built_service_images(
|
|
4816
|
+
client=client,
|
|
4817
|
+
image_refs=replaced_image_refs,
|
|
4818
|
+
)
|
|
4819
|
+
await _emit_deploy_status(
|
|
4820
|
+
status_handler,
|
|
4821
|
+
rich_message="[green]Replaced service image cleaned[/]",
|
|
4822
|
+
plain_message="Replaced service image cleaned.",
|
|
4823
|
+
)
|
|
4824
|
+
|
|
4624
4825
|
if not wait:
|
|
4826
|
+
await _cleanup_replaced_service_images()
|
|
4625
4827
|
return deploy_result
|
|
4626
4828
|
previous_container_id = (
|
|
4627
4829
|
previous_runtime_state.container_id
|
|
@@ -4655,6 +4857,7 @@ async def deploy_image(
|
|
|
4655
4857
|
"timed out waiting for deployed service to become live: "
|
|
4656
4858
|
f"{deploy_plan.spec.metadata.name} ({deploy_result.service_id})"
|
|
4657
4859
|
) from exc
|
|
4860
|
+
await _cleanup_replaced_service_images()
|
|
4658
4861
|
return deploy_result
|
|
4659
4862
|
|
|
4660
4863
|
deploy_result = await _run_deploy_operation(
|
|
@@ -31,7 +31,11 @@ from meshagent.api.image_runtime import (
|
|
|
31
31
|
from meshagent.api.error_codes import ErrorCode
|
|
32
32
|
from meshagent.api.room_ports import ROOM_INTERNAL_API_PORT
|
|
33
33
|
from meshagent.cli import async_typer, cli, image
|
|
34
|
-
from meshagent.api.room_server_client import
|
|
34
|
+
from meshagent.api.room_server_client import (
|
|
35
|
+
PublishedBuildImage,
|
|
36
|
+
RoomException,
|
|
37
|
+
ServiceRuntimeState,
|
|
38
|
+
)
|
|
35
39
|
from meshagent.api.specs.service import (
|
|
36
40
|
ContainerMountSpec,
|
|
37
41
|
ContainerSpec,
|
|
@@ -60,6 +64,19 @@ class _FakeParticipant:
|
|
|
60
64
|
return None
|
|
61
65
|
|
|
62
66
|
|
|
67
|
+
def _fake_published_build_image(
|
|
68
|
+
*,
|
|
69
|
+
tag: str = "registry.meshagent.com/repo/web:1",
|
|
70
|
+
resolved_ref: str = "registry.meshagent.com/repo/web@sha256:digest",
|
|
71
|
+
) -> PublishedBuildImage:
|
|
72
|
+
return PublishedBuildImage(
|
|
73
|
+
tag=tag,
|
|
74
|
+
digest="sha256:digest",
|
|
75
|
+
resolved_ref=resolved_ref,
|
|
76
|
+
optimized=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
63
80
|
def _route_for_service(
|
|
64
81
|
*, domain: str, room_name: str, port: str, service_id: str
|
|
65
82
|
) -> Route:
|
|
@@ -298,7 +315,7 @@ async def test_deploy_image_missing_room_prints_create_room_guidance(
|
|
|
298
315
|
)
|
|
299
316
|
|
|
300
317
|
assert exc_info.value.exit_code == 1
|
|
301
|
-
assert printed == [
|
|
318
|
+
assert printed[-1:] == [
|
|
302
319
|
"[red]Room does not exist: missing-room\n"
|
|
303
320
|
"Create it first with "
|
|
304
321
|
"'meshagent rooms create missing-room --if-not-exists', "
|
|
@@ -930,6 +947,14 @@ async def test_pack_image_streams_generated_build_context_and_waits_for_exit(
|
|
|
930
947
|
captured["streamed_context"] = bytes(streamed)
|
|
931
948
|
return "build-1"
|
|
932
949
|
|
|
950
|
+
async def list_builds(self):
|
|
951
|
+
return [
|
|
952
|
+
SimpleNamespace(
|
|
953
|
+
id="build-1",
|
|
954
|
+
published_images=[_fake_published_build_image()],
|
|
955
|
+
)
|
|
956
|
+
]
|
|
957
|
+
|
|
933
958
|
class _FakeRoomClient:
|
|
934
959
|
def __init__(self) -> None:
|
|
935
960
|
self.containers = _FakeContainers()
|
|
@@ -1060,6 +1085,14 @@ async def test_build_image_streams_context_and_waits_for_exit(
|
|
|
1060
1085
|
captured["streamed_context"] = bytes(streamed)
|
|
1061
1086
|
return "build-1"
|
|
1062
1087
|
|
|
1088
|
+
async def list_builds(self):
|
|
1089
|
+
return [
|
|
1090
|
+
SimpleNamespace(
|
|
1091
|
+
id="build-1",
|
|
1092
|
+
published_images=[_fake_published_build_image()],
|
|
1093
|
+
)
|
|
1094
|
+
]
|
|
1095
|
+
|
|
1063
1096
|
class _FakeRoomClient:
|
|
1064
1097
|
def __init__(self) -> None:
|
|
1065
1098
|
self.containers = _FakeContainers()
|
|
@@ -1180,8 +1213,9 @@ async def test_build_image_normalizes_shorthand_room_registry_tag(
|
|
|
1180
1213
|
captured["project_id_arg"] = project_id
|
|
1181
1214
|
return "project-1"
|
|
1182
1215
|
|
|
1183
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
1216
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
1184
1217
|
captured["build_stage_kwargs"] = kwargs
|
|
1218
|
+
return _fake_published_build_image()
|
|
1185
1219
|
|
|
1186
1220
|
monkeypatch.setattr(image, "get_client", _fake_get_client)
|
|
1187
1221
|
monkeypatch.setattr(image, "resolve_project_id", _fake_resolve_project_id)
|
|
@@ -1238,6 +1272,14 @@ async def test_build_image_pack_streams_context_and_defaults_context_path(
|
|
|
1238
1272
|
captured["streamed_context"] = bytes(streamed)
|
|
1239
1273
|
return "build-1"
|
|
1240
1274
|
|
|
1275
|
+
async def list_builds(self):
|
|
1276
|
+
return [
|
|
1277
|
+
SimpleNamespace(
|
|
1278
|
+
id="build-1",
|
|
1279
|
+
published_images=[_fake_published_build_image()],
|
|
1280
|
+
)
|
|
1281
|
+
]
|
|
1282
|
+
|
|
1241
1283
|
class _FakeRoomClient:
|
|
1242
1284
|
def __init__(self) -> None:
|
|
1243
1285
|
self.containers = _FakeContainers()
|
|
@@ -1344,6 +1386,14 @@ async def test_build_image_pack_preserves_ignored_dockerfile_and_dockerignore(
|
|
|
1344
1386
|
pass
|
|
1345
1387
|
return "build-1"
|
|
1346
1388
|
|
|
1389
|
+
async def list_builds(self):
|
|
1390
|
+
return [
|
|
1391
|
+
SimpleNamespace(
|
|
1392
|
+
id="build-1",
|
|
1393
|
+
published_images=[_fake_published_build_image()],
|
|
1394
|
+
)
|
|
1395
|
+
]
|
|
1396
|
+
|
|
1347
1397
|
class _FakeRoomClient:
|
|
1348
1398
|
def __init__(self) -> None:
|
|
1349
1399
|
self.containers = _FakeContainers()
|
|
@@ -1625,6 +1675,14 @@ async def test_build_image_can_disable_room_image_optimization(
|
|
|
1625
1675
|
pass
|
|
1626
1676
|
return "build-1"
|
|
1627
1677
|
|
|
1678
|
+
async def list_builds(self):
|
|
1679
|
+
return [
|
|
1680
|
+
SimpleNamespace(
|
|
1681
|
+
id="build-1",
|
|
1682
|
+
published_images=[_fake_published_build_image()],
|
|
1683
|
+
)
|
|
1684
|
+
]
|
|
1685
|
+
|
|
1628
1686
|
class _FakeRoomClient:
|
|
1629
1687
|
def __init__(self) -> None:
|
|
1630
1688
|
self.containers = _FakeContainers()
|
|
@@ -1896,6 +1954,21 @@ async def test_run_image_build_stage_requests_repository_token_and_prepends_regi
|
|
|
1896
1954
|
captured["build_kwargs"] = kwargs
|
|
1897
1955
|
return "build-1"
|
|
1898
1956
|
|
|
1957
|
+
async def list_builds(self):
|
|
1958
|
+
return [
|
|
1959
|
+
SimpleNamespace(
|
|
1960
|
+
id="build-1",
|
|
1961
|
+
published_images=[
|
|
1962
|
+
_fake_published_build_image(
|
|
1963
|
+
tag="registry.meshagent.com/powerboards/test:latest",
|
|
1964
|
+
resolved_ref=(
|
|
1965
|
+
"registry.meshagent.com/powerboards/test@sha256:digest"
|
|
1966
|
+
),
|
|
1967
|
+
)
|
|
1968
|
+
],
|
|
1969
|
+
)
|
|
1970
|
+
]
|
|
1971
|
+
|
|
1899
1972
|
class _FakeRoomClient:
|
|
1900
1973
|
def __init__(self) -> None:
|
|
1901
1974
|
self.containers = _FakeContainersClient()
|
|
@@ -2999,6 +3072,81 @@ async def test_delete_built_image_from_room_cache_times_out(
|
|
|
2999
3072
|
)
|
|
3000
3073
|
|
|
3001
3074
|
|
|
3075
|
+
def test_built_service_image_refs_selects_built_refs() -> None:
|
|
3076
|
+
old_digest = "sha256:" + "a" * 64
|
|
3077
|
+
new_digest = "sha256:" + "b" * 64
|
|
3078
|
+
other_digest = "sha256:" + "c" * 64
|
|
3079
|
+
existing_service = ServiceSpec(
|
|
3080
|
+
version="v1",
|
|
3081
|
+
kind="Service",
|
|
3082
|
+
metadata=ServiceMetadata(name="repo-web"),
|
|
3083
|
+
container=ContainerSpec(
|
|
3084
|
+
image=f"registry.meshagent.com/repo/web@{old_digest}",
|
|
3085
|
+
storage=ContainerMountSpec(
|
|
3086
|
+
images=[
|
|
3087
|
+
ImageStorageMountSpec(
|
|
3088
|
+
image=f"registry.meshagent.com/repo/web@{old_digest}",
|
|
3089
|
+
path="/app",
|
|
3090
|
+
),
|
|
3091
|
+
ImageStorageMountSpec(
|
|
3092
|
+
image=f"registry.meshagent.com/repo/web@{new_digest}",
|
|
3093
|
+
path="/current",
|
|
3094
|
+
),
|
|
3095
|
+
ImageStorageMountSpec(
|
|
3096
|
+
image=f"registry.meshagent.com/repo/other@{other_digest}",
|
|
3097
|
+
path="/other",
|
|
3098
|
+
),
|
|
3099
|
+
ImageStorageMountSpec(
|
|
3100
|
+
image="meshagent/node:default",
|
|
3101
|
+
path="/runtime",
|
|
3102
|
+
),
|
|
3103
|
+
ImageStorageMountSpec(
|
|
3104
|
+
image="registry.meshagent.com/repo/web:latest",
|
|
3105
|
+
path="/tag",
|
|
3106
|
+
),
|
|
3107
|
+
ImageStorageMountSpec(
|
|
3108
|
+
image="registry.meshagent.com/repo/web:old",
|
|
3109
|
+
path="/old-tag",
|
|
3110
|
+
),
|
|
3111
|
+
]
|
|
3112
|
+
),
|
|
3113
|
+
),
|
|
3114
|
+
)
|
|
3115
|
+
refs = image._built_service_image_refs(
|
|
3116
|
+
service_spec=existing_service,
|
|
3117
|
+
parsed_tag=image._parse_build_tag("registry.meshagent.com/repo/web:1"),
|
|
3118
|
+
project_registry="registry.meshagent.com",
|
|
3119
|
+
)
|
|
3120
|
+
|
|
3121
|
+
assert refs == [
|
|
3122
|
+
f"registry.meshagent.com/repo/web@{old_digest}",
|
|
3123
|
+
f"registry.meshagent.com/repo/web@{new_digest}",
|
|
3124
|
+
"registry.meshagent.com/repo/web:latest",
|
|
3125
|
+
"registry.meshagent.com/repo/web:old",
|
|
3126
|
+
]
|
|
3127
|
+
|
|
3128
|
+
|
|
3129
|
+
@pytest.mark.asyncio
|
|
3130
|
+
async def test_delete_replaced_built_service_images_ignores_missing_image() -> None:
|
|
3131
|
+
captured: dict[str, object] = {}
|
|
3132
|
+
|
|
3133
|
+
class _FakeContainers:
|
|
3134
|
+
async def delete_image(self, *, image: str) -> None:
|
|
3135
|
+
captured["deleted_image"] = image
|
|
3136
|
+
raise RoomException("image not found", code=ErrorCode.NOT_FOUND)
|
|
3137
|
+
|
|
3138
|
+
client = SimpleNamespace(containers=_FakeContainers())
|
|
3139
|
+
|
|
3140
|
+
await image._delete_replaced_built_service_images(
|
|
3141
|
+
client=client,
|
|
3142
|
+
image_refs=["registry.meshagent.com/repo/web@sha256:" + "a" * 64],
|
|
3143
|
+
)
|
|
3144
|
+
|
|
3145
|
+
assert captured["deleted_image"] == (
|
|
3146
|
+
"registry.meshagent.com/repo/web@sha256:" + "a" * 64
|
|
3147
|
+
)
|
|
3148
|
+
|
|
3149
|
+
|
|
3002
3150
|
@pytest.mark.asyncio
|
|
3003
3151
|
async def test_apply_deploy_plan_reports_service_apply_steps() -> None:
|
|
3004
3152
|
statuses: list[str] = []
|
|
@@ -3153,9 +3301,10 @@ async def test_deploy_image_pack_builds_before_deploying(
|
|
|
3153
3301
|
encoding="utf-8",
|
|
3154
3302
|
)
|
|
3155
3303
|
|
|
3156
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
3304
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3157
3305
|
captured["build_kwargs"] = kwargs
|
|
3158
3306
|
captured["events"].append("build")
|
|
3307
|
+
return _fake_published_build_image()
|
|
3159
3308
|
|
|
3160
3309
|
async def _fake_run_deploy_domain_prompt_tui(
|
|
3161
3310
|
*, service_name: str, port: str, room_name: str, pages_domain: str
|
|
@@ -3301,7 +3450,9 @@ async def test_deploy_image_pack_builds_before_deploying(
|
|
|
3301
3450
|
assert isinstance(created_service, tuple)
|
|
3302
3451
|
service_spec = created_service[2]
|
|
3303
3452
|
assert service_spec.container is not None
|
|
3304
|
-
assert
|
|
3453
|
+
assert (
|
|
3454
|
+
service_spec.container.image == "registry.meshagent.com/repo/web@sha256:digest"
|
|
3455
|
+
)
|
|
3305
3456
|
assert service_spec.metadata.annotations is not None
|
|
3306
3457
|
assert image.ANNOTATION_REQUEST_VALIDATION_METHOD not in (
|
|
3307
3458
|
service_spec.metadata.annotations
|
|
@@ -3329,9 +3480,10 @@ async def test_deploy_image_pack_fails_before_build_when_env_secret_is_missing(
|
|
|
3329
3480
|
encoding="utf-8",
|
|
3330
3481
|
)
|
|
3331
3482
|
|
|
3332
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
3483
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3333
3484
|
del kwargs
|
|
3334
3485
|
captured["build_called"] = True
|
|
3486
|
+
return _fake_published_build_image()
|
|
3335
3487
|
|
|
3336
3488
|
class _FakeSecrets:
|
|
3337
3489
|
async def exists(
|
|
@@ -3427,8 +3579,9 @@ async def test_deploy_image_pack_requires_matching_volume_mount(
|
|
|
3427
3579
|
encoding="utf-8",
|
|
3428
3580
|
)
|
|
3429
3581
|
|
|
3430
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
3582
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3431
3583
|
captured["build_kwargs"] = kwargs
|
|
3584
|
+
return _fake_published_build_image()
|
|
3432
3585
|
|
|
3433
3586
|
class _FakeRoomClient:
|
|
3434
3587
|
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
@@ -3497,9 +3650,10 @@ async def test_deploy_image_pack_allows_matching_volume_mount(
|
|
|
3497
3650
|
encoding="utf-8",
|
|
3498
3651
|
)
|
|
3499
3652
|
|
|
3500
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
3653
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3501
3654
|
captured["build_kwargs"] = kwargs
|
|
3502
3655
|
captured["events"].append("build")
|
|
3656
|
+
return _fake_published_build_image()
|
|
3503
3657
|
|
|
3504
3658
|
class _FakeRoomClient:
|
|
3505
3659
|
def __init__(self) -> None:
|
|
@@ -3594,9 +3748,10 @@ CMD ["/app/dist/index.js"]
|
|
|
3594
3748
|
encoding="utf-8",
|
|
3595
3749
|
)
|
|
3596
3750
|
|
|
3597
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
3751
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3598
3752
|
captured["build_kwargs"] = kwargs
|
|
3599
3753
|
captured["events"].append("build")
|
|
3754
|
+
return _fake_published_build_image()
|
|
3600
3755
|
|
|
3601
3756
|
class _FakeRoomClient:
|
|
3602
3757
|
def __init__(self) -> None:
|
|
@@ -3670,7 +3825,7 @@ CMD ["/app/dist/index.js"]
|
|
|
3670
3825
|
assert service_spec.container.storage.images is not None
|
|
3671
3826
|
assert service_spec.container.storage.images == [
|
|
3672
3827
|
ImageStorageMountSpec(
|
|
3673
|
-
image="registry.meshagent.com/repo/web:
|
|
3828
|
+
image="registry.meshagent.com/repo/web@sha256:digest",
|
|
3674
3829
|
path=IMAGE_RUNTIME_MOUNT_PATH,
|
|
3675
3830
|
subpath=IMAGE_RUNTIME_MOUNT_SUBPATH,
|
|
3676
3831
|
read_only=True,
|
|
@@ -3912,9 +4067,10 @@ async def test_deploy_image_pack_domain_uses_inferred_exposed_port(
|
|
|
3912
4067
|
encoding="utf-8",
|
|
3913
4068
|
)
|
|
3914
4069
|
|
|
3915
|
-
async def _fake_run_image_build_stage(**kwargs) ->
|
|
4070
|
+
async def _fake_run_image_build_stage(**kwargs) -> PublishedBuildImage:
|
|
3916
4071
|
captured["build_kwargs"] = kwargs
|
|
3917
4072
|
captured["events"].append("build")
|
|
4073
|
+
return _fake_published_build_image()
|
|
3918
4074
|
|
|
3919
4075
|
class _FakeRoomClient:
|
|
3920
4076
|
def __init__(self) -> None:
|
|
@@ -116,7 +116,7 @@ def test_switch_active_profile_matches_email_and_updates_process_api_url(
|
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def
|
|
119
|
+
def test_resolve_api_url_prefers_explicit_then_environment_then_profile(
|
|
120
120
|
tmp_path,
|
|
121
121
|
monkeypatch,
|
|
122
122
|
) -> None:
|
|
@@ -134,7 +134,7 @@ def test_resolve_api_url_prefers_explicit_then_profile_then_environment(
|
|
|
134
134
|
api_url="https://profile.meshagent.test",
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
assert local_settings.resolve_api_url() == "https://
|
|
137
|
+
assert local_settings.resolve_api_url() == "https://env.meshagent.test"
|
|
138
138
|
assert (
|
|
139
139
|
local_settings.resolve_api_url(api_url="https://explicit.meshagent.test/")
|
|
140
140
|
== "https://explicit.meshagent.test"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.42.2"
|