Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/specifications/remote-docker-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ root 所有か rootless Docker の可能性を警告して続行する。控え
(ssh 先のときだけ)docker が実際に使う context(環境変数を外さない `docker context show`)」の
順で決める。解決した context があればローカル端末でもフラット URI に付ける。Remote-SSH 統合
端末でネスト URI と `settings.context` の両方が付くときは、手元の VS Code に同名の context が
あれば直接 attach できるフラット URI を info で提示する。
あれば直接 attach できるフラット URI を info で提示し、`DEVBASE_EDITOR_SSH_HOST=`(空)で
恒久的にそのフラット URI へ切り替えられることも示す。空文字は自動検出のオプトアウトで、
ネストを付けず `settings.context` だけを残す。明示と devbase の解決結果は ssh の有無に
よらず付けるが、`docker context show` の推測は ssh 先のときに限るため、推測だけが元の
ときは空文字で `settings.context` も消える。その場合は恒久化の案内を出さない。

```mermaid
sequenceDiagram
Expand Down
15 changes: 13 additions & 2 deletions docs/user/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ DEVBASE_ACCOUNT_GROUP=kkg
| `DEVBASE_EDITOR` | 起動コマンド(既定: `code`)。`cursor` / `code-insiders` 等も可 |
| `DEVBASE_WORKSPACE` | 開く `*.code-workspace` ファイルの**コンテナ内絶対パス**を明示指定する(例 `/home/ubuntu/share/work/uttarov2-doc.workspace`)。**効くのはリポジトリ 1 件の構成だけ**です。2 件以上の構成では `devbase up` が自動生成した `/work/<プロジェクト名>.code-workspace` を直接開くため、この env を設定しても上書きできません。`~/share`(= 全コンテナ共有ボリューム `/persistent/ai/share` への symlink)配下に置けば全コンテナで共用可 |
| `DEVBASE_OPEN_INDEX` | scale 時に開く dev インスタンス番号(既定: `1`) |
| `DEVBASE_EDITOR_SSH_HOST` | Remote-SSH 跨ホスト構成での ssh-remote ホスト名(例 `mac2`)。**通常は `~/.vscode-server` から自動検出**され不要。検出が外れる場合のみ明示。下記「リモート Docker」参照 |
| `DEVBASE_EDITOR_SSH_HOST` | Remote-SSH 跨ホスト構成での ssh-remote ホスト名(例 `mac2`)。**通常は `~/.vscode-server` から自動検出**され不要。検出が外れる場合のみ明示。**空文字(`DEVBASE_EDITOR_SSH_HOST=`)はネストのオプトアウト**で、フラット URI を強制する。下記「リモート Docker」参照 |
| `DEVBASE_EDITOR_DOCKER_CONTEXT` | attach に使う docker context を手で決めたいときだけ明示する。未設定なら devbase が解決した context(`--context` / `DEVBASE_DOCKER_CONTEXT` / `project.local.yml`)、それも無ければ跨ホスト時にホストの `docker context show` |
| `DEVBASE_DOCKER_CONTEXT` | `devbase up/down/ps/logs/login/scale/build/rebuild` が向ける docker context。`project.local.yml` の `docker.context` より優先し、CLI `--context` に負ける。グローバル `.env` に書くと全プロジェクトが同じホストへ向くため、通常は `project.local.yml` に書く。下記「リモート Docker」参照 |
| `DEVBASE_WINDOW_TITLE` | attach 先 VS Code の `window.title` テンプレート。`{container}` が実コンテナ名(例 `nyle-dx-dev-1`)に置換される。既定は `{container}${separator}${dirty}${activeEditorShort}`。`0` / `false` / `off` / 空文字で無効化。下記「ウィンドウタイトル」参照 |
Expand Down Expand Up @@ -404,7 +404,7 @@ gid が変わったらファイルを消すか `docker.gid` を書いてくだ
| ローカル端末(Mac / Linux / WSL) | 同じマシン(従来) | フラット URI |
| ローカル端末 | リモート context | フラット URI + `settings.context=<ctx>`。手元の Dev Containers 拡張がその context 経由で attach する |
| Remote-SSH 統合ターミナル(Windows VS Code → Mac) | Mac | ネスト URI `…@ssh-remote+<host>`(下記) |
| Remote-SSH 統合ターミナル | リモート context(WSL / EC2 など) | ネスト URI + `settings.context=<ctx>`。Mac の Dev Containers が context 経由で attach する。あわせて、手元の VS Code に同名の context があれば直接 attach できるフラット URI も表示する(Windows → Mac → WSL(Windows) の一周を避けたいとき) |
| Remote-SSH 統合ターミナル | リモート context(WSL / EC2 など) | ネスト URI + `settings.context=<ctx>`。Mac の Dev Containers が context 経由で attach する。あわせて、手元の VS Code に同名の context があれば直接 attach できるフラット URI も表示する(Windows → Mac → WSL(Windows) の一周を避けたいとき)。毎回そのフラット URI で開きたければ `DEVBASE_EDITOR_SSH_HOST=`(空)を書く(下記「手元の context で直接開く」) |

`settings.context` は「`DEVBASE_EDITOR_DOCKER_CONTEXT` の明示 → devbase が解決した context →
(ssh 先のときだけ)`docker context show`」の順で決まります。
Expand All @@ -430,6 +430,17 @@ DEVBASE_EDITOR_SSH_HOST=mac2

> 同一ホスト構成(手元 Mac/Linux で直接、または ssh 先の Docker にコンテナが無い場合)では ssh-remote ホストは付かず、従来どおりフラット URI で開きます。

##### 手元の context で直接開く(ネストのオプトアウト)

コンテナが ssh 先とも別のホスト(例 WSL2)にあり、**手元の VS Code 側にも同名の docker context がある**なら、ssh 先を経由するネスト URI ではなく、手元の Dev Containers 拡張にその context で直接 attach させられます。`DEVBASE_EDITOR_SSH_HOST` を**空文字で明示**すると自動検出を行わず、`settings.context` 付きのフラット URI で `code` を起動します:

```sh
# projects/<name>/env(その context を使うプロジェクトだけに効かせる)
DEVBASE_EDITOR_SSH_HOST=
```

前提は 2 つです。1 つは、`code` の実行先(手元の VS Code が docker CLI を呼ぶ側)で `docker --context <ctx> ps` がコンテナを返すこと。もう 1 つは、`settings.context` が **devbase の解決した context(`project.local.yml` / `--context` / `DEVBASE_DOCKER_CONTEXT`)か `DEVBASE_EDITOR_DOCKER_CONTEXT` の明示**から来ていることです。ssh 先の `docker context show` の推測だけに頼っている構成では、空文字にすると推測も行われず `settings.context` が付かない(手元の既定 daemon へ繋ぎに行く)ため、`devbase up` はこの案内を出しません。Windows の VS Code で `dev.containers.executeInWSL` が `true` なら WSL 内の CLI が使われるので、WSL 内に `docker context create <ctx> --docker host=unix:///var/run/docker.sock` で同名 context を作れば足ります。行を消せば自動検出(ネスト URI)に戻ります。

