diff --git a/docs/specifications/secret-backend.md b/docs/specifications/secret-backend.md index 522226cd..43fbc894 100644 --- a/docs/specifications/secret-backend.md +++ b/docs/specifications/secret-backend.md @@ -814,7 +814,7 @@ TUI の「OpenBao の接続設定」は同じプロセスの中から `use openb | 欄 | 接続先(`url`、1 行)、`role_id` と `secret_id`(伏せ字)。空の欄は変えない(`use` の引き継ぎ)。`role_id` を変えるときは `secret_id` も要る。`mount` / `user` / `layout` / `group_aliases` / キャッシュの設定は変えない | | 保存の後 | `test` と同じ確認を自動で行う。失敗しても保存した設定は残し、欄を入れ直せる。`use` が失敗したら `test` を呼ばずに欄へ戻る | | 確認のグループ | グループ別の置き場(`version: 2`・`layout: group`)で、TUI の現在地(`PWD`)がプロジェクトの外のときだけ、保存の後・確認の直前に対象のグループを選ばせ、`test` へ属性 `group`(CLI の `--group` と同じ)で渡す。選択は sync / init と同じ部品(`actions_env._group_attrs`。候補は宣言済みのグループと「名前を入力」)。プロジェクトの中では選ばせず、`test` がグループの宣言から決める。`version: 1` では選ばせず `group` を渡さない | -| 使えないグループ名 | 「名前を入力」の名前は `validate_account_group` で検証し、通らなければ「--group に使えない名前です: ...」を出してグループの選択へ戻る(`test` を呼ばない)。この規則を通る予約語の `global` / `projects` は選択では拒まず、`test` が `storage_group` の検証で 2 を返し、確認の失敗(警告と入れ直しの問い)になる | +| 使えないグループ名 | 「名前を入力」の名前は `validate_account_group` で検証し、続けて CLI の `--group` と同じ `storage_group` の検査(置き場の予約語 `global` / `projects`、`group_aliases` の読み替え先)を通す。どちらかで落ちれば「--group に使えない名前です: ...」を出してグループの選択へ戻る(`test` を呼ばない)。sync / init の選択も同じ部品を通る | | グループの選択で戻る | Esc・← で「接続を確かめずに戻ります。保存した設定は残っています。」を出し、`test` を呼ばず、入れ直しの問いも出さずに env メニューへ戻る(`ARG_CANCEL`。一時停止しない)。Ctrl-C は TUI 全体を終える | | 入れ直した後 | 入れ直しの問いで「はい」を選ぶと欄へ戻り、次に保存した後でもう一度グループを選ぶ | | token | 入力させず、保存しない(実行のたびにブートストラップ機密で取り直す) | @@ -1566,8 +1566,8 @@ groups: (`--group` を付けて CLI から打ったときと同じ参照) - 同じ条件で認証に失敗すると、グループを選んだ後に警告と入れ直しの問いが出て、保存した `secret_id` が残ること - グループの選択で戻ると `test` を呼ばずに `ARG_CANCEL` で戻り、保存した設定が残って入れ直しの問いが出ないこと - - 「名前を入力」に `validate_account_group` で落ちる名前を入れると「--group に使えない名前です」が出て - グループの選択へ戻ること + - 「名前を入力」に `validate_account_group` で落ちる名前(`default` など)や置き場の予約語 `global` / + `projects` を入れると「--group に使えない名前です」が出て、`test` を呼ばずにグループの選択へ戻ること - プロジェクトの中ではグループの選択が出ず、宣言のグループの置き場を読んで成功すること - `version: 1` ではグループの選択が出ず、`test` が `group` なしで呼ばれること - `version: 2` の端末で `DEVBASE_ROOT` から TUI の接続設定を保存し、グループを選ぶと「読めた参照」が出て diff --git a/lib/devbase/tui/actions_env.py b/lib/devbase/tui/actions_env.py index e8795b7a..92c9639d 100644 --- a/lib/devbase/tui/actions_env.py +++ b/lib/devbase/tui/actions_env.py @@ -110,14 +110,18 @@ def _group_attrs(devbase_root: Path) -> dict: それ以外の設定では ``{}`` (グループの選択を出さずに今どおり実行する)。設定が読めない ときも ``{}`` で委譲し、委譲先のコマンドが誤りを出す。使えない名前を入れたら、 - ``--group`` と同じ検証の文を出して選択へ戻る。 + ``--group`` と同じ検証の文を出して選択へ戻る。名前の規則に加えて、``--group`` と同じく + 置き場のグループ名の検査 (``storage_group``: 予約語 ``global`` / ``projects`` と + ``group_aliases`` の読み替え先) も通し、通らない名前は委譲先へ渡さない。 """ + from devbase.env.secret_store import SecretStore from devbase.errors import DevbaseError from devbase.volume.manager import validate_account_group keys_screen = _screen("actions_env_keys") try: grouped = keys_screen._grouped(devbase_root) + settings = SecretStore(devbase_root).config.openbao if grouped else None except DevbaseError: return {} if not grouped: @@ -125,7 +129,9 @@ def _group_attrs(devbase_root: Path) -> dict: while True: name = keys_screen._select_group(devbase_root) try: - return {"group": validate_account_group(name)} + name = validate_account_group(name) + settings.storage_group(name) + return {"group": name} except DevbaseError as e: logger.error("--group に使えない名前です: %s", e) diff --git a/tests/cli/tui/test_actions_env.py b/tests/cli/tui/test_actions_env.py index 76a19e3e..33de2ae1 100644 --- a/tests/cli/tui/test_actions_env.py +++ b/tests/cli/tui/test_actions_env.py @@ -300,3 +300,23 @@ def test_grouped_sync_reasks_for_an_unusable_typed_name(monkeypatch, tmp_path, c assert captured['attrs'] == {'subcommand': 'sync', 'group': 'personal'} assert 'default' in caplog.text + + +@pytest.mark.parametrize('op', ['sync', 'init']) +@pytest.mark.parametrize('reserved', ['global', 'projects']) +def test_grouped_sync_and_init_reask_for_a_reserved_storage_group(monkeypatch, tmp_path, caplog, + op, reserved): + """#397: 置き場の予約語は --group と同じ検証の文を出して選択へ戻り、委譲先へ渡さない""" + from devbase.tui import actions_env_keys + + _grouped_root(tmp_path) + captured = _capture_dispatch(monkeypatch) + picks = iter([actions_env_keys.TYPE_GROUP, 'acme']) + monkeypatch.setattr(menu, 'select', lambda *a, **k: next(picks)) + monkeypatch.setattr(menu, 'text', lambda *a, **k: reserved) + + assert actions_env._run_operation(tmp_path, op) == 0 + + assert captured['attrs']['group'] == 'acme' + assert '--group に使えない名前です' in caplog.text + assert f"'{reserved}' は置き場のグループ名に使えません" in caplog.text diff --git a/tests/cli/tui/test_actions_env_openbao.py b/tests/cli/tui/test_actions_env_openbao.py index b217fecb..49187468 100644 --- a/tests/cli/tui/test_actions_env_openbao.py +++ b/tests/cli/tui/test_actions_env_openbao.py @@ -281,3 +281,30 @@ def test_a_flat_layout_checks_without_a_group(openbao_root, openbao, monkeypatch assert rc == 0 assert seen == [None] assert not script.messages('select') + + +@pytest.mark.parametrize('reserved', ['global', 'projects']) +def test_a_reserved_storage_group_returns_to_the_selection(grouped, openbao, monkeypatch, caplog, + reserved): + """#397: 置き場の予約語は test へ渡さず、--group と同じ検証の文を出して選択へ戻る""" + from devbase.tui.actions_env_keys import TYPE_GROUP + + groups = [] + real_test = env_backend.cmd_env_backend_test + + def spy(root, *, group=None): + groups.append(group) + return real_test(root, group=group) + + monkeypatch.setattr(env_backend, 'cmd_env_backend_test', spy) + script = Script(monkeypatch, text=[local_url(openbao), reserved], secret=['', ''], + select=[TYPE_GROUP, 'acme']) + + rc, out = run(grouped) + + assert rc == 0 + assert '--group に使えない名前です' in logs(caplog) + assert f"'{reserved}' は置き場のグループ名に使えません" in logs(caplog) + assert len(script.messages('select')) == 2 + assert reserved not in groups + assert '読めた参照' in out