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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
- **非推奨の `container` / `ct` グループは名前解決の対象外になりました(PLAN61 / #200)。**
`devbase container up <name>` は実在するプロジェクト名でも usage エラー(終了コード 2)です。
名前の指定は `devbase project up <name>` か `devbase up <name>` を使ってください。
- **機密の置き場(`.env` / `age` / OpenBao)に書いた `DEVBASE_ACCOUNT_GROUP` を、`version: 1` でも
使わなくなりました(PLAN62 / #185)。** 置き場の値は機密の合成から外れ、devbase のプロセスの
環境変数にも dev コンテナの `environment` にも載りません。アカウントグループを決めるのは
`env` ファイル(`projects/<name>/env` / `$DEVBASE_ROOT/env`)とシェルの環境変数だけになります。
置き場に残っていれば、コマンドのたびに置き場ごとに 1 回、消し方(`devbase env delete
DEVBASE_ACCOUNT_GROUP` と付ける引数)を添えた警告が出ます(値は出しません)。

> **Note:** これまで `version: 1` で置き場の値でグループを切り替えていた端末では、ボリューム
> のグループが `env` ファイルの宣言(無ければ `default`)へ戻ります。同じグループで使い
> 続けるには `projects/<name>/env` に `DEVBASE_ACCOUNT_GROUP=<group>` を書いてください。
- `devbase env set DEVBASE_ACCOUNT_GROUP=...`(`-p` / `--user` / `--group` 付きも同じ)は、置き場へ
書かずに `env` ファイルへ書くよう案内して終了コード 1 で止まります。`env import` / `env edit` と
他のキーの `env set` は変わりません。

### Fixed
- **`devbase build --help` / `-h` がビルドを始めてしまう**のを直しました(PLAN61 / #196)。
Expand Down
3 changes: 3 additions & 0 deletions docs/user/cli-reference/03-env.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ devbase env set KEY=VALUE [-p] [--user] [--group NAME]
| `--user` | 個人単位の置き場に設定(サーバ backend のみ。[機密の保存先を選ぶ](../env-backend.md)) |
| `--group NAME` | 対象のグループの置き場に設定(グループ別の置き場のみ。`-p` と組み合わせるときはプロジェクトのグループと同じ置き場に限る) |

`DEVBASE_ACCOUNT_GROUP` は書けません(どのオプションでも終了コード 1)。アカウントグループは
`projects/<name>/env` か `$DEVBASE_ROOT/env` に書きます([環境変数ガイド](../environment-variables.md#機密の置き場には書けない))。

```bash
# グローバルに設定
devbase env set ANTHROPIC_API_KEY=sk-xxx
Expand Down
14 changes: 8 additions & 6 deletions docs/user/env-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,11 @@ OpenBao の KV v2 にはコメント・空行の置き場がありません。`d
なります。プロジェクトの外(`$DEVBASE_ROOT` など)では 2 → 3 で決まります。

**機密の置き場に書いた `DEVBASE_ACCOUNT_GROUP` はグループの決定に使われません**(置き場を
決める値をその置き場から読むことになるため)。置き場には書かないでください。書くと
コンテナを起動するプロセスの環境変数にだけ載り、ボリュームのグループが変わって
[`up` / `scale` が止まる](#up--scale-がグループの食い違いで止まったとき)ことがあります。
決める値をその置き場から読むことになるため)。置き場の値は機密の合成から外れ、コンテナを
起動するプロセスの環境変数にもコンテナにも載りません(`version: 1` でも同じ)。置き場に
残っていればコマンドのたびに警告が 1 回出るので、そこに書かれた
`devbase env delete DEVBASE_ACCOUNT_GROUP ...` で消してください。`devbase env set` は
このキーを置き場へ書かずに終了コード 1 で止まります([環境変数ガイド](environment-variables.md#機密の置き場には書けない))。

`default` をボリューム名(`devbase_home_default`)はそのままに、置き場の上だけ別の名前で
扱うには `group_aliases` を使います。`default: nyle` なら、グループを宣言していない
Expand Down Expand Up @@ -407,9 +409,9 @@ Error: ボリュームと機密のアカウントグループが食い違うた
| 環境変数で指定したグループ(例: `kkg`) | `projects/<name>/env` に `DEVBASE_ACCOUNT_GROUP=kkg` を書く。プロジェクトの `env` はラッパーが読み込むため、両方が揃う |
| `env` ファイルで決まるグループ | シェルの環境変数を外す(`unset DEVBASE_ACCOUNT_GROUP`。シェルの設定ファイルで `export` していればその行も消す) |

どちらでもなく、機密の置き場に `DEVBASE_ACCOUNT_GROUP` を書いていたときは、その行を
`devbase env delete DEVBASE_ACCOUNT_GROUP`(書いた置き場に合わせて `-p` / `--user` / `--group`)で
消してください。
機密の置き場に書いた `DEVBASE_ACCOUNT_GROUP` はこの食い違いを起こしません(置き場の値は
プロセスの環境変数へ載らないため)。置き場に残っていると別の警告が出るので、そこに書かれた
`devbase env delete DEVBASE_ACCOUNT_GROUP ...` で消してください。

`version: 1` の設定では、この検査は行いません。

Expand Down
17 changes: 16 additions & 1 deletion docs/user/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,21 @@ DEVBASE_ACCOUNT_GROUP=kkg
[コンテナ運用ガイド](container-operations.md)、Google 認証の手順は
[Google 認証ガイド](google-auth.md) を参照してください。

### 機密の置き場には書けない

`DEVBASE_ACCOUNT_GROUP` を決めるのは `env` ファイル(`projects/<name>/env` /
`$DEVBASE_ROOT/env`)とシェルの環境変数だけです。機密の置き場(`.env` / `age` / OpenBao の
どれでも、`version: 1` でも)に書いた値は使われず、コンテナへも渡りません。置き場に残って
いれば、devbase のコマンドを打つたびに次の警告が 1 回出ます(値は出しません)。

```text
Warning: 機密の置き場(グローバル)にある DEVBASE_ACCOUNT_GROUP は使いません。アカウントグループは env ファイル(projects/<name>/env・$DEVBASE_ROOT/env)で決まります。消すには: devbase env delete DEVBASE_ACCOUNT_GROUP
```

警告に書かれたとおり `devbase env delete DEVBASE_ACCOUNT_GROUP`(置き場に合わせて `-p` /
`--user` / `--group` が付きます)で消してください。`devbase env set DEVBASE_ACCOUNT_GROUP=...` は
置き場へ書かずに終了コード 1 で止まります。`env` ファイルに書いてください。

### 機密の置き場もグループで分ける(OpenBao)

機密の保存先に OpenBao を使い、グループ別の置き場(`secrets/backend.yml` の `version: 2`)を
Expand All @@ -274,7 +289,7 @@ DEVBASE_ACCOUNT_GROUP=kkg
2. `$DEVBASE_ROOT/env`
3. どちらにも無ければ `default`

**機密の置き場(`devbase env set` で入れた値)に書いた `DEVBASE_ACCOUNT_GROUP` は使われません。**
**機密の置き場に書いた `DEVBASE_ACCOUNT_GROUP` は使われません**([機密の置き場には書けない](#機密の置き場には書けない))。
グループはここに挙げたファイルに書いてください。シェルの環境変数で渡したグループとファイルで
決まるグループが食い違うと、`devbase up` / `scale` は起動せずに止まります(直し方は
[`up` / `scale` がグループの食い違いで止まったとき](env-backend.md#up--scale-がグループの食い違いで止まったとき))。
Expand Down
95 changes: 86 additions & 9 deletions issues/PLAN62_inject-account-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,34 @@

## 受け入れ条件

- [ ] 1. 前提: チーム共通の機密(`version: 1`・`plaintext` / `age` backend)に `DEVBASE_ACCOUNT_GROUP=kkg` があり、
- [x] 1. 前提: チーム共通の機密(`version: 1`・`plaintext` / `age` backend)に `DEVBASE_ACCOUNT_GROUP=kkg` があり、
プロセスの環境変数に `DEVBASE_ACCOUNT_GROUP` が無い
操作: `runtime.inject(root, project)` を呼ぶ
結果: プロセスの環境変数に `DEVBASE_ACCOUNT_GROUP` が載らず、`resolve_account_group()` は `default` を返す
- [ ] 2. 前提: 1 と同じ置き場で、プロセスの環境変数に `DEVBASE_ACCOUNT_GROUP=with` がある(`env` ファイル由来)
検証: `tests/env/test_runtime.py::test_inject_does_not_put_the_stores_account_group_into_the_environment[age|plaintext]`
- [x] 2. 前提: 1 と同じ置き場で、プロセスの環境変数に `DEVBASE_ACCOUNT_GROUP=with` がある(`env` ファイル由来)
操作: `runtime.inject(root, project)` を呼ぶ
結果: プロセスの環境変数は `with` のまま変わらない
- [ ] 3. 個人共通・プロジェクトのチーム・プロジェクトの個人の置き場にあっても、1・2 と同じになる
- [ ] 4. 置き場にあったときは、キー名と置き場の種類・消し方を含む警告が出て、値は出ない。
検証: `tests/env/test_runtime.py::test_inject_keeps_the_environments_account_group[age|plaintext]`、`::test_inject_keeps_the_projects_env_declaration`
- [x] 3. 個人共通・プロジェクトのチーム・プロジェクトの個人の置き場にあっても、1・2 と同じになる
検証: `tests/env/test_runtime.py::test_every_store_layer_is_dropped_from_the_environment[4 層]`、`::test_every_store_layer_loses_to_the_environment[4 層]`
- [x] 4. 置き場にあったときは、キー名と置き場の種類・消し方を含む警告が出て、値は出ない。
1 回の CLI 起動で同じ置き場について 2 回以上出ない
- [ ] 5. `resolve()` の結果(`SecretEnv.names` / `values`)に `DEVBASE_ACCOUNT_GROUP` が含まれない。
検証: `tests/env/test_runtime.py::test_warns_once_with_the_store_and_how_to_delete[4 種]`、`::test_warning_for_a_grouped_reference_names_the_group`、`::test_warning_for_a_grouped_global_reference_still_adds_the_group`、`::test_warning_is_not_repeated_across_stores_and_release`、`::test_each_reference_warns_separately`、`::test_empty_value_still_warns_and_no_key_does_not`
- [x] 5. `resolve()` の結果(`SecretEnv.names` / `values`)に `DEVBASE_ACCOUNT_GROUP` が含まれない。
したがって dev コンテナの `environment` へ置き場の値は列挙されない
- [ ] 6. `layout: group` で、置き場(`$DEVBASE_ROOT/env` にも `projects/<name>/env` にもグループの宣言が無い
検証: `tests/env/test_runtime.py::test_resolve_never_lists_the_account_group`、`::test_child_env_does_not_carry_the_stores_account_group`
- [x] 6. `layout: group` で、置き場(`$DEVBASE_ROOT/env` にも `projects/<name>/env` にもグループの宣言が無い
プロジェクトの、共通の機密)に `DEVBASE_ACCOUNT_GROUP=kkg` があっても、`devbase up` の前の
グループの食い違いの検査で止まらない
- [ ] 7. `devbase env set DEVBASE_ACCOUNT_GROUP=kkg`(`-p` / `--user` / `--group` 付きも同じ)は、置き場へ書かずに
検証: `tests/commands/test_container_up_order.py::test_store_account_group_does_not_stop_the_group_check`
- [x] 7. `devbase env set DEVBASE_ACCOUNT_GROUP=kkg`(`-p` / `--user` / `--group` 付きも同じ)は、置き場へ書かずに
`env` ファイルへ書くよう案内して終了コード 1
- [ ] 8. 他のキーの注入(重ね順・注入の履歴・`clear_injected` による巻き戻し)の既存テストが変更なしで通る
- [ ] 9. 全体テスト(`uv run pytest tests/`)が通る
検証: `tests/commands/test_env_account_group.py::test_set_refuses_the_account_group_without_opening_the_store[6 組]`、`::test_set_refuses_the_account_group_without_creating_the_file[global|project]`、`::test_set_refuses_the_account_group_with_surrounding_spaces`、`::test_set_of_another_key_still_writes`
- [x] 8. 他のキーの注入(重ね順・注入の履歴・`clear_injected` による巻き戻し)の既存テストが変更なしで通る
検証: `tests/env/test_runtime.py`(既存 38 件は追記のみで本文の変更なし)・`tests/env/test_runtime_store.py`・`tests/cli/test_secret_injection.py`(変更なし)が全体テストで通過
- [x] 9. 全体テスト(`uv run pytest tests/`)が通る
検証: `uv run --locked pytest -q tests/` → 2717 passed(exit 0)、`ruff check --select=E9,F63,F7,F82 lib` → All checks passed(exit 0)

## 影響

Expand Down Expand Up @@ -107,3 +116,71 @@
| 常に行う | 全体テスト |
| 確認してから行う | 前提 1(`version: 1` の挙動を変えること。設計 Pull Request の承認で確かめる) |
| 行わない | 置き場の値の自動削除、他のキーの扱いの変更 |

## 実装計画

設計は [PLAN62_inject-account-group-design.md](PLAN62_inject-account-group-design.md)。タスクはその「構成要素」と「テスト設計」から導く。どのタスクも失敗するテスト → 通す最小実装 → 整理の順で進める。

### 修正対象

- `lib/devbase/env/runtime.py`(`_warned_account_group_refs` / `_without_account_group` の新設、`resolve` の変更)
- `lib/devbase/commands/env.py`(`cmd_env_set` の拒否)
- `lib/devbase/env/keys.py`(コメント)
- `tests/env/test_runtime.py`(受け入れ条件 1〜5)
- `tests/commands/test_container_up_order.py`(受け入れ条件 6)
- `tests/commands/test_env_account_group.py`(新設。受け入れ条件 7)
- `docs/user/environment-variables.md` / `docs/user/env-backend.md` / `docs/user/cli-reference/03-env.md` / `CHANGELOG.md`

### Task 1: [x] `resolve` が 4 つの置き場の `DEVBASE_ACCOUNT_GROUP` を合成しない

- **対象ファイル:** `lib/devbase/env/runtime.py`、`tests/env/test_runtime.py`
- **変更内容:** `_without_account_group(ref, data)` を新設し、`resolve` の 4 つの `store.load` の直後に通す(決定 1・6。重ね順 3 は変えない)。docstring に理由を書き足す
- **満たす受け入れ条件:** 1、2、3、5
- **進め方:** age / plaintext の `store` と `_FourLayerStore`(4 置き場を parametrize)で、`inject` 後の `os.environ` と `resolve_account_group()`、`resolve` の `names` / `global_names` / `project_names` / `values` を確かめるテストを先に書く → `resolve` を変えて通す

### Task 2: [x] 外したことを置き場ごとに 1 回だけ警告する

- **対象ファイル:** `lib/devbase/env/runtime.py`、`tests/env/test_runtime.py`
- **変更内容:** モジュール変数 `_warned_account_group_refs`(`SecretRef` の集合)を新設し、`_without_account_group` がキーを見つけた参照について 1 回だけ `logger.warning` を出す。文言は設計「警告」の表どおり(`label()`、`-p` / `--user` / `--group` の引数、プロジェクトの参照なら実行場所)。値は出さない
- **満たす受け入れ条件:** 4
- **進め方:** caplog で 4 種の参照とグループ付きの参照の文言、`release_store` と別 `store` をまたいだ重複抑止、値が出ないことを確かめるテストを先に書く → 集合と警告を足して通す

### Task 3: [x] `layout: group` で置き場の値があっても `up` の検査が止まらない

- **対象ファイル:** `tests/commands/test_container_up_order.py`
- **変更内容:** 偽の OpenBao(`layout: group`、`default → nyle`)の `team/nyle/global` に `DEVBASE_ACCOUNT_GROUP=kkg` を置き、`runtime.inject(root, 'web')` の後に `container._check_group_consistency()` が True になることを固定する(決定 8)。実装の変更は Task 1 に含まれる
- **満たす受け入れ条件:** 6
- **進め方:** Task 1 の前に書けば失敗し、Task 1 で通る。ここでは順序どおりに呼ぶテストを足して通ることを確かめる(Task 1 の実装で満たされるため追加の実装は無い)

### Task 4: [x] `env set DEVBASE_ACCOUNT_GROUP=...` を置き場を開く前に拒否する

- **対象ファイル:** `lib/devbase/commands/env.py`、`tests/commands/test_env_account_group.py`(新設)
- **変更内容:** `cmd_env_set` でキー名が `keys.DEVBASE_ACCOUNT_GROUP` なら、`_open_target_env` より前に error ログを出して 1 を返す(決定 7)。文言は設計「コマンド `env set`」の表どおり
- **満たす受け入れ条件:** 7
- **進め方:** 偽の OpenBao で `project` / `user` / `group` の各組み合わせが 1 で POST が無いこと、ファイル backend で `.env` が作られないこと、error ログに `env` ファイルの案内があることを確かめるテストを先に書く → 早期 return を足して通す

### Task 5: [x] 文書・コメント・CHANGELOG を新しい挙動に合わせる

- **対象ファイル:** `lib/devbase/env/keys.py`、`docs/user/environment-variables.md`、`docs/user/env-backend.md`、`docs/user/cli-reference/03-env.md`、`CHANGELOG.md`
- **変更内容:** 設計「構成要素」の表のとおり。置き場の値は `version: 1` でも使われず警告が出ること、`env set` で書けないこと、`up` / `scale` の食い違いの直し方から「置き場に書いていたとき」の段落を書き替える
- **満たす受け入れ条件:** (文書。受け入れ条件には無いが要求の対象範囲「含む」)
- **進め方:** 文書のためテスト駆動を適用しない

### Task 6: [x] 全体テストと静的解析

- **対象ファイル:** なし
- **変更内容:** `uv run --locked pytest -q tests/` と `ruff check --select=E9,F63,F7,F82 lib` を実行し、終了コードを記録する
- **満たす受け入れ条件:** 8、9
- **進め方:** 既存テスト(`tests/env/test_runtime.py`・`tests/env/test_runtime_store.py`・`tests/cli/test_secret_injection.py`)を変更していないことを `git diff --stat` で確かめる

### リスクと対処

| リスク | 対処 |
| --- | --- |
| `resolve` は `inject` / `child_env` / コンテナの列挙の共通経路で、外し方を誤ると他のキーの重ね順が変わる | 読み取りの直後にキーを除くだけにし、重ね順の既存テスト(Task 6)を変更なしで通す |
| 警告の集合がテスト間で漏れ、重複抑止のテストが順序に依存する | 各テストの先頭で `monkeypatch.setattr(runtime, '_warned_account_group_refs', set())` |
| テストが実環境の `DEVBASE_ROOT`(シェルが持つ)を継承し、実の secrets / OpenBao に触る | 新しいテストは必ず `DEVBASE_ROOT` を tmp へ向け、偽の OpenBao / `_FourLayerStore` を使う |

### 切り戻し手順

- `runtime.py` と `commands/env.py` の変更を戻せば元の挙動に戻る。置き場の値は消していないため、データの巻き戻しは無い
Loading
Loading