dev-bubble 0.7.20__tar.gz → 0.7.21__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.
- {dev_bubble-0.7.20/dev_bubble.egg-info → dev_bubble-0.7.21}/PKG-INFO +1 -1
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/__init__.py +1 -1
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/auth_proxy.py +10 -6
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/config.py +9 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/runtime/incus.py +18 -10
- {dev_bubble-0.7.20 → dev_bubble-0.7.21/dev_bubble.egg-info}/PKG-INFO +1 -1
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/dev_bubble.egg-info/SOURCES.txt +1 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/conftest.py +8 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_auth_proxy.py +56 -1
- dev_bubble-0.7.21/tests/test_get_info_remote.py +138 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/.claude/CLAUDE.md +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/.github/workflows/ci.yml +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/.github/workflows/publish.yml +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/.gitignore +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/CHANGELOG.md +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/LICENSE +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/README.md +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/SPEC.md +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/__main__.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/ai.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/automation.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/clean.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/cli.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/clone.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/cloud.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/cloud_types.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/__init__.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/cloud_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/completion.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/doctor.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/images.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/infrastructure.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/internal.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/lifecycle.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/list_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/relay_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/remote_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/security_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/settings.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/commands/status_cmd.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/container_helpers.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/data/skill.md +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/default_repos.json +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/finalization.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/git_store.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/github_token.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/graphql_validator.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/hooks/__init__.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/hooks/lean.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/hooks/python.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/image_management.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/__init__.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/builder.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/base.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/cloud-init.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/lean-toolchain.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/lean.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/python.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/claude.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/codex.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/elan.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/emacs.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/gh.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/neovim.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/pi.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/pins.json +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/uv.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/images/scripts/tools/vscode.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/incus_bridge.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/lean.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/lifecycle.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/naming.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/network.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/notices.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/output.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/provisioning.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/relay.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/remote.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/repo_registry.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/runtime/__init__.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/runtime/base.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/runtime/colima.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/security.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/setup.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/skill.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/spinner.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/target.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/token_store.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/tools.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/tunnel.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/bubble/vscode.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/config/com.bubble.git-update.plist +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/config/com.bubble.image-refresh.plist +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/config/com.bubble.relay-daemon.plist +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/conftest.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/dev_bubble.egg-info/dependency_links.txt +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/dev_bubble.egg-info/entry_points.txt +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/dev_bubble.egg-info/requires.txt +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/dev_bubble.egg-info/top_level.txt +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/pyproject.toml +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/scratch/forkproxy-repro.sh +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/setup.cfg +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_ai.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_authorized_keys.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_branch_no_target.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_bridge_listener.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_build_lock.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_claude_projects_symlink.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_cloud.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_colima.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_completion.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_config.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_customize.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_editor.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_ephemeral.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_git_store.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_github_security_override.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_github_token.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_graphql_validator.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_hooks.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_integration.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_internal.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_lifecycle.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_list_columns.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_list_remote.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_mounts.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_multi_target.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_naming.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_network.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_notices.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_reattach_network.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_relay.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_remote.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_repo_registry.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_security.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_skill.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_spinner.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_status.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_systemd_path.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_target.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_token_no_argv_leak.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_tools.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_tunnel.py +0 -0
- {dev_bubble-0.7.20 → dev_bubble-0.7.21}/tests/test_vscode.py +0 -0
|
@@ -76,14 +76,18 @@ from urllib.request import (
|
|
|
76
76
|
build_opener,
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
-
from .config import
|
|
79
|
+
from .config import AUTH_PROXY_DIR
|
|
80
80
|
from .token_store import RateLimiter as _RateLimiter
|
|
81
81
|
from .token_store import RateWindow, TokenStore, setup_file_logging
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
# The daemon is a host singleton that always uses ~/.bubble (see
|
|
84
|
+
# AUTH_PROXY_DIR in config.py). Both the daemon and callers running under a
|
|
85
|
+
# custom BUBBLE_HOME must resolve these files against that fixed location so
|
|
86
|
+
# they agree on where the endpoint and per-container tokens live.
|
|
87
|
+
AUTH_PROXY_PORT_FILE = AUTH_PROXY_DIR / "auth-proxy.port"
|
|
88
|
+
AUTH_PROXY_ENDPOINT_FILE = AUTH_PROXY_DIR / "auth-proxy.endpoint"
|
|
89
|
+
AUTH_PROXY_LOG = AUTH_PROXY_DIR / "auth-proxy.log"
|
|
90
|
+
AUTH_PROXY_TOKENS = AUTH_PROXY_DIR / "auth-tokens.json"
|
|
87
91
|
|
|
88
92
|
# Default port (configurable via config.toml)
|
|
89
93
|
DEFAULT_PORT = 7654
|
|
@@ -1574,7 +1578,7 @@ def run_daemon(port: int = 0):
|
|
|
1574
1578
|
port: Port to listen on. 0 means use config or default.
|
|
1575
1579
|
"""
|
|
1576
1580
|
_setup_logging()
|
|
1577
|
-
|
|
1581
|
+
AUTH_PROXY_DIR.mkdir(parents=True, exist_ok=True)
|
|
1578
1582
|
|
|
1579
1583
|
if not port:
|
|
1580
1584
|
from .config import load_config
|
|
@@ -20,6 +20,15 @@ import tomli_w
|
|
|
20
20
|
# Override with BUBBLE_HOME environment variable
|
|
21
21
|
DATA_DIR = Path(os.environ.get("BUBBLE_HOME", Path.home() / ".bubble"))
|
|
22
22
|
CONFIG_FILE = DATA_DIR / "config.toml"
|
|
23
|
+
|
|
24
|
+
# The auth-proxy daemon is a host singleton, installed via launchd/systemd
|
|
25
|
+
# with no BUBBLE_HOME in its environment, so it always runs against the
|
|
26
|
+
# default ~/.bubble and writes its endpoint/port/token/log files there.
|
|
27
|
+
# Callers (e.g. `bubble open`) may run under a custom BUBBLE_HOME, but must
|
|
28
|
+
# resolve those daemon files against the daemon's fixed location rather than
|
|
29
|
+
# their own DATA_DIR — otherwise they look for an endpoint the daemon never
|
|
30
|
+
# wrote and write tokens the daemon never reads. See issue #304.
|
|
31
|
+
AUTH_PROXY_DIR = Path.home() / ".bubble"
|
|
23
32
|
REGISTRY_FILE = DATA_DIR / "registry.json"
|
|
24
33
|
GIT_DIR = DATA_DIR / "git"
|
|
25
34
|
REPOS_FILE = DATA_DIR / "repos.json"
|
|
@@ -199,17 +199,25 @@ class IncusRuntime(ContainerRuntime):
|
|
|
199
199
|
def _get_info(self, name: str) -> ContainerInfo:
|
|
200
200
|
"""Get info for a single container.
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
Passes the remote scope and the name filter as *separate*
|
|
203
|
+
arguments (``incus list <remote>: name=<name>``). Concatenating
|
|
204
|
+
them into one token (``incus list <remote>:<name>``) breaks on
|
|
205
|
+
recent incus versions: a ``list`` argument that doesn't end in
|
|
206
|
+
``:`` is treated as a name filter on the *default* remote, so it
|
|
207
|
+
matches nothing on a non-default remote (e.g. ``bubble-colima``
|
|
208
|
+
on macOS). The bare ``<remote>:`` token reliably scopes the list
|
|
209
|
+
to the remote, the same way the no-name path in
|
|
210
|
+
``list_containers`` already relies on.
|
|
211
|
+
|
|
212
|
+
``name=<name>`` is matched as a substring on some incus versions,
|
|
213
|
+
so we still confirm an exact name match before returning.
|
|
209
214
|
"""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
scope = [self._q("")] if self._remote else []
|
|
216
|
+
data = self._run_json(["list", *scope, f"name={name}"])
|
|
217
|
+
if isinstance(data, list):
|
|
218
|
+
for c in data:
|
|
219
|
+
if c.get("name") == name:
|
|
220
|
+
return self._parse_container(c)
|
|
213
221
|
raise RuntimeError(f"Container '{name}' not found")
|
|
214
222
|
|
|
215
223
|
def list_containers(self, fast: bool = True) -> list[ContainerInfo]:
|
|
@@ -156,7 +156,15 @@ def tmp_data_dir(tmp_path, monkeypatch):
|
|
|
156
156
|
try:
|
|
157
157
|
import bubble.auth_proxy as auth_proxy_mod
|
|
158
158
|
|
|
159
|
+
# These files are pinned to the fixed singleton ~/.bubble
|
|
160
|
+
# (AUTH_PROXY_DIR), not DATA_DIR, so they don't follow BUBBLE_HOME
|
|
161
|
+
# (issue #304). Patch all four explicitly to keep tests off the
|
|
162
|
+
# real ~/.bubble.
|
|
159
163
|
monkeypatch.setattr(auth_proxy_mod, "AUTH_PROXY_PORT_FILE", data_dir / "auth-proxy.port")
|
|
164
|
+
monkeypatch.setattr(
|
|
165
|
+
auth_proxy_mod, "AUTH_PROXY_ENDPOINT_FILE", data_dir / "auth-proxy.endpoint"
|
|
166
|
+
)
|
|
167
|
+
monkeypatch.setattr(auth_proxy_mod, "AUTH_PROXY_LOG", data_dir / "auth-proxy.log")
|
|
160
168
|
monkeypatch.setattr(auth_proxy_mod, "AUTH_PROXY_TOKENS", data_dir / "auth-tokens.json")
|
|
161
169
|
except ImportError:
|
|
162
170
|
pass
|
|
@@ -24,6 +24,48 @@ from bubble.auth_proxy import (
|
|
|
24
24
|
validate_path,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Daemon file location (issue #304)
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestDaemonFileLocation:
|
|
33
|
+
"""The auth-proxy daemon is a host singleton pinned to ~/.bubble.
|
|
34
|
+
|
|
35
|
+
Its endpoint/port/token/log files must resolve against that fixed
|
|
36
|
+
location even when the caller runs under a custom BUBBLE_HOME, or the
|
|
37
|
+
caller looks for an endpoint the daemon never wrote and writes tokens
|
|
38
|
+
the daemon never reads (issue #304).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def test_files_ignore_bubble_home(self, tmp_path, monkeypatch):
|
|
42
|
+
import importlib
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
import bubble.auth_proxy
|
|
46
|
+
import bubble.config
|
|
47
|
+
|
|
48
|
+
fixed = Path.home() / ".bubble"
|
|
49
|
+
try:
|
|
50
|
+
monkeypatch.setenv("BUBBLE_HOME", str(tmp_path))
|
|
51
|
+
importlib.reload(bubble.config)
|
|
52
|
+
importlib.reload(bubble.auth_proxy)
|
|
53
|
+
|
|
54
|
+
# DATA_DIR follows BUBBLE_HOME ...
|
|
55
|
+
assert bubble.config.DATA_DIR == tmp_path
|
|
56
|
+
# ... but the daemon's files stay at the fixed singleton location.
|
|
57
|
+
assert bubble.config.AUTH_PROXY_DIR == fixed
|
|
58
|
+
assert bubble.auth_proxy.AUTH_PROXY_ENDPOINT_FILE == fixed / "auth-proxy.endpoint"
|
|
59
|
+
assert bubble.auth_proxy.AUTH_PROXY_PORT_FILE == fixed / "auth-proxy.port"
|
|
60
|
+
assert bubble.auth_proxy.AUTH_PROXY_TOKENS == fixed / "auth-tokens.json"
|
|
61
|
+
assert bubble.auth_proxy.AUTH_PROXY_LOG == fixed / "auth-proxy.log"
|
|
62
|
+
finally:
|
|
63
|
+
# Restore default module state for subsequent tests.
|
|
64
|
+
monkeypatch.delenv("BUBBLE_HOME", raising=False)
|
|
65
|
+
importlib.reload(bubble.config)
|
|
66
|
+
importlib.reload(bubble.auth_proxy)
|
|
67
|
+
|
|
68
|
+
|
|
27
69
|
# ---------------------------------------------------------------------------
|
|
28
70
|
# Token management
|
|
29
71
|
# ---------------------------------------------------------------------------
|
|
@@ -1669,7 +1711,14 @@ class TestApiProxyIntegration:
|
|
|
1669
1711
|
|
|
1670
1712
|
@pytest.fixture
|
|
1671
1713
|
def auth_proxy_env(tmp_path, monkeypatch):
|
|
1672
|
-
"""
|
|
1714
|
+
"""Isolate the auth_proxy daemon's files into tmp_path.
|
|
1715
|
+
|
|
1716
|
+
The daemon's endpoint/port/token/log files are pinned to the fixed
|
|
1717
|
+
singleton location ~/.bubble (AUTH_PROXY_DIR), independent of
|
|
1718
|
+
BUBBLE_HOME (see issue #304), so setting BUBBLE_HOME alone would leave
|
|
1719
|
+
these tests writing the real ~/.bubble/auth-tokens.json. Monkeypatch
|
|
1720
|
+
the module constants directly to keep the tests hermetic.
|
|
1721
|
+
"""
|
|
1673
1722
|
import importlib
|
|
1674
1723
|
|
|
1675
1724
|
import bubble.auth_proxy
|
|
@@ -1678,4 +1727,10 @@ def auth_proxy_env(tmp_path, monkeypatch):
|
|
|
1678
1727
|
monkeypatch.setenv("BUBBLE_HOME", str(tmp_path))
|
|
1679
1728
|
importlib.reload(bubble.config)
|
|
1680
1729
|
importlib.reload(bubble.auth_proxy)
|
|
1730
|
+
monkeypatch.setattr(bubble.auth_proxy, "AUTH_PROXY_PORT_FILE", tmp_path / "auth-proxy.port")
|
|
1731
|
+
monkeypatch.setattr(
|
|
1732
|
+
bubble.auth_proxy, "AUTH_PROXY_ENDPOINT_FILE", tmp_path / "auth-proxy.endpoint"
|
|
1733
|
+
)
|
|
1734
|
+
monkeypatch.setattr(bubble.auth_proxy, "AUTH_PROXY_LOG", tmp_path / "auth-proxy.log")
|
|
1735
|
+
monkeypatch.setattr(bubble.auth_proxy, "AUTH_PROXY_TOKENS", tmp_path / "auth-tokens.json")
|
|
1681
1736
|
return tmp_path
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Regression test for issue #300: container lookup on a non-default remote.
|
|
2
|
+
|
|
3
|
+
On macOS/Colima, ``IncusRuntime`` is constructed with
|
|
4
|
+
``remote="bubble-colima"``. Recent incus clients treat a single
|
|
5
|
+
concatenated ``list`` token (``incus list <remote>:<name>``) as a name
|
|
6
|
+
filter on the *default* remote, so it matches nothing on a non-default
|
|
7
|
+
remote and ``_get_info`` raised "Container not found" even when the
|
|
8
|
+
container was running (breaking the base-image rebuild path).
|
|
9
|
+
|
|
10
|
+
``_get_info`` must instead pass the remote scope and the name filter as
|
|
11
|
+
*separate* arguments (``incus list <remote>: name=<name>``).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import subprocess
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from bubble.runtime.incus import IncusRuntime
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fake_subprocess(records, containers):
|
|
25
|
+
"""A ``_run_subprocess`` stand-in that records argv and serves JSON.
|
|
26
|
+
|
|
27
|
+
The fake models incus's ``name=`` filtering so tests exercise the real
|
|
28
|
+
behavior ``_get_info`` depends on, not just the argv shape: a ``list``
|
|
29
|
+
with a ``name=<value>`` token returns *containers* whose names contain
|
|
30
|
+
``<value>`` as a substring, mirroring incus versions where ``name=``
|
|
31
|
+
over-matches.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def run(self, cmd, *, capture=True):
|
|
35
|
+
records.append(cmd)
|
|
36
|
+
name_filter = next((a[len("name=") :] for a in cmd if a.startswith("name=")), None)
|
|
37
|
+
if name_filter is not None:
|
|
38
|
+
result = [c for c in containers if name_filter in c["name"]]
|
|
39
|
+
else:
|
|
40
|
+
result = containers
|
|
41
|
+
return subprocess.CompletedProcess(cmd, 0, stdout=json.dumps(result), stderr="")
|
|
42
|
+
|
|
43
|
+
return run
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _container(name, status="Running"):
|
|
47
|
+
return {"name": name, "status": status, "state": {}}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_get_info_uses_separate_remote_and_filter_tokens(monkeypatch):
|
|
51
|
+
records: list[list[str]] = []
|
|
52
|
+
monkeypatch.setattr(
|
|
53
|
+
IncusRuntime,
|
|
54
|
+
"_run_subprocess",
|
|
55
|
+
_fake_subprocess(records, [_container("base-builder")]),
|
|
56
|
+
)
|
|
57
|
+
rt = IncusRuntime(remote="bubble-colima")
|
|
58
|
+
info = rt._get_info("base-builder")
|
|
59
|
+
assert info.name == "base-builder"
|
|
60
|
+
# Exactly one incus invocation, with the remote scope and name filter as
|
|
61
|
+
# *separate* argv tokens (never the broken "bubble-colima:base-builder").
|
|
62
|
+
assert len(records) == 1
|
|
63
|
+
cmd = records[0]
|
|
64
|
+
assert cmd == ["incus", "list", "bubble-colima:", "name=base-builder", "--format=json"]
|
|
65
|
+
assert "bubble-colima:base-builder" not in cmd
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_get_info_no_remote_omits_scope_token(monkeypatch):
|
|
69
|
+
records: list[list[str]] = []
|
|
70
|
+
monkeypatch.setattr(
|
|
71
|
+
IncusRuntime,
|
|
72
|
+
"_run_subprocess",
|
|
73
|
+
_fake_subprocess(records, [_container("foo")]),
|
|
74
|
+
)
|
|
75
|
+
rt = IncusRuntime()
|
|
76
|
+
info = rt._get_info("foo")
|
|
77
|
+
assert info.name == "foo"
|
|
78
|
+
assert records[0] == ["incus", "list", "name=foo", "--format=json"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_get_info_exact_match_among_substring_matches(monkeypatch):
|
|
82
|
+
# Some incus versions match `name=base` as a substring, returning both
|
|
83
|
+
# "base" and "base-builder"; _get_info must return the exact match.
|
|
84
|
+
records: list[list[str]] = []
|
|
85
|
+
monkeypatch.setattr(
|
|
86
|
+
IncusRuntime,
|
|
87
|
+
"_run_subprocess",
|
|
88
|
+
_fake_subprocess(records, [_container("base-builder"), _container("base")]),
|
|
89
|
+
)
|
|
90
|
+
rt = IncusRuntime(remote="bubble-colima")
|
|
91
|
+
info = rt._get_info("base")
|
|
92
|
+
assert info.name == "base"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_get_info_not_found_raises(monkeypatch):
|
|
96
|
+
records: list[list[str]] = []
|
|
97
|
+
monkeypatch.setattr(
|
|
98
|
+
IncusRuntime,
|
|
99
|
+
"_run_subprocess",
|
|
100
|
+
_fake_subprocess(records, []),
|
|
101
|
+
)
|
|
102
|
+
rt = IncusRuntime(remote="bubble-colima")
|
|
103
|
+
with pytest.raises(RuntimeError, match="not found"):
|
|
104
|
+
rt._get_info("missing")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_get_info_superstring_only_is_not_a_match(monkeypatch):
|
|
108
|
+
# Asking for "base-build" when only "base-builder" exists must NOT match.
|
|
109
|
+
records: list[list[str]] = []
|
|
110
|
+
monkeypatch.setattr(
|
|
111
|
+
IncusRuntime,
|
|
112
|
+
"_run_subprocess",
|
|
113
|
+
_fake_subprocess(records, [_container("base-builder")]),
|
|
114
|
+
)
|
|
115
|
+
rt = IncusRuntime(remote="bubble-colima")
|
|
116
|
+
with pytest.raises(RuntimeError, match="not found"):
|
|
117
|
+
rt._get_info("base-build")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_launch_then_lookup_on_remote(monkeypatch):
|
|
121
|
+
"""Reproduces issue #300 end-to-end: launch base-builder on the
|
|
122
|
+
bubble-colima remote, then look it up via the separate-token filter."""
|
|
123
|
+
records: list[list[str]] = []
|
|
124
|
+
monkeypatch.setattr(
|
|
125
|
+
IncusRuntime,
|
|
126
|
+
"_run_subprocess",
|
|
127
|
+
_fake_subprocess(records, [_container("base-builder")]),
|
|
128
|
+
)
|
|
129
|
+
rt = IncusRuntime(remote="bubble-colima")
|
|
130
|
+
info = rt.launch("base-builder", "base")
|
|
131
|
+
assert info.name == "base-builder"
|
|
132
|
+
launch_cmd, list_cmd = records
|
|
133
|
+
# launch uses remote:name as a single resource identifier (correct).
|
|
134
|
+
assert launch_cmd == ["incus", "launch", "bubble-colima:base", "bubble-colima:base-builder"]
|
|
135
|
+
# the follow-up lookup must NOT concatenate remote and name.
|
|
136
|
+
assert "bubble-colima:base-builder" not in list_cmd
|
|
137
|
+
assert "bubble-colima:" in list_cmd
|
|
138
|
+
assert "name=base-builder" in list_cmd
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|