##### 制約

- `docker context use` で**現在の context 自体**をリモートへ向けた状態は、これまでどおりの
Expand Down
21 changes: 19 additions & 2 deletions lib/devbase/editor/opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,13 +669,22 @@ def _build_open_uri(ctx: EditorContext, env, container: str, workdir: str,

ネスト URI (ssh_host + docker_context) のときは、手元 VS Code に同名 context が
あれば ssh 先を経由せず直接 attach できるフラット URI を info ログで提示する。
あわせて、``DEVBASE_EDITOR_SSH_HOST=`` (空) で恒久的にフラット URI へ切り替えられる
ことも示す (案内だけでは毎回手で貼ることになる)。この案内は context が明示か devbase の
解決結果から来ているときだけ出す。``docker context show`` の推測だけが元のときは、
ssh_host を外すと推測も行われず ``settings.context`` が消えるため出さない。

戻り値は ``(uri, uri_flag)``。
"""
ssh_host = (resolve_editor_ssh_host(env, auto_detect=ctx.in_vscode)
if ctx.is_ssh else None)
if ssh_host or docker_context:
docker_context = resolve_docker_context(env, default=docker_context)
# 明示 (DEVBASE_EDITOR_DOCKER_CONTEXT) と devbase の解決結果は ssh の有無によらず
# 付ける。`docker context show` の推測だけは ssh 先のときに限る (ローカル端末で
# 毎回 docker を叩かない)。
explicit_context = env.get("DEVBASE_EDITOR_DOCKER_CONTEXT")
resolved_context = docker_context
if ssh_host or resolved_context or explicit_context is not None:
docker_context = resolve_docker_context(env, default=resolved_context)
else:
docker_context = None
# DEVBASE_WORKSPACE があれば *.code-workspace をワークスペースとして開く。VS Code は
Expand All @@ -694,6 +703,14 @@ def _build_open_uri(ctx: EditorContext, env, container: str, workdir: str,
"次で直接 attach できます:", docker_context)
logger.info(" %s %s '%s'",
" ".join(shlex.quote(c) for c in display), uri_flag, flat)
# 空文字の明示はネストのオプトアウト (resolve_editor_ssh_host)。毎回手で貼る
# 代わりに恒久化する方法を、その場で示す。ただし context が `docker context
# show` の推測だけから来ている場合は、ssh_host を外すと推測も行われず
# settings.context が消える (別の daemon へ繋ぎに行く) ため案内しない。
if resolved_context or explicit_context is not None:
logger.info(
" env に DEVBASE_EDITOR_SSH_HOST= (空) を書くと、次回からこのフラット URI で"
"直接開きます")
return uri, uri_flag


Expand Down
89 changes: 89 additions & 0 deletions tests/editor/test_opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,95 @@ def boom(*a, **kw):
text = "\n".join(r.getMessage() for r in caplog.records)
flat = uri.replace("@ssh-remote+mac2", "")
assert flat in text and "同名" in text
# 恒久化の方法 (空文字オプトアウト) もその場で示す。
assert "DEVBASE_EDITOR_SSH_HOST=" in text


def test_open_editor_remote_ssh_empty_ssh_host_launches_flat_uri_with_context(monkeypatch, caplog):
"""Remote-SSH + 解決した context でも DEVBASE_EDITOR_SSH_HOST= (空) ならフラット URI で launch。

手元の VS Code に同名 context がある構成 (Windows → WSL の docker を直接) で、案内の
フラット URI を毎回手で貼らずに済ませる経路。settings.context は残す。
"""
import logging
monkeypatch.setattr(opener.shutil, "which", lambda c: "/usr/bin/code")

def boom(*a, **kw):
raise AssertionError("docker context show should not run")

monkeypatch.setattr(opener.subprocess, "run", boom)
monkeypatch.setattr(opener, "_query_container_name", lambda *a, **kw: None)
calls = []
with caplog.at_level(logging.INFO):
opener.open_editor(
project_name="investment", dev_service_name="dev", workdir="/work/investment",
environ={"VSCODE_IPC_HOOK_CLI": "/run/x.sock",
"SSH_CONNECTION": "192.168.1.16 5 192.168.1.201 22",
"DEVBASE_EDITOR_SSH_HOST": ""},
isatty=True, ipc_alive=True, launcher=lambda cmd, env: calls.append(cmd),
docker_context="wsl",
)
uri = calls[0][2]
assert "@ssh-remote+" not in uri and uri.endswith("/work/investment")
assert _decode(uri)["settings"]["context"] == "wsl"
text = "\n".join(r.getMessage() for r in caplog.records)
assert "同名" not in text # ネストしていないので案内は出ない


def test_open_editor_remote_ssh_context_from_docker_show_omits_opt_out_hint(monkeypatch, caplog):
"""context が `docker context show` の推測だけのときは、空文字オプトアウトの案内を出さない。

ssh_host を外すと推測も行われず settings.context が消える (別の daemon へ繋ぐ) ため、
案内どおりにすると提示したフラット URI と違うものが開く。
"""
import logging
monkeypatch.setattr(opener.shutil, "which", lambda c: "/usr/bin/code")
monkeypatch.setattr(opener, "_query_container_name", lambda *a, **kw: None)
monkeypatch.setattr(opener, "resolve_docker_context",
lambda env, default=None, **kw: default or "probed")
calls = []
with caplog.at_level(logging.INFO):
opener.open_editor(
project_name="adminer", dev_service_name="dev", workdir="/work/adminer",
environ={"VSCODE_IPC_HOOK_CLI": "/run/x.sock",
"SSH_CONNECTION": "192.168.1.16 5 192.168.1.201 22",
"DEVBASE_EDITOR_SSH_HOST": "mac2"},
isatty=True, ipc_alive=True, launcher=lambda cmd, env: calls.append(cmd),
)
uri = calls[0][2]
assert "@ssh-remote+mac2" in uri and _decode(uri)["settings"]["context"] == "probed"
text = "\n".join(r.getMessage() for r in caplog.records)
assert "同名" in text and "DEVBASE_EDITOR_SSH_HOST=" not in text


def test_open_editor_remote_ssh_explicit_editor_context_survives_opt_out(monkeypatch, caplog):
"""DEVBASE_EDITOR_DOCKER_CONTEXT の明示は、空文字オプトアウト後も settings.context に残る。"""
import logging
monkeypatch.setattr(opener.shutil, "which", lambda c: "/usr/bin/code")
monkeypatch.setattr(opener, "_query_container_name", lambda *a, **kw: None)

def boom(*a, **kw):
raise AssertionError("docker context show should not run")

monkeypatch.setattr(opener.subprocess, "run", boom)
base = {"VSCODE_IPC_HOOK_CLI": "/run/x.sock",
"SSH_CONNECTION": "192.168.1.16 5 192.168.1.201 22",
"DEVBASE_EDITOR_DOCKER_CONTEXT": "wsl"}
calls = []
with caplog.at_level(logging.INFO):
opener.open_editor(
project_name="adminer", dev_service_name="dev", workdir="/work/adminer",
environ={**base, "DEVBASE_EDITOR_SSH_HOST": "mac2"},
isatty=True, ipc_alive=True, launcher=lambda cmd, env: calls.append(cmd),
)
assert "DEVBASE_EDITOR_SSH_HOST=" in "\n".join(r.getMessage() for r in caplog.records)
opener.open_editor(
project_name="adminer", dev_service_name="dev", workdir="/work/adminer",
environ={**base, "DEVBASE_EDITOR_SSH_HOST": ""},
isatty=True, ipc_alive=True, launcher=lambda cmd, env: calls.append(cmd),
)
flat = calls[1][2]
assert "@ssh-remote" not in flat and _decode(flat)["settings"]["context"] == "wsl"


def test_open_editor_explicit_editor_context_beats_resolved(monkeypatch):
Expand Down
Loading