From 658e2a7c6731bcd266cbb9c16f764fb2d09a5cc4 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 11:14:47 +0900 Subject: [PATCH 01/22] =?UTF-8?q?docs(PLAN58):=20Compose=20=E3=81=AE=20pro?= =?UTF-8?q?files=20=E3=81=A7=E4=BB=98=E9=9A=8F=E3=82=B5=E3=83=BC=E3=83=93?= =?UTF-8?q?=E3=82=B9=E7=BE=A4=E3=82=92=E5=BE=8C=E3=81=8B=E3=82=89=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E3=83=BB=E5=81=9C=E6=AD=A2=E3=81=99=E3=82=8B=E8=A6=81?= =?UTF-8?q?=E6=B1=82=E4=BB=95=E6=A7=98=E3=81=A8=E8=A8=AD=E8=A8=88=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev のほかに app / db などを持つプロジェクトで、devbase up の既定では dev だけを 起動し、付随するサービス群を後から起動・停止できるようにするための要求仕様と設計。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 257 +++++++++++++++++++++++ issues/PLAN58_compose-profiles.md | 130 ++++++++++++ 2 files changed, 387 insertions(+) create mode 100644 issues/PLAN58_compose-profiles-design.md create mode 100644 issues/PLAN58_compose-profiles.md diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md new file mode 100644 index 00000000..35ad6464 --- /dev/null +++ b/issues/PLAN58_compose-profiles-design.md @@ -0,0 +1,257 @@ +# 189: Compose の profiles で付随サービス群を後から起動・停止する + +要求と受け入れ条件は [PLAN58_compose-profiles.md](PLAN58_compose-profiles.md) にある。この文書は「どう作るか」だけを扱う。 + +## 機能一覧 + +| # | 機能 | 誰が使うか | +| --- | --- | --- | +| F1 | プロファイルのサービスを起動する | プロジェクトの利用者 | +| F2 | プロファイルのサービスを停止して削除する | プロジェクトの利用者 | +| F3 | プロファイルの一覧と稼働状況を見る | プロジェクトの利用者 | +| F4 | 停止(`down` と `up` 冒頭)を全プロファイルへ効かせる | プロジェクトの利用者 | +| F5 | 有効なプロファイルをフックへ伝える | プロジェクトの作者 | + +## 構成要素 + +| 要素 | 責務 | +| --- | --- | +| プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | +| プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。停止には全プロファイルを指定する | +| フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ | +| 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | + +```mermaid +graph TD + subgraph 入口 + CLI[引数の受け口] + end + subgraph 操作 + OP[プロファイルの操作] + RS[プロファイルの解決] + HK[フックの実行] + end + subgraph 実行 + DC[Compose の呼び出し] + end + CLI --> OP + OP --> RS + OP --> DC + OP --> HK + HK --> DC +``` + +## システム構成 + +### 文脈 + +```mermaid +graph LR + 利用者 --> devbase[devbase の配布物] + devbase --> compose[Docker Compose] + compose --> daemon[Docker デーモン] + devbase --> hook[プロジェクトのフック] + devbase --> store[機密の置き場] +``` + +Docker Compose と Docker デーモンは、こちらが変えられない外部の系である。プロジェクトのフックは各プロジェクトが持つ。devbase が定めるのは呼び出しの約束(環境変数)だけである。 + +### 配置 + +```mermaid +graph TD + subgraph 利用者の端末 + W[wrapper: bin/devbase] + P[Python: lib/devbase] + F[生成物: .docker-compose.scale.yml] + end + subgraph Docker の実行基盤 + D1[dev-1..N] + D2[プロファイルのサービス] + end + W -->|引数と env| P + P -->|読む| F + P -->|コマンド列 + 機密の環境変数| D2 + P -.->|触らない| D1 +``` + +境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名を明示して渡す。そのため dev-1..N は再作成の対象に入らない。 + +## 置き場所 + +```text +lib/devbase/ +├── cli.py # profile サブコマンドの登録(変更) +├── commands/ +│ └── container.py # cmd_profile_up / down / list、dispatch(変更) +├── project/ +│ └── runtime.py # hook_env に有効なプロファイルを足す(変更) +├── utils/ +│ └── docker.py # docker_compose_down を全プロファイル対応へ(変更) +└── volume/ + └── compose.py # profiles を保つことの確認のみ(変更なし) + +tests/ +├── commands/ +│ └── test_container_profile.py # 新設 +└── volume/ + └── test_compose_profiles.py # 新設 +``` + +## 構造 + +型は追加しない。変わるのは処理の順序と、関数の責務の境目である。 + +| 関数 | 変更 | 責務 | +| --- | --- | --- | +| `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | +| `cmd_profile_up(profile, context)` | 新設 | F1。終了コードを返す | +| `cmd_profile_down(profile, context)` | 新設 | F2。終了コードを返す | +| `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | +| `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ | +| `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す | +| `_run_deploy_script_for_instances(...) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | +| `_dispatch_lifecycle` | 変更 | `profile` のサブコマンドを handlers へ足す | + +## 入出力の契約 + +仕様記述の置き場所は設けない。OpenAPI の対象になる経路が無く、変わる約束はコマンドだけである。 + +### 変わる約束 + +| 名前 | 入力 | 出力(成功) | 失敗の形 | +| --- | --- | --- | --- | +| `devbase project profile up [name] ` | プロファイル名(必須)、プロジェクト名(省略時は現在地) | 対象サービスを起動し、フックを実行して 0 | 構成ファイルが無い / 未知のプロファイル / Compose かフックが失敗 → 1 | +| `devbase project profile down [name] ` | 同上 | 対象サービスのコンテナを削除して 0 | 同上(フックは呼ばない) | +| `devbase project profile list [name]` | プロジェクト名(省略可) | プロファイルと稼働状況を表で出して 0 | 構成ファイルが無い → 1 | +| `devbase container profile ...` / `devbase ct profile ...` | 同上 | 同上。非推奨の警告を 1 行出す | 同上 | + +`[name]` と `` の並びは既存の `scale` と同じ規則に従う。値が 1 個ならプロファイル名に割り当てられる。2 個なら(プロジェクト名、プロファイル名)になる。 + +### 互換性の扱い + +| 変更 | 既存の呼び出し側への影響 | +| --- | --- | +| `profile` サブコマンドの追加 | 無い。既存の引数の形を変えない | +| `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じで、観測できる違いを生まない | +| `DEVBASE_ACTIVE_PROFILES` の追加 | 無い。既存のフックは読まなければ従来どおり動く | + +`bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` は変えない。この一覧は「3 番目の引数をプロジェクト名として解決してよいサブコマンド」を表す。`profile` ではその位置に `up` / `down` / `list` が来る。一覧へ足すと、`up` という名前のプロジェクトが実在したときにそちらへ移動してしまう。プロジェクト名の解決は Python 側の `_dispatch_lifecycle` に任せる。 + +### 検査の手段 + +`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。 + +## 処理の流れ + +```mermaid +sequenceDiagram + participant U as 利用者 + participant CLI as 引数の受け口 + participant OP as プロファイルの操作 + participant RS as プロファイルの解決 + participant DC as Compose の呼び出し + participant HK as フックの実行 + U->>CLI: devbase project profile up local-app + CLI->>OP: cmd_profile_up("local-app") + OP->>OP: 接続先を反映し機密を注入する + OP->>RS: 生成済みの構成を読む + alt 構成ファイルが無い + RS-->>OP: 無し + OP-->>U: devbase up を促して 1 + else 未知のプロファイル + RS-->>OP: 該当なし + OP-->>U: 既知の名前を並べて 1 + else + RS-->>OP: サービス名の集合 + OP->>DC: compose --profile local-app up -d <サービス...> + DC-->>OP: 終了コード + OP->>HK: ./deploy(DEVBASE_ACTIVE_PROFILES=local-app) + HK-->>OP: 全インスタンスの成否 + OP-->>U: 0 または 1 + end +``` + +停止(F2)は同じ並びから、フックの呼び出しを除いたものである。渡すのは `up -d` ではなく `down <サービス...>` である。 + +`up` と `down` の冒頭の停止(F4)は次のように変わる。 + +```mermaid +graph LR + A[devbase down] --> B[compose --profile '*' down -t0] + C[devbase up] --> D[compose --profile '*' down -t0] + D --> E[compose up -d] + E --> F[既定のサービスだけが動く] +``` + +## 非機能の実現方式 + +| 大項目 | 要求の条件 | 実現方式 | 確かめ方 | +| --- | --- | --- | --- | +| 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | +| セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | +| システム環境 | Docker Compose v2 系および v5 系で動く。`--profile '*'` と `depends_on.required` を使う | `--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる(機能は落ちるが壊れない) | v5.1.4 で全サービスが消えることを手動で確かめる。古い版は「未確認のまま残ること」に載せる | + +`depends_on.required: false` はプロジェクト側の書き方であり、devbase の実装には現れない。文書で案内する。 + +## 決定の記録 + +### 決定 1: プロファイルの解決は生成済みの `.docker-compose.scale.yml` を読んで行う + +`devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 + +`docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 + +### 決定 2: プロファイルの操作はサービス名を明示して渡す + +サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示し、dev を照合から外す。 + +### 決定 3: プロファイル起動の後は `./deploy` を呼び直す。新しいフックは作らない + +`./deploy` は既にインスタンスごとに呼ばれ、プロジェクト側で冪等に書かれている。プロファイルの有無は `DEVBASE_ACTIVE_PROFILES` で分岐できる。フック名を増やす理由がない。 + +`./post-profile-up` のような新しい名前を足す案は採らない。プロジェクトが持つ約束の数が増える。どちらに書くべきかの判断も各プロジェクトに生まれる。 + +### 決定 4: 停止は全プロファイルを対象にし、`up` は既定の状態へ揃える + +`devbase up` は「その構成で開発環境を作り直す」操作である。プロファイルのサービスだけが前の状態のまま残ると、`up` の後の状態が直前の操作に依存する。 + +`up` の冒頭の停止を dev-1..N だけに絞る案は採らない(利用者の指示、2026-09-16)。テストを続けたい場合は `up` の後にもう一度プロファイルを起動する。 + +### 決定 5: プロファイルの停止に `-t0` を使わない + +プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行う。こちらは既定の猶予(10 秒)で落とす。 + +### 決定 6: プロジェクト名は位置引数で受け、`bin/devbase` は変えない + +既存の `scale` と同じ並び(`[name] <値>`)に揃える。`bin/devbase` の名前解決は 3 番目の引数だけを見る。`profile` をその一覧へ足すと、`up` / `down` / `list` がプロジェクト名として解決されうる。Python 側の `_dispatch_lifecycle` には名前を解決して移動する経路が既にある。そちらに寄せる。 + +## テスト設計 + +| 受け入れ条件 | 何で確かめるか | +| --- | --- | +| `profiles` 付きのサービスは `devbase up` で起動しない | Compose の既定の挙動。生成物が `profiles` を保つことを `tests/volume/test_compose_profiles.py` で検査する | +| `profile up X` で対象サービスだけが起動する | `subprocess.run` を差し替え、組み立てた引数列が `--profile X up -d <対象サービス>` であり、dev を含まないことを検査する | +| 既定のサービスの Container ID と `StartedAt` が変わらない | 手動確認(`alpine:3` の最小構成で `docker inspect` の値を前後で比較する) | +| `profile down X` でコンテナが削除され、ボリュームは残る | 引数列に `down <対象サービス>` が含まれ、`--volumes` を含まないことを検査する | +| `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する | +| 構成ファイルが無いときに 1 で止まる | 空の一時ディレクトリで呼び、終了コードと、コンテナを作る呼び出しが発生しないことを検査する | +| 未知のプロファイル名で 1 で止まる | 既知の名前が出力に並ぶことと終了コードを検査する | +| `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | +| `devbase down` が全プロファイルを消し、0 で終わる | 引数列に `--profile *` が含まれることを検査する。全体の削除は手動確認 | +| `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることを検査する。実際の状態は手動確認 | +| フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する | +| フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | +| プロファイルを持たないプロジェクトの挙動が変わらない | 既存の `tests/commands/test_container_up_order.py` と `tests/cli/test_up_roundtrips.py` が通ること | +| 生成物が `profiles` を保つ | `generate_scaled_compose` の出力を読み、非 dev サービスの `profiles` が残ることを検査する | + +テストは実 docker と実 `DEVBASE_ROOT` に触れない。`subprocess.run` を差し替える。作業ディレクトリは `tmp_path` を使う。 + +## 未確認のまま残ること + +| 項目 | 内容 | +| --- | --- | +| 古い Compose での `--profile '*'` | 手元で確かめたのは v5.1.4 のみ。ワイルドカードを解釈しない版での挙動(対象が現在と同じに留まる想定)は未確認 | +| プロファイルが複数同時に有効な場合 | `DEVBASE_ACTIVE_PROFILES` はカンマ区切りを許すが、同時に 2 つ以上を起動する操作は今回作らない。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | +| プロファイルのサービスが dev を `depends_on` に持つ構成 | 今回の対象(dev → プロファイル)と向きが逆の依存は検証していない | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md new file mode 100644 index 00000000..b213f77d --- /dev/null +++ b/issues/PLAN58_compose-profiles.md @@ -0,0 +1,130 @@ +# PLAN58: Compose の profiles で付随サービス群を後から起動・停止する + +対象 issue: devbasex/devbase#189 +関連: volareinc/devbase-ext#31(carmo-system-console 側の適用) + +## 依頼(原文) + +> carmo-system-consoleでは、devコンテナと同時にテスト用のapp / dbコンテナなども全て立ち上がってしまいますが、defaultはdevコンテナだけとし、 +> あとから追加でテスト用サーバ群も立ち上げられるようにしたい。 +> +> 方法を大まかに検討してissuesを起票してください + +> 追加: +> テストコンテナ群は群後から起動も停止もできるようにしたい(この際、devコンテナの稼働には影響がないようにすること) + +## 目的 + +dev のほかに app / db などのサービスを持つプロジェクトで、`devbase up` の既定では dev だけを起動し、付随するサービス群は後から起動・停止できるようにする。その起動・停止で dev コンテナを再作成も再起動もしない。 + +判定に使うのは Compose の `profiles` である。プロジェクトは `compose.yml` の各サービスへ `profiles:` を書くだけでよく、devbase 側は「プロファイルを指定して起動・停止する口」と「停止を全プロファイルへ効かせる」ことを担う。 + +## 前提 + +- 前提 1: プロファイル名は devbase が決めず、プロジェクトが `compose.yml` に書いた名前をそのまま受ける。devbase は既定のプロファイル名を持たない +- 前提 2: プロファイル付きのサービスは scale の対象にしない。dev だけが `dev-1`..`dev-N` へ複製される現在の仕組みは変えない +- 前提 3: `devbase up` はテスト用サーバが起動していても、既定の状態(dev だけ)へ揃える。継続したい利用者は `up` の後にもう一度プロファイルを起動する(利用者の指示、2026-09-16) +- 前提 4: プロファイルを使っていないプロジェクトの `up` / `down` / `scale` の挙動は変えない + +## 対象範囲 + +含む: + +- `profiles:` 付きのサービスを `devbase up` で起動しないこと(Compose の既定の挙動をそのまま通す) +- プロファイル単位で起動・停止するコマンド +- 停止(`devbase down` と `up` 冒頭の停止)を全プロファイルへ効かせること +- プロジェクトのフックへ、有効なプロファイルを伝えること +- プロファイルを使うプロジェクト作者向けの文書 + +含まない: + +- プロファイル付きサービスの scale(インスタンスごとの複製) +- `project.yml` で既定の有効プロファイルを宣言する仕組み(必要になってから別 issue で扱う) +- carmo-system-console 側の `compose.yml` / フックの書き換え(volareinc/devbase-ext#31) +- 新しい型・クラスの追加。実装は既存のモジュール関数の並びに足す(そのためクラス図を作らない) +- 永続データの追加・変更(そのため ER 図・テーブル定義・CRUD 図を作らない) +- 画面の追加・変更(そのため画面一覧・遷移図を作らない) + +## 用語 + +| 用語 | 意味 | +| --- | --- | +| プロファイル | Compose の `profiles:` に書いた名前。付随サービス群をまとめる単位 | +| 既定のサービス | `profiles:` を持たないサービス。`devbase up` で起動する | +| プロファイルのサービス | そのプロファイルに属し、既定のサービスに含まれないサービス | + +## 受け入れ条件 + +起動と停止: + +- [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない +- [ ] `devbase container profile up X` を実行すると、プロファイル X のサービスだけが起動する。既定のサービス(dev を含む)の Container ID と `StartedAt` は実行の前後で変わらない +- [ ] `devbase container profile down X` を実行すると、プロファイル X のサービスのコンテナが削除される。既定のサービスの Container ID と `StartedAt` は実行の前後で変わらない +- [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る +- [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す +- [ ] `.docker-compose.scale.yml` が無い状態で `devbase container profile up X` を実行すると、`devbase up` を促すメッセージを出して終了コード 1 で止まる(コンテナは作らない) +- [ ] `compose.yml` に無いプロファイル名を渡すと、存在する名前の一覧を出して終了コード 1 で止まる +- [ ] `devbase project profile up <プロジェクト> X` は、そのプロジェクトのディレクトリで `devbase container profile up X` を実行したのと同じ結果になる(`down` / `list` も同じ) + +停止の網羅: + +- [ ] プロファイル X のサービスが起動している状態で `devbase down` を実行すると、既定のサービスとプロファイル X のサービスの両方が削除され、終了コード 0 で終わる +- [ ] 同じ状態で `devbase up` を実行すると、冒頭の停止でプロファイル X のサービスも止まり、起動後は既定のサービスだけが動いている + +フック: + +- [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` では空、`devbase container profile up X` の後に呼ばれるときは `X`(複数あればカンマ区切り)が入る +- [ ] `devbase container profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる + +退行しないこと: + +- [ ] `profiles:` を 1 つも持たないプロジェクトで、`devbase up` / `down` / `scale` が起動するコンテナの集合と順序が現在と変わらない +- [ ] 生成される `.docker-compose.scale.yml` は、プロファイル付きのサービスについても `profiles:` を保ったまま出力する + +## 非機能の条件 + +| 大項目 | 条件 | +| --- | --- | +| 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | +| セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | +| システム環境 | Docker Compose v2 系および v5 系で動く。`--profile '*'` と `depends_on.required` を使う | + +## 影響 + +| 対象 | 影響 | +| --- | --- | +| 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | +| データ | なし(スキーマも名前付きボリュームの構成も変えない) | +| 既存の振る舞い | `down` が全プロファイルを対象にする。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも観測できる違いを生まない | + +## 検証手段 + +| 項目 | 手段 | +| --- | --- | +| テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | +| 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | +| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する | + +自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 + +## 前提とする取り決め + +| 項目 | 参照先 / 決めたこと | +| --- | --- | +| プロジェクト構造 | CLI の実装は `lib/devbase/` 配下(CONTRIBUTING.md)。コマンドの追加は `lib/devbase/cli.py` と `lib/devbase/commands/container.py`、compose の生成物に関わる部分は `lib/devbase/volume/compose.py` | +| コーディング規約 | Python は PEP 8(CONTRIBUTING.md)。文書は `docs/` 配下、図は Mermaid | +| テスト戦略 | `tests/commands/` と `tests/volume/` の既存の書き方に合わせ、`subprocess` を差し替えて **組み立てたコマンド列**を検証する。実 docker と実 DEVBASE_ROOT には触れない(テストは実環境の `DEVBASE_ROOT` を継承するため、backend を隔離する) | + +## 境界 + +| 区分 | 内容 | +| --- | --- | +| 常に行う | 既存テストの実行、変更範囲内の命名統一、日本語のコメントと文書 | +| 確認してから行う | 既存コマンドの引数の変更、`project.yml` のスキーマ追加、フック名の新設 | +| 行わない | 依頼範囲外のリファクタリング、`.docker-compose.scale.yml` の生成規則のうちプロファイルに関係しない部分の変更 | + +## 未決 + +| 項目 | 誰が決めるか | 期限 | +| --- | --- | --- | +| プロファイル起動の後に呼ぶフックを `./deploy` の再利用にするか、新しい名前にするか | 設計工程(`design`)で決める | 実装計画の前 | From d9cdf55bde1039f6106b445bc2a371b2cbfa7d5b Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 11:30:57 +0900 Subject: [PATCH 02/22] =?UTF-8?q?docs(PLAN58):=20=E3=83=AC=E3=83=93?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E6=8C=87=E6=91=98=E3=81=AB=E6=B2=BF=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E8=A6=81=E6=B1=82=E4=BB=95=E6=A7=98=E3=81=A8=E8=A8=AD?= =?UTF-8?q?=E8=A8=88=E3=81=AE=E8=A8=98=E8=BF=B0=E3=82=92=E7=9B=B4=E3=81=99?= =?UTF-8?q?=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - プロファイルの宣言元 (compose.yml) と devbase が読む生成物の対応を明記 - container/ct の profile は name positional を受けないことを契約と決定 6 に反映 - profile down が docker_compose_down を通らないことを決定 5 に明記 - profile list の表のスキーマ (PROFILE / SERVICES / RUNNING) と partial の表し方を追記 - deploy フックへ渡す indices の解決元 (config.scale / DEFAULT_SCALE) を追記 - hook_env の変更が _run_pre_up_hook にも効くことを決定 3 に明記 - 入れ子 subparser の dest 分離 (profile_subcommand) と誤ディスパッチ回避を追記 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 78 ++++++++++++++++++++---- issues/PLAN58_compose-profiles.md | 10 ++- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 35ad6464..b1aefba0 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -19,7 +19,7 @@ | プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | | Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。停止には全プロファイルを指定する | -| フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ | +| フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | ```mermaid @@ -107,12 +107,16 @@ tests/ | --- | --- | --- | | `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | | `cmd_profile_up(profile, context)` | 新設 | F1。終了コードを返す | -| `cmd_profile_down(profile, context)` | 新設 | F2。終了コードを返す | +| `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、自分で `down` のコマンド列を組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | -| `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ | -| `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す | +| `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | +| `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(...) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | -| `_dispatch_lifecycle` | 変更 | `profile` のサブコマンドを handlers へ足す | +| `_dispatch_lifecycle` | 変更 | `profile` を handlers へ 1 つ足し、`args.profile_subcommand` で 3 つの入口へ振り分ける | + +`_run_deploy_script_for_instances` へ渡す `indices` は `range(1, scale + 1)` である。`scale` は `project.yml` の `config.scale` から取る。未指定なら `project_runtime.DEFAULT_SCALE`(現在は 2)を使う。`cmd_up` が使っている解決の式をそのまま再利用する。`.docker-compose.scale.yml` の `dev-*` を数え直すことはしない。 + +`profile` の subparser は `dest` を親と分ける。親の `project` / `container` は `dest='subcommand'` のままとし、入れ子側は `dest='profile_subcommand'` を使う。`cli.py` の `_dispatch` は `args.subcommand == 'list'` を見て `project list`(プロジェクト一覧)へ振り分けるためである。入れ子で `subcommand` を再利用すると、`devbase project profile list` がそちらへ流れてしまう。`_dispatch_lifecycle` の handlers には `'profile'` を 1 つだけ足す。その中で `profile_subcommand` を見て up / down / list を選ぶ。 ## 入出力の契約 @@ -125,9 +129,29 @@ tests/ | `devbase project profile up [name] ` | プロファイル名(必須)、プロジェクト名(省略時は現在地) | 対象サービスを起動し、フックを実行して 0 | 構成ファイルが無い / 未知のプロファイル / Compose かフックが失敗 → 1 | | `devbase project profile down [name] ` | 同上 | 対象サービスのコンテナを削除して 0 | 同上(フックは呼ばない) | | `devbase project profile list [name]` | プロジェクト名(省略可) | プロファイルと稼働状況を表で出して 0 | 構成ファイルが無い → 1 | -| `devbase container profile ...` / `devbase ct profile ...` | 同上 | 同上。非推奨の警告を 1 行出す | 同上 | +| `devbase container profile up ` / `down ` / `list`(`ct` も同じ) | プロファイル名のみ。プロジェクト名は受け付けない | 同上。非推奨の警告を 1 行出す | 同上 | + +`project` の `[name]` と `` の並びは既存の `scale` と同じ規則に従う。値が 1 個ならプロファイル名に割り当てられる。2 個なら(プロジェクト名、プロファイル名)になる。 + +`container` / `ct` は `[name]` を持たない。値は常にプロファイル名である。`container` 群のサブコマンドは現在地のプロジェクトで動く既存の規約に従う。この非対称は `project` / `container` の既存の作りと同じである。 + +### `profile list` の表 + +| 列 | 内容 | +| --- | --- | +| PROFILE | プロファイル名。生成物に現れた順で並べる | +| SERVICES | そのプロファイルに属するサービス名。宣言順にカンマ区切りで並べる | +| RUNNING | `稼働数/総数` と状態語。例: `3/3 running`、`1/3 partial`、`0/3 stopped` | + +状態語の決め方は次のとおりである。 + +| 稼働数 | 状態語 | +| --- | --- | +| 総数と同じ | `running` | +| 1 以上で総数未満 | `partial` | +| 0 | `stopped` | -`[name]` と `` の並びは既存の `scale` と同じ規則に従う。値が 1 個ならプロファイル名に割り当てられる。2 個なら(プロジェクト名、プロファイル名)になる。 +稼働の判定には `docker compose ps --format json` を使う。`State` が `running` のサービスだけを数える。`exited` や `paused` は稼働に数えない。プロファイルが 1 つも無ければ、見出しの行だけを出して 0 で終わる。 ### 互換性の扱い @@ -137,7 +161,9 @@ tests/ | `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じで、観測できる違いを生まない | | `DEVBASE_ACTIVE_PROFILES` の追加 | 無い。既存のフックは読まなければ従来どおり動く | -`bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` は変えない。この一覧は「3 番目の引数をプロジェクト名として解決してよいサブコマンド」を表す。`profile` ではその位置に `up` / `down` / `list` が来る。一覧へ足すと、`up` という名前のプロジェクトが実在したときにそちらへ移動してしまう。プロジェクト名の解決は Python 側の `_dispatch_lifecycle` に任せる。 +`bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` は変えない。現在の中身は `up down ps logs scale rebuild` である。この一覧は「3 番目の引数をプロジェクト名として解決してよいサブコマンド」を表す。`profile` ではその位置に `up` / `down` / `list` が来る。一覧へ足すと、`up` という名前のプロジェクトが実在したときにそちらへ移動してしまう。プロジェクト名の解決は Python 側の `_dispatch_lifecycle` に任せる。 + +同じ一覧から `login` / `build` も外れている。この 2 つは `project` でも `[name]` を受け付けない。`profile` が `container` で `[name]` を受け付けないのも同じ筋である。 ### 検査の手段 @@ -173,7 +199,9 @@ sequenceDiagram end ``` -停止(F2)は同じ並びから、フックの呼び出しを除いたものである。渡すのは `up -d` ではなく `down <サービス...>` である。 +停止(F2)は同じ並びから、フックの呼び出しを除いたものである。渡すのは `up -d` ではなく `down <サービス...>` である。`docker_compose_down` は通らない。理由は決定 5 に書く。 + +`--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。起動は `['--profile', X, 'up', '-d', <サービス...>]`、停止は `['--profile', X, 'down', <サービス...>]` になる。 `up` と `down` の冒頭の停止(F4)は次のように変わる。 @@ -201,6 +229,8 @@ graph LR `devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 +要求仕様が `compose.yml` と書く箇所との対応は次のとおりである。利用者がプロファイルを宣言するのは `compose.yml` であり、devbase が読むのはその宣言を引き継いだ生成物である。宣言の内容は同じなので、受け入れ条件の文言は変えない。 + `docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 ### 決定 2: プロファイルの操作はサービス名を明示して渡す @@ -213,6 +243,14 @@ graph LR `./post-profile-up` のような新しい名前を足す案は採らない。プロジェクトが持つ約束の数が増える。どちらに書くべきかの判断も各プロジェクトに生まれる。 +`hook_env` は `_run_pre_up_hook` も呼んでいる。そのため `./pre-up` にも `DEVBASE_ACTIVE_PROFILES` が渡る。`./pre-up` を呼ぶのは `cmd_up` だけなので、値は常に空である。`profile up` は `./pre-up` を呼ばない。コンテナの起動前に済ませる準備は `up` の役目だからである。要求仕様が `./pre-up` を挙げるのは、この空の値を含めた約束のことである。 + +| フック | 呼ぶ経路 | `DEVBASE_ACTIVE_PROFILES` | +| --- | --- | --- | +| `./pre-up` | `cmd_up` のみ | 常に空 | +| `./deploy` | `cmd_up` | 空 | +| `./deploy` | `cmd_profile_up` | 起動したプロファイル名 | + ### 決定 4: 停止は全プロファイルを対象にし、`up` は既定の状態へ揃える `devbase up` は「その構成で開発環境を作り直す」操作である。プロファイルのサービスだけが前の状態のまま残ると、`up` の後の状態が直前の操作に依存する。 @@ -223,9 +261,26 @@ graph LR プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行う。こちらは既定の猶予(10 秒)で落とす。 +そのため `cmd_profile_down` は既存の `docker_compose_down` を通らない。この関数は `['down', '-t0']` を固定で組み立て、サービス名の引数も受け取らないためである。`cmd_profile_down` は `docker_compose(['--profile', X, 'down', <サービス...>])` を直接呼ぶ。`-t0` は付けない。 + +`docker_compose_down` に `services` と `timeout` の引数を足す案は採らない。この関数の呼び出し側は `devbase down` と `up` 冒頭の停止だけであり、どちらも全体を `-t0` で落とす。引数を増やすと、使われない組み合わせが関数の表に残る。変更は `--profile '*'` を足すことに留める(決定 4)。 + +| 関数 | 用途 | タイムアウト | サービスの指定 | +| --- | --- | --- | --- | +| `docker_compose_down` | `devbase down` / `up` 冒頭の停止 | `-t0` | しない(全体) | +| `cmd_profile_down` | プロファイルの停止 | 既定(10 秒) | する | + ### 決定 6: プロジェクト名は位置引数で受け、`bin/devbase` は変えない -既存の `scale` と同じ並び(`[name] <値>`)に揃える。`bin/devbase` の名前解決は 3 番目の引数だけを見る。`profile` をその一覧へ足すと、`up` / `down` / `list` がプロジェクト名として解決されうる。Python 側の `_dispatch_lifecycle` には名前を解決して移動する経路が既にある。そちらに寄せる。 +プロジェクト名を受けるのは `project profile` だけである。既存の `scale` と同じ並び(`[name] <値>`)に揃える。`container profile` は受けない。`container` 群のサブコマンドは現在地のプロジェクトで動く、という既存の規約に従う。`project` でも `login` / `build` が同じ理由で `[name]` を持たない。 + +`bin/devbase` は変えない。`_PROJECT_NAME_SUBCOMMANDS`(`up down ps logs scale rebuild`)の名前解決は 3 番目の引数だけを見る。`profile` をその一覧へ足すと、`up` / `down` / `list` がプロジェクト名として解決されうる。Python 側の `_dispatch_lifecycle` には名前を解決して移動する経路が既にある。そちらに寄せる。 + +| 入口 | `[name]` | 名前の解決 | +| --- | --- | --- | +| `devbase project profile up [name] ` | 受ける | `_dispatch_lifecycle`(Python 側) | +| `devbase container profile up ` | 受けない | しない(現在地で動く) | +| `devbase ct profile up ` | 受けない | しない(現在地で動く) | ## テスト設計 @@ -235,7 +290,8 @@ graph LR | `profile up X` で対象サービスだけが起動する | `subprocess.run` を差し替え、組み立てた引数列が `--profile X up -d <対象サービス>` であり、dev を含まないことを検査する | | 既定のサービスの Container ID と `StartedAt` が変わらない | 手動確認(`alpine:3` の最小構成で `docker inspect` の値を前後で比較する) | | `profile down X` でコンテナが削除され、ボリュームは残る | 引数列に `down <対象サービス>` が含まれ、`--volumes` を含まないことを検査する | -| `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する | +| `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する。全稼働・一部稼働・全停止の 3 通りで `3/3 running` / `1/3 partial` / `0/3 stopped` を確かめる | +| `project profile list` が `project list` へ流れない | `profile_subcommand` の分離を、`devbase project profile list` の解析結果と呼ばれたハンドラで検査する | | 構成ファイルが無いときに 1 で止まる | 空の一時ディレクトリで呼び、終了コードと、コンテナを作る呼び出しが発生しないことを検査する | | 未知のプロファイル名で 1 で止まる | 既知の名前が出力に並ぶことと終了コードを検査する | | `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index b213f77d..2c7b3240 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -55,6 +55,8 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 ## 受け入れ条件 +以下で `compose.yml` と書くのは、利用者がプロファイルを宣言する場所のことである。devbase が実際に読むのは、その宣言を引き継いだ生成物 `.docker-compose.scale.yml` である(設計の決定 1)。 + 起動と停止: - [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない @@ -62,8 +64,8 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `devbase container profile down X` を実行すると、プロファイル X のサービスのコンテナが削除される。既定のサービスの Container ID と `StartedAt` は実行の前後で変わらない - [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る - [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す -- [ ] `.docker-compose.scale.yml` が無い状態で `devbase container profile up X` を実行すると、`devbase up` を促すメッセージを出して終了コード 1 で止まる(コンテナは作らない) -- [ ] `compose.yml` に無いプロファイル名を渡すと、存在する名前の一覧を出して終了コード 1 で止まる +- [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない +- [ ] `compose.yml` に無いプロファイル名を `up` / `down` へ渡すと、存在する名前の一覧を出して終了コード 1 で止まる - [ ] `devbase project profile up <プロジェクト> X` は、そのプロジェクトのディレクトリで `devbase container profile up X` を実行したのと同じ結果になる(`down` / `list` も同じ) 停止の網羅: @@ -73,7 +75,9 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 フック: -- [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` では空、`devbase container profile up X` の後に呼ばれるときは `X`(複数あればカンマ区切り)が入る +- [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` から呼ばれるときは、どちらも空である +- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る(複数あればカンマ区切り) +- [ ] `devbase container profile up X` は `./pre-up` を呼ばない - [ ] `devbase container profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる 退行しないこと: From bfad3b5108dc0463bb47ca30060fca6f6c83bbe4 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 11:40:08 +0900 Subject: [PATCH 03/22] =?UTF-8?q?docs(PLAN58):=20=E9=A0=86=E6=96=B9?= =?UTF-8?q?=E5=90=91=E3=81=AE=E4=BE=9D=E5=AD=98=E3=81=AE=E5=89=8D=E6=8F=90?= =?UTF-8?q?=E3=81=A8=E6=9C=80=E4=BD=8E=E5=AF=BE=E5=BF=9C=20Compose=20?= =?UTF-8?q?=E7=89=88=E3=82=92=E6=98=8E=E8=A8=98=E3=81=99=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit プロファイルのサービスが dev を depends_on に持つ構成を、前提 5 として 要求仕様へ明記した。生成物では dev-1..N へ書き換わり、既定の required: true では profile up が dev を対象へ取り込み再作成しうるためである。決定 2 へ 判定の表を足し、テスト設計にも確かめ方を加えた。 あわせて最低対応版を Docker Compose 2.20.0 と定めた。前提 5 が要求する depends_on.required が 2.20.0 からの機能で、それ未満では構成の検証に失敗する。 --profile '*' が使える最古の版は公式ドキュメントに記載が無く、確かめられな かったことを「未確認のまま残ること」へ残した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 27 +++++++++++++++++++++--- issues/PLAN58_compose-profiles.md | 15 +++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index b1aefba0..60ae251c 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -219,9 +219,16 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose v2 系および v5 系で動く。`--profile '*'` と `depends_on.required` を使う | `--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる(機能は落ちるが壊れない) | v5.1.4 で全サービスが消えることを手動で確かめる。古い版は「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。`--profile '*'` と `depends_on.required` を使う | 最低対応版を 2.20.0 とする。`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる | v5.1.4 で全サービスが消えることを手動で確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、「未確認のまま残ること」に載せる | -`depends_on.required: false` はプロジェクト側の書き方であり、devbase の実装には現れない。文書で案内する。 +最低対応版は `depends_on.required` が決める。この属性は 2.20.0 からの機能である。前提 5 はプロファイルのサービスへこの属性を要求する。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 + +| 版 | 扱い | +| --- | --- | +| 2.20.0 以上 | 対応する。手元で確かめたのは v5.1.4 | +| 2.20.0 未満の v2 | 対象外。`required: false` を書いた構成の検証に失敗する | + +`depends_on.required: false` を書くのはプロジェクト側であり、devbase の実装には現れない。文書で案内する。ただし任意の推奨ではなく、順方向の依存を持つ場合の必須条件である(決定 2)。 ## 決定の記録 @@ -237,6 +244,18 @@ graph LR サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示し、dev を照合から外す。 +**サービス名を明示しても、順方向の依存は対象を広げる。** `_build_scaled_services` は非 dev サービスにも `_rewrite_depends_on` を適用する(`lib/devbase/volume/compose.py:488`)。プロファイルのサービスが `depends_on: dev` を書いていると、生成物では `dev-1`..`dev-N` になる。`depends_on` の既定は `required: true` である。Compose はこれを解決し、dev-1..N を対象へ取り込んで再作成しうる。受け入れ条件の「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。 + +そこで、プロファイルのサービスが既定のサービスへ依存を持つ場合は `required: false` を必須とする(要求仕様の前提 5)。 + +| プロファイルのサービスの書き方 | `profile up X` が触るもの | 判定 | +| --- | --- | --- | +| `depends_on` を書かない | そのサービスだけ | 可 | +| `depends_on: {dev: {required: false}}` | そのサービスだけ | 可 | +| `depends_on: [dev]`(既定の `required: true`) | そのサービスと dev-1..N | 不可。dev を再作成しうる | + +生成のときに devbase が `required: false` を補う案は採らない。依存が必須かどうかはプロジェクトが決める意図であり、生成物が黙って緩めると `devbase up` の起動順の意図が読めなくなる。2.20.0 未満でしか動かせない利用者にも、書いていない属性を押し付けることになる。 + ### 決定 3: プロファイル起動の後は `./deploy` を呼び直す。新しいフックは作らない `./deploy` は既にインスタンスごとに呼ばれ、プロジェクト側で冪等に書かれている。プロファイルの有無は `DEVBASE_ACTIVE_PROFILES` で分岐できる。フック名を増やす理由がない。 @@ -289,6 +308,8 @@ graph LR | `profiles` 付きのサービスは `devbase up` で起動しない | Compose の既定の挙動。生成物が `profiles` を保つことを `tests/volume/test_compose_profiles.py` で検査する | | `profile up X` で対象サービスだけが起動する | `subprocess.run` を差し替え、組み立てた引数列が `--profile X up -d <対象サービス>` であり、dev を含まないことを検査する | | 既定のサービスの Container ID と `StartedAt` が変わらない | 手動確認(`alpine:3` の最小構成で `docker inspect` の値を前後で比較する) | +| `depends_on: dev` を持つプロファイルサービスで dev が再作成されない | 手動確認。`required: false` を付けた構成で `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる。同じ構成から `required: false` を外すと dev が対象に入ることも確かめ、前提 5 が要ることを示す | +| 生成物が `depends_on` の `required` を保つ | `generate_scaled_compose` の出力で、`depends_on: {dev: {required: false}}` が `dev-1`..`dev-N` へ写り、各要素に `required: false` が残ることを検査する | | `profile down X` でコンテナが削除され、ボリュームは残る | 引数列に `down <対象サービス>` が含まれ、`--volumes` を含まないことを検査する | | `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する。全稼働・一部稼働・全停止の 3 通りで `3/3 running` / `1/3 partial` / `0/3 stopped` を確かめる | | `project profile list` が `project list` へ流れない | `profile_subcommand` の分離を、`devbase project profile list` の解析結果と呼ばれたハンドラで検査する | @@ -308,6 +329,6 @@ graph LR | 項目 | 内容 | | --- | --- | +| `--profile '*'` が使える最古の版 | 公式ドキュメント([profiles](https://docs.docker.com/compose/how-tos/profiles/))に版の記載が無く、確かめられなかった。最低対応版は `depends_on.required` の 2.20.0 を根拠に定めた | | 古い Compose での `--profile '*'` | 手元で確かめたのは v5.1.4 のみ。ワイルドカードを解釈しない版での挙動(対象が現在と同じに留まる想定)は未確認 | | プロファイルが複数同時に有効な場合 | `DEVBASE_ACTIVE_PROFILES` はカンマ区切りを許すが、同時に 2 つ以上を起動する操作は今回作らない。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | -| プロファイルのサービスが dev を `depends_on` に持つ構成 | 今回の対象(dev → プロファイル)と向きが逆の依存は検証していない | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 2c7b3240..4851107c 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -25,6 +25,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - 前提 2: プロファイル付きのサービスは scale の対象にしない。dev だけが `dev-1`..`dev-N` へ複製される現在の仕組みは変えない - 前提 3: `devbase up` はテスト用サーバが起動していても、既定の状態(dev だけ)へ揃える。継続したい利用者は `up` の後にもう一度プロファイルを起動する(利用者の指示、2026-09-16) - 前提 4: プロファイルを使っていないプロジェクトの `up` / `down` / `scale` の挙動は変えない +- 前提 5: プロファイルのサービスは、既定のサービス(dev を含む)を `depends_on` に持たない。持つ場合は `required: false` を付ける。この前提が崩れると、プロファイルの起動が dev を巻き込んで再作成しうる(設計の決定 2) ## 対象範囲 @@ -62,6 +63,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない - [ ] `devbase container profile up X` を実行すると、プロファイル X のサービスだけが起動する。既定のサービス(dev を含む)の Container ID と `StartedAt` は実行の前後で変わらない - [ ] `devbase container profile down X` を実行すると、プロファイル X のサービスのコンテナが削除される。既定のサービスの Container ID と `StartedAt` は実行の前後で変わらない +- [ ] `depends_on: {dev: {required: false}}` を持つプロファイル X のサービスを `devbase container profile up X` で起動しても、既定のサービスの Container ID と `StartedAt` は変わらない - [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る - [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない @@ -91,7 +93,16 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | -| システム環境 | Docker Compose v2 系および v5 系で動く。`--profile '*'` と `depends_on.required` を使う | +| システム環境 | Docker Compose 2.20.0 以上で動く。`--profile '*'` と `depends_on.required` を使う。2.20.0 未満は対象外とする | + +最低対応版を 2.20.0 とする根拠は次のとおりである。 + +| 使う機能 | 使える版 | 根拠 | +| --- | --- | --- | +| `depends_on.required` | 2.20.0 以上 | [公式仕様](https://docs.docker.com/reference/compose-file/services/#depends_on)に「Introduced in Docker Compose version 2.20.0」とある | +| `--profile '*'` | 未確認 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | + +2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。前提 5 はこの属性を要求するため、その版では起動できない構成になる。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 ## 影響 @@ -107,7 +118,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | -| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する | +| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {required: false}}` を付けた版でも同じ手順を通す | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From 68fd3dfcba957a43edba9bf6af8cf9bc421f2493 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 11:56:52 +0900 Subject: [PATCH 04/22] =?UTF-8?q?docs(PLAN58):=20=E6=97=A2=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9=E3=81=AE=E9=99=A4?= =?UTF-8?q?=E5=A4=96=E3=82=92=20--no-deps=20=E3=81=B8=E6=94=B9=E3=82=81?= =?UTF-8?q?=E3=80=81depends=5Fon=20=E3=81=AE=E4=BE=8B=E3=81=AB=20condition?= =?UTF-8?q?=20=E3=82=92=E8=B6=B3=E3=81=99=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 決定 2 を「サービス名をすべて明示し `--no-deps` を付ける」へ改めた。 `required: false` は依存先の不在を緩めるだけで、対象からは外さない - 前提 5 を `--no-deps` 前提へ書き換え、プロファイルのサービスを 全件渡すことを明記した - 受け入れ条件と手動確認に、dev の環境変数の値を変えた場合を足した - map 記法の `depends_on` の例へ `condition: service_started` を足した - `_run_deploy_script_for_instances` のシグネチャと引数の受け渡しを明示した Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 60 +++++++++++++++--------- issues/PLAN58_compose-profiles.md | 14 ++++-- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 60ae251c..47c9f63f 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -18,7 +18,7 @@ | --- | --- | | プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。停止には全プロファイルを指定する | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付け、停止には全プロファイルを指定する | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | @@ -76,7 +76,7 @@ graph TD P -.->|触らない| D1 ``` -境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名を明示して渡す。そのため dev-1..N は再作成の対象に入らない。 +境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付けて渡す。そのため dev-1..N は操作の対象に入らない。 ## 置き場所 @@ -106,14 +106,23 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | | `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | -| `cmd_profile_up(profile, context)` | 新設 | F1。終了コードを返す | +| `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、自分で `down` のコマンド列を組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | | `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | -| `_run_deploy_script_for_instances(...) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | +| `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | | `_dispatch_lifecycle` | 変更 | `profile` を handlers へ 1 つ足し、`args.profile_subcommand` で 3 つの入口へ振り分ける | +`active_profiles` は `cmd_profile_up` からフックまで、引数として順に手渡す。`_run_deploy_script_for_instances` がこの引数を持たないと、`hook_env` へ値を届ける経路が無い。既存の呼び出しを壊さないため、既定は空のタプルとする。 + +| 呼び出し元 | 渡す値 | `./deploy` が受け取る `DEVBASE_ACTIVE_PROFILES` | +| --- | --- | --- | +| `cmd_up` | 省略(既定の `()`) | 空文字列 | +| `cmd_profile_up` | `(X,)`(起動したプロファイル名) | `X` | + +`_run_deploy_script_for_instances` は受け取った値を加工せず `hook_env(config, active_profiles=active_profiles)` へ渡す。環境変数の名前と区切り(カンマ)を決めるのは `hook_env` だけである。 + `_run_deploy_script_for_instances` へ渡す `indices` は `range(1, scale + 1)` である。`scale` は `project.yml` の `config.scale` から取る。未指定なら `project_runtime.DEFAULT_SCALE`(現在は 2)を使う。`cmd_up` が使っている解決の式をそのまま再利用する。`.docker-compose.scale.yml` の `dev-*` を数え直すことはしない。 `profile` の subparser は `dest` を親と分ける。親の `project` / `container` は `dest='subcommand'` のままとし、入れ子側は `dest='profile_subcommand'` を使う。`cli.py` の `_dispatch` は `args.subcommand == 'list'` を見て `project list`(プロジェクト一覧)へ振り分けるためである。入れ子で `subcommand` を再利用すると、`devbase project profile list` がそちらへ流れてしまう。`_dispatch_lifecycle` の handlers には `'profile'` を 1 つだけ足す。その中で `profile_subcommand` を見て up / down / list を選ぶ。 @@ -191,7 +200,7 @@ sequenceDiagram OP-->>U: 既知の名前を並べて 1 else RS-->>OP: サービス名の集合 - OP->>DC: compose --profile local-app up -d <サービス...> + OP->>DC: compose --profile local-app up -d --no-deps <サービス...> DC-->>OP: 終了コード OP->>HK: ./deploy(DEVBASE_ACTIVE_PROFILES=local-app) HK-->>OP: 全インスタンスの成否 @@ -201,7 +210,7 @@ sequenceDiagram 停止(F2)は同じ並びから、フックの呼び出しを除いたものである。渡すのは `up -d` ではなく `down <サービス...>` である。`docker_compose_down` は通らない。理由は決定 5 に書く。 -`--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。起動は `['--profile', X, 'up', '-d', <サービス...>]`、停止は `['--profile', X, 'down', <サービス...>]` になる。 +`--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。起動は `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]`、停止は `['--profile', X, 'down', <サービス...>]` になる。`<サービス...>` はプロファイル X に属するサービスの全件である(決定 2)。 `up` と `down` の冒頭の停止(F4)は次のように変わる。 @@ -219,16 +228,16 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。`--profile '*'` と `depends_on.required` を使う | 最低対応版を 2.20.0 とする。`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる | v5.1.4 で全サービスが消えることを手動で確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる | v5.1.4 で全サービスが消えることを手動で確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、「未確認のまま残ること」に載せる | -最低対応版は `depends_on.required` が決める。この属性は 2.20.0 からの機能である。前提 5 はプロファイルのサービスへこの属性を要求する。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 +既定のサービスを対象から外すのは `--no-deps` である。この選択肢は古くからあり、版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 | 版 | 扱い | | --- | --- | | 2.20.0 以上 | 対応する。手元で確かめたのは v5.1.4 | | 2.20.0 未満の v2 | 対象外。`required: false` を書いた構成の検証に失敗する | -`depends_on.required: false` を書くのはプロジェクト側であり、devbase の実装には現れない。文書で案内する。ただし任意の推奨ではなく、順方向の依存を持つ場合の必須条件である(決定 2)。 +`depends_on.required: false` を書くのはプロジェクト側であり、devbase の実装には現れない。文書で案内する。ただし dev を対象から外す働きは持たない。その役目は `--no-deps` が担う(決定 2)。 ## 決定の記録 @@ -240,21 +249,28 @@ graph LR `docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 -### 決定 2: プロファイルの操作はサービス名を明示して渡す +### 決定 2: プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付ける -サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示し、dev を照合から外す。 +サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示する。 -**サービス名を明示しても、順方向の依存は対象を広げる。** `_build_scaled_services` は非 dev サービスにも `_rewrite_depends_on` を適用する(`lib/devbase/volume/compose.py:488`)。プロファイルのサービスが `depends_on: dev` を書いていると、生成物では `dev-1`..`dev-N` になる。`depends_on` の既定は `required: true` である。Compose はこれを解決し、dev-1..N を対象へ取り込んで再作成しうる。受け入れ条件の「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。 +**サービス名の明示だけでは足りない。** `_build_scaled_services` は非 dev サービスにも `_rewrite_depends_on` を適用する(`lib/devbase/volume/compose.py:488`)。プロファイルのサービスが `depends_on: dev` を書いていると、生成物では `dev-1`..`dev-N` になる。Compose は依存先を解決して対象へ取り込む。受け入れ条件の「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。 -そこで、プロファイルのサービスが既定のサービスへ依存を持つ場合は `required: false` を必須とする(要求仕様の前提 5)。 +**`required: false` はこれを止めない。** この属性が緩めるのは「依存先が不在のときのエラー」だけである。依存先を操作の対象から外す働きは持たない。Compose v5.1.4 の dry-run でも dev の起動が含まれる。依存先の構成が変わったときに再作成する実装のため、機密などの値が変わった状態では Container ID が変わりうる。 -| プロファイルのサービスの書き方 | `profile up X` が触るもの | 判定 | +そこで `--no-deps` を使う。起動のコマンド列は `--profile X up -d --no-deps <対象サービス...>` になる。この選択肢は依存先を操作の対象から外す。 + +**`--no-deps` は依存先を自動起動しない。** そのため、プロファイルに属するサービスをすべて明示して渡すことが前提になる(要求仕様の前提 5)。1 つでも落とすと、そのサービスは起動しない。渡す集合は「プロファイルの解決」が生成物から求めるため、取りこぼしは起きない。 + +| 組み立て方 | `profile up X` が触るもの | 判定 | | --- | --- | --- | -| `depends_on` を書かない | そのサービスだけ | 可 | -| `depends_on: {dev: {required: false}}` | そのサービスだけ | 可 | -| `depends_on: [dev]`(既定の `required: true`) | そのサービスと dev-1..N | 不可。dev を再作成しうる | +| `--profile X up -d`(サービス名なし) | 既定のサービスを含む全体 | 不可。dev を再作成しうる | +| `--profile X up -d <一部のサービス>` | 渡したサービスだけ | 不可。残りが起動しない | +| `--profile X up -d <対象サービス...>`(`--no-deps` なし) | そのサービスと依存先の dev-1..N | 不可。dev を再作成しうる | +| `--profile X up -d --no-deps <対象サービス...>` | そのサービスだけ | 可 | + +`depends_on` の書き方は前提にしない。`depends_on: {dev: {condition: service_started, required: false}}` でも `depends_on: [dev]` でも、`--no-deps` を付ければ dev は対象に入らない。 -生成のときに devbase が `required: false` を補う案は採らない。依存が必須かどうかはプロジェクトが決める意図であり、生成物が黙って緩めると `devbase up` の起動順の意図が読めなくなる。2.20.0 未満でしか動かせない利用者にも、書いていない属性を押し付けることになる。 +生成のときに devbase が `required: false` を補う案は採らない。依存が必須かどうかはプロジェクトが決める意図であり、生成物が黙って緩めると `devbase up` の起動順の意図が読めなくなる。対象から外す役目は `--no-deps` が担うため、補う必要もない。 ### 決定 3: プロファイル起動の後は `./deploy` を呼び直す。新しいフックは作らない @@ -306,10 +322,12 @@ graph LR | 受け入れ条件 | 何で確かめるか | | --- | --- | | `profiles` 付きのサービスは `devbase up` で起動しない | Compose の既定の挙動。生成物が `profiles` を保つことを `tests/volume/test_compose_profiles.py` で検査する | -| `profile up X` で対象サービスだけが起動する | `subprocess.run` を差し替え、組み立てた引数列が `--profile X up -d <対象サービス>` であり、dev を含まないことを検査する | +| `profile up X` で対象サービスだけが起動する | `subprocess.run` を差し替え、組み立てた引数列が `--profile X up -d --no-deps <対象サービス...>` であり、dev を含まないことを検査する | +| プロファイルのサービスをすべて渡す | 同じ引数列に、プロファイル X に属するサービスが全件並ぶことを検査する。1 つでも欠けると `--no-deps` で起動しないため(決定 2) | | 既定のサービスの Container ID と `StartedAt` が変わらない | 手動確認(`alpine:3` の最小構成で `docker inspect` の値を前後で比較する) | -| `depends_on: dev` を持つプロファイルサービスで dev が再作成されない | 手動確認。`required: false` を付けた構成で `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる。同じ構成から `required: false` を外すと dev が対象に入ることも確かめ、前提 5 が要ることを示す | -| 生成物が `depends_on` の `required` を保つ | `generate_scaled_compose` の出力で、`depends_on: {dev: {required: false}}` が `dev-1`..`dev-N` へ写り、各要素に `required: false` が残ることを検査する | +| `depends_on: dev` を持つプロファイルサービスで dev が再作成されない | 手動確認。`depends_on: {dev: {condition: service_started, required: false}}` の構成と `depends_on: [dev]` の構成の両方で `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる | +| dev の環境変数の値が変わっても dev が再作成されない | 手動確認。機密など dev の環境変数の値を変えてから `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる。`--no-deps` が無い組み立てでは再作成が起きることも確かめ、この選択肢が要ることを示す | +| 生成物が `depends_on` の `required` を保つ | `generate_scaled_compose` の出力で、`depends_on: {dev: {condition: service_started, required: false}}` が `dev-1`..`dev-N` へ写り、各要素に `condition` と `required` が残ることを検査する | | `profile down X` でコンテナが削除され、ボリュームは残る | 引数列に `down <対象サービス>` が含まれ、`--volumes` を含まないことを検査する | | `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する。全稼働・一部稼働・全停止の 3 通りで `3/3 running` / `1/3 partial` / `0/3 stopped` を確かめる | | `project profile list` が `project list` へ流れない | `profile_subcommand` の分離を、`devbase project profile list` の解析結果と呼ばれたハンドラで検査する | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 4851107c..decf4e36 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -25,7 +25,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - 前提 2: プロファイル付きのサービスは scale の対象にしない。dev だけが `dev-1`..`dev-N` へ複製される現在の仕組みは変えない - 前提 3: `devbase up` はテスト用サーバが起動していても、既定の状態(dev だけ)へ揃える。継続したい利用者は `up` の後にもう一度プロファイルを起動する(利用者の指示、2026-09-16) - 前提 4: プロファイルを使っていないプロジェクトの `up` / `down` / `scale` の挙動は変えない -- 前提 5: プロファイルのサービスは、既定のサービス(dev を含む)を `depends_on` に持たない。持つ場合は `required: false` を付ける。この前提が崩れると、プロファイルの起動が dev を巻き込んで再作成しうる(設計の決定 2) +- 前提 5: プロファイルの起動は `--no-deps` を付けて Compose を呼ぶ。そのため既定のサービス(dev を含む)は操作の対象に入らない。あわせて、そのプロファイルに属するサービスをすべて明示して渡す。`--no-deps` は依存先を自動起動しないためである。1 つでも渡し漏らすと、そのサービスは起動しない(設計の決定 2) ## 対象範囲 @@ -63,7 +63,10 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない - [ ] `devbase container profile up X` を実行すると、プロファイル X のサービスだけが起動する。既定のサービス(dev を含む)の Container ID と `StartedAt` は実行の前後で変わらない - [ ] `devbase container profile down X` を実行すると、プロファイル X のサービスのコンテナが削除される。既定のサービスの Container ID と `StartedAt` は実行の前後で変わらない -- [ ] `depends_on: {dev: {required: false}}` を持つプロファイル X のサービスを `devbase container profile up X` で起動しても、既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] `devbase container profile up X` は、プロファイル X に属するサービスをすべて Compose へ渡し、`--no-deps` を付ける +- [ ] `depends_on: {dev: {condition: service_started, required: false}}` を持つプロファイル X のサービスを `devbase container profile up X` で起動しても、既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] `depends_on: [dev]`(`required` を書かない形)を持つサービスでも、`devbase container profile up X` で既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] dev の環境変数の値を変えた後でも、`devbase container profile up X` は dev を再作成しない - [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る - [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない @@ -93,16 +96,17 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | -| システム環境 | Docker Compose 2.20.0 以上で動く。`--profile '*'` と `depends_on.required` を使う。2.20.0 未満は対象外とする | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | 最低対応版を 2.20.0 とする根拠は次のとおりである。 | 使う機能 | 使える版 | 根拠 | | --- | --- | --- | +| `--no-deps` | 2 系全般 | `docker compose up` の古くからある選択肢。版の下限を作らない | | `depends_on.required` | 2.20.0 以上 | [公式仕様](https://docs.docker.com/reference/compose-file/services/#depends_on)に「Introduced in Docker Compose version 2.20.0」とある | | `--profile '*'` | 未確認 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | -2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。前提 5 はこの属性を要求するため、その版では起動できない構成になる。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 +既定のサービスを対象から外すのは `--no-deps` である。この選択肢は版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 ## 影響 @@ -118,7 +122,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | -| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {required: false}}` を付けた版でも同じ手順を通す | +| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From 5429f66317f2f110d59e51c68c86678b3fbf57ce Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:10:14 +0900 Subject: [PATCH 05/22] =?UTF-8?q?docs(PLAN58):=20--profile=20'*'=20?= =?UTF-8?q?=E3=81=AE=E6=96=AD=E5=AE=9A=E3=82=92=E7=A2=BA=E8=AA=8D=E6=B8=88?= =?UTF-8?q?=E3=81=BF=E3=81=AE=E7=89=88=E3=81=B8=E9=99=90=E3=82=8A=E3=80=81?= =?UTF-8?q?=E3=83=95=E3=83=83=E3=82=AF=E3=81=AE=E5=80=A4=E3=82=92=E5=8D=98?= =?UTF-8?q?=E4=B8=80=E3=81=A8=E6=98=8E=E8=A8=98=E3=81=99=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 互換性の扱いの表から「観測できる違いを生まない」の断定を外す。v5.1.4 で 確認済み、2.20.0 以上 5.x 未満は未検証と書き、未確認の表と揃える - この経路が profiles を使わない全プロジェクトの up / down に効くことを明記 - 手動確認へ「profiles を持たないプロジェクトで up / down が従来どおり動く」 を追加(要求仕様の検証手段と設計のテスト設計の両方) - フックの受け入れ条件を「常に単一値。カンマ区切りは将来の拡張の予約」へ直し、 設計の決定 3・未確認の表・テスト設計と揃える Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 24 ++++++++++++++++++------ issues/PLAN58_compose-profiles.md | 8 +++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 47c9f63f..767cd5eb 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -123,6 +123,8 @@ tests/ `_run_deploy_script_for_instances` は受け取った値を加工せず `hook_env(config, active_profiles=active_profiles)` へ渡す。環境変数の名前と区切り(カンマ)を決めるのは `hook_env` だけである。 +今回の範囲では、渡る値は常にプロファイル名 1 つである。`cmd_profile_up` が受ける名前が 1 つだけで、同時に 2 つ以上を起動する操作を作らないためである。カンマ区切りは将来の拡張のための予約であり、現時点でその形になる経路は無い(「未確認のまま残ること」)。 + `_run_deploy_script_for_instances` へ渡す `indices` は `range(1, scale + 1)` である。`scale` は `project.yml` の `config.scale` から取る。未指定なら `project_runtime.DEFAULT_SCALE`(現在は 2)を使う。`cmd_up` が使っている解決の式をそのまま再利用する。`.docker-compose.scale.yml` の `dev-*` を数え直すことはしない。 `profile` の subparser は `dest` を親と分ける。親の `project` / `container` は `dest='subcommand'` のままとし、入れ子側は `dest='profile_subcommand'` を使う。`cli.py` の `_dispatch` は `args.subcommand == 'list'` を見て `project list`(プロジェクト一覧)へ振り分けるためである。入れ子で `subcommand` を再利用すると、`devbase project profile list` がそちらへ流れてしまう。`_dispatch_lifecycle` の handlers には `'profile'` を 1 つだけ足す。その中で `profile_subcommand` を見て up / down / list を選ぶ。 @@ -167,9 +169,18 @@ tests/ | 変更 | 既存の呼び出し側への影響 | | --- | --- | | `profile` サブコマンドの追加 | 無い。既存の引数の形を変えない | -| `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じで、観測できる違いを生まない | +| `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じになる。Docker Compose v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | | `DEVBASE_ACTIVE_PROFILES` の追加 | 無い。既存のフックは読まなければ従来どおり動く | +`--profile '*'` の行だけは、影響の範囲が広い。この経路は `devbase down` と `devbase up` 冒頭の停止に入るため、プロファイルを使わない既存の全プロジェクトを通る。だから「退行しないこと」の受け入れ条件に直結する。 + +対象が変わらないと言えるのは、確かめた版だけである。ワイルドカードを解釈しない版で `*` がリテラルのプロファイル名として扱われるかは「未確認のまま残ること」に載せたままであり、ここでも断定しない。 + +| 版 | `--profile '*'` を付けた `down` の対象 | 根拠 | +| --- | --- | --- | +| v5.1.4 | 現在と同じ | 手元で確認済み | +| 2.20.0 以上 5.x 未満 | 現在と同じになる想定 | 未検証 | + `bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` は変えない。現在の中身は `up down ps logs scale rebuild` である。この一覧は「3 番目の引数をプロジェクト名として解決してよいサブコマンド」を表す。`profile` ではその位置に `up` / `down` / `list` が来る。一覧へ足すと、`up` という名前のプロジェクトが実在したときにそちらへ移動してしまう。プロジェクト名の解決は Python 側の `_dispatch_lifecycle` に任せる。 同じ一覧から `login` / `build` も外れている。この 2 つは `project` でも `[name]` を受け付けない。`profile` が `container` で `[name]` を受け付けないのも同じ筋である。 @@ -228,7 +239,7 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる | v5.1.4 で全サービスが消えることを手動で確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証) | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | 既定のサービスを対象から外すのは `--no-deps` である。この選択肢は古くからあり、版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 @@ -284,7 +295,7 @@ graph LR | --- | --- | --- | | `./pre-up` | `cmd_up` のみ | 常に空 | | `./deploy` | `cmd_up` | 空 | -| `./deploy` | `cmd_profile_up` | 起動したプロファイル名 | +| `./deploy` | `cmd_profile_up` | 起動したプロファイル名 1 つ | ### 決定 4: 停止は全プロファイルを対象にし、`up` は既定の状態へ揃える @@ -336,9 +347,10 @@ graph LR | `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | | `devbase down` が全プロファイルを消し、0 で終わる | 引数列に `--profile *` が含まれることを検査する。全体の削除は手動確認 | | `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることを検査する。実際の状態は手動確認 | -| フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する | +| フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | プロファイルを持たないプロジェクトの挙動が変わらない | 既存の `tests/commands/test_container_up_order.py` と `tests/cli/test_up_roundtrips.py` が通ること | +| プロファイルを持たないプロジェクトで `up` / `down` が従来どおり動く | 手動確認(確認済みの v5.1.4 で実施)。`profiles:` を持たない既存のプロジェクトで `devbase up` と `devbase down` を通し、起動するコンテナの集合と `down` 後に残らないことを確かめる。`--profile '*'` がこの経路に入るため | | 生成物が `profiles` を保つ | `generate_scaled_compose` の出力を読み、非 dev サービスの `profiles` が残ることを検査する | テストは実 docker と実 `DEVBASE_ROOT` に触れない。`subprocess.run` を差し替える。作業ディレクトリは `tmp_path` を使う。 @@ -348,5 +360,5 @@ graph LR | 項目 | 内容 | | --- | --- | | `--profile '*'` が使える最古の版 | 公式ドキュメント([profiles](https://docs.docker.com/compose/how-tos/profiles/))に版の記載が無く、確かめられなかった。最低対応版は `depends_on.required` の 2.20.0 を根拠に定めた | -| 古い Compose での `--profile '*'` | 手元で確かめたのは v5.1.4 のみ。ワイルドカードを解釈しない版での挙動(対象が現在と同じに留まる想定)は未確認 | -| プロファイルが複数同時に有効な場合 | `DEVBASE_ACTIVE_PROFILES` はカンマ区切りを許すが、同時に 2 つ以上を起動する操作は今回作らない。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | +| 古い Compose での `--profile '*'` | 確認済みは v5.1.4 のみ。2.20.0 以上 5.x 未満は未検証。ワイルドカードを解釈しない版で `*` がリテラルのプロファイル名として扱われるか(対象が現在と同じに留まる想定)は未確認 | +| プロファイルが複数同時に有効な場合 | 同時に 2 つ以上を起動する操作は今回作らない。よって `DEVBASE_ACTIVE_PROFILES` は常に単一値で、カンマ区切りは将来の拡張のための予約である。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index decf4e36..4512ea13 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -81,7 +81,8 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 フック: - [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` から呼ばれるときは、どちらも空である -- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る(複数あればカンマ区切り) +- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値は常にプロファイル名 1 つである +- [ ] 同時に 2 つ以上のプロファイルを起動する操作は今回作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この範囲では受け入れ条件にしない - [ ] `devbase container profile up X` は `./pre-up` を呼ばない - [ ] `devbase container profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる @@ -104,7 +105,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | --- | | `--no-deps` | 2 系全般 | `docker compose up` の古くからある選択肢。版の下限を作らない | | `depends_on.required` | 2.20.0 以上 | [公式仕様](https://docs.docker.com/reference/compose-file/services/#depends_on)に「Introduced in Docker Compose version 2.20.0」とある | -| `--profile '*'` | 未確認 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | +| `--profile '*'` | v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | 既定のサービスを対象から外すのは `--no-deps` である。この選択肢は版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 @@ -114,7 +115,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | -| 既存の振る舞い | `down` が全プロファイルを対象にする。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも観測できる違いを生まない | +| 既存の振る舞い | `down` が全プロファイルを対象にする。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | ## 検証手段 @@ -123,6 +124,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | | 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | +| 手動確認(退行) | 確認済みの Docker Compose v5.1.4 で実施する。`profiles:` を持たないプロジェクトで `devbase up` と `devbase down` を通し、従来どおり動くことを確かめる。起動するコンテナの集合と順序、`down` 後に何も残らないことを見る。`--profile '*'` がこの経路に入るためである | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From b96b5fe4f3c33b179fcb48bb270b44be8073e2d6 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:19:00 +0900 Subject: [PATCH 06/22] =?UTF-8?q?docs(PLAN58):=20=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E3=82=92=20stop=20=E3=81=A8=20rm=20-f=20=E3=81=AE=202=20?= =?UTF-8?q?=E6=AE=B5=E3=81=B8=E6=94=B9=E3=82=81=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `down <サービス...>` は 2 つの問題を持つ。`[SERVICES]` 位置引数は比較的 新しい追加で、最低対応版 2.20.0 が受け付ける保証が無い。受け付けない版では `down` がプロジェクト全体を落とす。加えて Compose の対象選択は指定した サービスの祖先も含めるため、dev が db へ `depends_on` を持つ構成では `down db` が dev も消す。どちらも受け入れ条件を破る。 そこで `cmd_profile_down` は `--profile X stop <サービス...>` と `--profile X rm -f <サービス...>` の 2 段で行う。サービス指定が古くから 安定しており、対象が依存元へ広がらない。最低対応版の根拠も `depends_on.required` の 2.20.0 だけで閉じる。 - 決定 5 をこの方式の決定として書き直し、猶予は既定の 10 秒、 `rm` は `-v` を付けないことを明記した - 構造の表、入出力の契約、処理の流れ(図とコマンド列の表)、 非機能の実現方式を同じ方式へ揃えた - 使用機能の表へ `stop` / `rm -f` を足し、`down [SERVICES]` を 使わないことと、下限が `depends_on.required` だけで閉じることを書いた - 依存の向きが逆の構成で dev が止まらないことの確かめ方を、 受け入れ条件・検証手段・テスト設計へ足した Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 74 +++++++++++++++++++----- issues/PLAN58_compose-profiles.md | 9 ++- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 767cd5eb..4031e6f7 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -18,7 +18,7 @@ | --- | --- | | プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付け、停止には全プロファイルを指定する | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | @@ -76,7 +76,7 @@ graph TD P -.->|触らない| D1 ``` -境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付けて渡す。そのため dev-1..N は操作の対象に入らない。 +境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名をすべて明示して渡す。起動はさらに `--no-deps` を付ける。停止は対象を広げない `stop` と `rm -f` を使う(決定 5)。そのため dev-1..N はどちらの操作の対象にも入らない。 ## 置き場所 @@ -107,7 +107,7 @@ tests/ | --- | --- | --- | | `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | -| `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、自分で `down` のコマンド列を組む(決定 5)。終了コードを返す | +| `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | | `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | @@ -138,7 +138,7 @@ tests/ | 名前 | 入力 | 出力(成功) | 失敗の形 | | --- | --- | --- | --- | | `devbase project profile up [name] ` | プロファイル名(必須)、プロジェクト名(省略時は現在地) | 対象サービスを起動し、フックを実行して 0 | 構成ファイルが無い / 未知のプロファイル / Compose かフックが失敗 → 1 | -| `devbase project profile down [name] ` | 同上 | 対象サービスのコンテナを削除して 0 | 同上(フックは呼ばない) | +| `devbase project profile down [name] ` | 同上 | 対象サービスを停止し、そのコンテナを削除して 0 | 同上。`stop` と `rm -f` のどちらかが失敗すれば 1(フックは呼ばない) | | `devbase project profile list [name]` | プロジェクト名(省略可) | プロファイルと稼働状況を表で出して 0 | 構成ファイルが無い → 1 | | `devbase container profile up ` / `down ` / `list`(`ct` も同じ) | プロファイル名のみ。プロジェクト名は受け付けない | 同上。非推奨の警告を 1 行出す | 同上 | @@ -219,9 +219,37 @@ sequenceDiagram end ``` -停止(F2)は同じ並びから、フックの呼び出しを除いたものである。渡すのは `up -d` ではなく `down <サービス...>` である。`docker_compose_down` は通らない。理由は決定 5 に書く。 +停止(F2)は同じ並びから、フックの呼び出しを除いたものである。Compose を呼ぶ回数だけが 2 回になる。 -`--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。起動は `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]`、停止は `['--profile', X, 'down', <サービス...>]` になる。`<サービス...>` はプロファイル X に属するサービスの全件である(決定 2)。 +```mermaid +sequenceDiagram + participant U as 利用者 + participant OP as プロファイルの操作 + participant RS as プロファイルの解決 + participant DC as Compose の呼び出し + U->>OP: cmd_profile_down("local-app") + OP->>RS: 生成済みの構成を読む + RS-->>OP: サービス名の集合 + OP->>DC: compose --profile local-app stop <サービス...> + DC-->>OP: 終了コード + alt stop が失敗 + OP-->>U: 1(rm は呼ばない) + else + OP->>DC: compose --profile local-app rm -f <サービス...> + DC-->>OP: 終了コード + OP-->>U: 0 または 1 + end +``` + +`down` は使わない。`docker_compose_down` も通らない。理由は決定 5 に書く。 + +`--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。組み立てるコマンド列は次のとおりである。`<サービス...>` はどれもプロファイル X に属するサービスの全件である(決定 2)。 + +| 操作 | コマンド列 | +| --- | --- | +| 起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | +| 停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | +| 停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | `up` と `down` の冒頭の停止(F4)は次のように変わる。 @@ -239,9 +267,9 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は停止にだけ使う。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証) | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証) | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | -既定のサービスを対象から外すのは `--no-deps` である。この選択肢は古くからあり、版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 +起動で既定のサービスを対象から外すのは `--no-deps` である。停止で対象から外すのは、サービスを明示した `stop` / `rm -f` である。どれも古くからある形で、版の下限を作らない。下限を決めるのは `depends_on.required` だけである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 | 版 | 扱い | | --- | --- | @@ -303,18 +331,31 @@ graph LR `up` の冒頭の停止を dev-1..N だけに絞る案は採らない(利用者の指示、2026-09-16)。テストを続けたい場合は `up` の後にもう一度プロファイルを起動する。 -### 決定 5: プロファイルの停止に `-t0` を使わない +### 決定 5: プロファイルの停止は `down` を使わず、`stop` と `rm -f` の 2 段で行う + +`cmd_profile_down` はまず `docker_compose(['--profile', X, 'stop', <サービス...>])` を呼ぶ。続けて `docker_compose(['--profile', X, 'rm', '-f', <サービス...>])` を呼ぶ。`<サービス...>` はプロファイル X の全件である(決定 2)。1 段目が失敗したら 2 段目は呼ばず、1 を返す。 -プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行う。こちらは既定の猶予(10 秒)で落とす。 +`down <サービス...>` を渡す案は採らない。理由は 2 つある。 -そのため `cmd_profile_down` は既存の `docker_compose_down` を通らない。この関数は `['down', '-t0']` を固定で組み立て、サービス名の引数も受け取らないためである。`cmd_profile_down` は `docker_compose(['--profile', X, 'down', <サービス...>])` を直接呼ぶ。`-t0` は付けない。 +| 理由 | 中身 | +| --- | --- | +| 対象が広がらない | `stop` / `rm` のサービス指定は古くから安定しており、依存元をたどって対象を広げない | +| 版の下限を作らない | `down [SERVICES]` の対応版を調べる必要がなくなり、最低対応版の根拠が `depends_on.required` の 2.20.0 だけで閉じる | + +**`down <サービス...>` は依存元も削除する。** Compose の対象選択は、指定したサービスの祖先も含める。祖先とは、そのサービスへ `depends_on` を持つ側である。根拠は [v5.1.4 の実装](https://github.com/docker/compose/blob/v5.1.4/pkg/compose/dependencies.go#L104-L129)である。dev が `depends_on: {db: {condition: service_started, required: false}}` を持つ構成では、`down db` が dev も消す。受け入れ条件「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。`required: false` はこれを止めない(決定 2 と同じ理由)。 + +**`down [SERVICES]` は版の下限を左右する。** この位置引数は比較的新しい追加で、宣言している最低対応版 2.20.0 が受け付ける保証が無い。受け付けない版では、`down` がプロジェクト全体(dev を含む)を落とす。`stop` / `rm` へ寄せると、対応版を調べる必要そのものが消える。 + +**猶予は既定の 10 秒とする。** プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行うため、`stop` に `-t` を付けず、既定の猶予で落とす。 + +**ボリュームは消さない。** `rm` には `-f` だけを付け、`-v` は付けない。`-v` は匿名ボリュームを消す選択肢である。名前付きボリュームが残ることは受け入れ条件にある。`-f` は削除の確認を省くためだけに要る。 `docker_compose_down` に `services` と `timeout` の引数を足す案は採らない。この関数の呼び出し側は `devbase down` と `up` 冒頭の停止だけであり、どちらも全体を `-t0` で落とす。引数を増やすと、使われない組み合わせが関数の表に残る。変更は `--profile '*'` を足すことに留める(決定 4)。 -| 関数 | 用途 | タイムアウト | サービスの指定 | -| --- | --- | --- | --- | -| `docker_compose_down` | `devbase down` / `up` 冒頭の停止 | `-t0` | しない(全体) | -| `cmd_profile_down` | プロファイルの停止 | 既定(10 秒) | する | +| 関数 | 用途 | 使う subcommand | 猶予 | サービスの指定 | +| --- | --- | --- | --- | --- | +| `docker_compose_down` | `devbase down` / `up` 冒頭の停止 | `down` | `-t0` | しない(全体) | +| `cmd_profile_down` | プロファイルの停止 | `stop` → `rm -f` | 既定(10 秒) | する | ### 決定 6: プロジェクト名は位置引数で受け、`bin/devbase` は変えない @@ -339,7 +380,8 @@ graph LR | `depends_on: dev` を持つプロファイルサービスで dev が再作成されない | 手動確認。`depends_on: {dev: {condition: service_started, required: false}}` の構成と `depends_on: [dev]` の構成の両方で `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる | | dev の環境変数の値が変わっても dev が再作成されない | 手動確認。機密など dev の環境変数の値を変えてから `profile up X` を実行し、dev-1..N の Container ID と `StartedAt` を前後で比べる。`--no-deps` が無い組み立てでは再作成が起きることも確かめ、この選択肢が要ることを示す | | 生成物が `depends_on` の `required` を保つ | `generate_scaled_compose` の出力で、`depends_on: {dev: {condition: service_started, required: false}}` が `dev-1`..`dev-N` へ写り、各要素に `condition` と `required` が残ることを検査する | -| `profile down X` でコンテナが削除され、ボリュームは残る | 引数列に `down <対象サービス>` が含まれ、`--volumes` を含まないことを検査する | +| `profile down X` でコンテナが削除され、ボリュームは残る | 2 回の呼び出しが `--profile X stop <対象サービス...>` と `--profile X rm -f <対象サービス...>` であり、`down` も `-v` / `--volumes` も含まないことを検査する。`stop` が失敗したときに `rm` が呼ばれず 1 で終わることも見る | +| dev がプロファイルのサービスへ `depends_on` を持っても dev が止まらない | 手動確認。dev に `depends_on: {db: {condition: service_started, required: false}}` を書いた構成で `profile up X` → `profile down X` を通し、dev-1..N の Container ID と `StartedAt` を停止の前後で比べる。`down db` の組み立てでは dev が消えることも確かめ、`stop` / `rm` へ寄せる必要を示す(決定 5) | | `profile list` がプロファイル名と稼働状況を出す | 稼働中のサービスを返す偽の `docker compose ps` を与え、出力の行を検査する。全稼働・一部稼働・全停止の 3 通りで `3/3 running` / `1/3 partial` / `0/3 stopped` を確かめる | | `project profile list` が `project list` へ流れない | `profile_subcommand` の分離を、`devbase project profile list` の解析結果と呼ばれたハンドラで検査する | | 構成ファイルが無いときに 1 で止まる | 空の一時ディレクトリで呼び、終了コードと、コンテナを作る呼び出しが発生しないことを検査する | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 4512ea13..2883792b 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -67,6 +67,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `depends_on: {dev: {condition: service_started, required: false}}` を持つプロファイル X のサービスを `devbase container profile up X` で起動しても、既定のサービスの Container ID と `StartedAt` は変わらない - [ ] `depends_on: [dev]`(`required` を書かない形)を持つサービスでも、`devbase container profile up X` で既定のサービスの Container ID と `StartedAt` は変わらない - [ ] dev の環境変数の値を変えた後でも、`devbase container profile up X` は dev を再作成しない +- [ ] dev が `depends_on: {db: {condition: service_started, required: false}}` を持ち、db をプロファイル X に入れた構成でも、`devbase container profile down X` の前後で dev の Container ID と `StartedAt` が変わらない - [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る - [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない @@ -97,17 +98,20 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'` と `--no-deps` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | 最低対応版を 2.20.0 とする根拠は次のとおりである。 | 使う機能 | 使える版 | 根拠 | | --- | --- | --- | | `--no-deps` | 2 系全般 | `docker compose up` の古くからある選択肢。版の下限を作らない | +| `stop <サービス...>` / `rm -f <サービス...>` | 2 系全般 | どちらもサービス指定が古くから安定している。版の下限を作らない | | `depends_on.required` | 2.20.0 以上 | [公式仕様](https://docs.docker.com/reference/compose-file/services/#depends_on)に「Introduced in Docker Compose version 2.20.0」とある | | `--profile '*'` | v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | -既定のサービスを対象から外すのは `--no-deps` である。この選択肢は版の下限を作らない。下限を決めるのは `depends_on.required` のほうである。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 +`down` の `[SERVICES]` 位置引数は使わない。プロファイルの停止は `stop` と `rm -f` の 2 段で行う(設計の決定 5)。この位置引数は比較的新しい追加で、対応版を調べないと下限を決められない。加えて、指定したサービスへ `depends_on` を持つ側まで対象に含める。使わないことで、どちらの問題も起きない。 + +版の下限を作る機能は `depends_on.required` だけである。`--no-deps` も `stop` / `rm -f` も 2 系全般で使える。`--profile '*'` は停止の対象を広げる向きの指定であり、解釈しない版でも対象が現在と同じになる想定のため、下限を引き上げない(未検証。設計の「未確認のまま残ること」)。よって 2.20.0 という下限は `depends_on.required` だけで閉じる。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 ## 影響 @@ -124,6 +128,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | | 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | +| 手動確認(依存の向きが逆の構成) | dev へ `depends_on: {db: {condition: service_started, required: false}}` を書き、db をプロファイル X に入れた構成で `profile up X` → `profile down X` を通す。停止の前後で dev の Container ID と `StartedAt` が変わらないことを見る。`stop` / `rm -f` が依存元を対象に含めないことの確認である(設計の決定 5) | | 手動確認(退行) | 確認済みの Docker Compose v5.1.4 で実施する。`profiles:` を持たないプロジェクトで `devbase up` と `devbase down` を通し、従来どおり動くことを確かめる。起動するコンテナの集合と順序、`down` 後に何も残らないことを見る。`--profile '*'` がこの経路に入るためである | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From 1db179a34ec1b3273931145b11b7876a1f78db09 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:27:36 +0900 Subject: [PATCH 07/22] =?UTF-8?q?docs(PLAN58):=20=E6=9C=89=E5=8A=B9?= =?UTF-8?q?=E3=81=AA=E3=83=97=E3=83=AD=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=82=92=20devbase=20=E3=81=8C=E6=B1=BA=E3=82=81=20COMPOSE=5FP?= =?UTF-8?q?ROFILES=20=E3=82=92=E5=A4=96=E3=81=99=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COMPOSE_PROFILES を継承すると devbase up が既定外のサービスまで起動し、 「up 後は既定のサービスだけ」という受け入れ条件が崩れる。 - 決定 7 を追加し、経路ごとの --profile と環境変数の扱いを表で定める - docker_compose() を構造の変更対象へ追加 - 入出力の契約・処理の流れ・非機能の実現方式を同じ内容へ揃える - 要求仕様へ受け入れ条件と手動確認の行を追加 - テスト設計へ env から COMPOSE_PROFILES が外れることの検査を追加 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 75 +++++++++++++++++++----- issues/PLAN58_compose-profiles.md | 5 +- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 4031e6f7..985b1622 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -18,7 +18,7 @@ | --- | --- | | プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` は子プロセスの環境から外す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | @@ -76,7 +76,7 @@ graph TD P -.->|触らない| D1 ``` -境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。プロファイルの操作はサービス名をすべて明示して渡す。起動はさらに `--no-deps` を付ける。停止は対象を広げない `stop` と `rm -f` を使う(決定 5)。そのため dev-1..N はどちらの操作の対象にも入らない。 +境界をまたぐのは 2 つである。Compose へ渡すコマンド列と、`_inject_secrets` がプロセスの環境へ載せた機密である。この環境からは `COMPOSE_PROFILES` を取り除く。有効なプロファイルを決めるのは devbase の `--profile` だけにするためである(決定 7)。プロファイルの操作はサービス名をすべて明示して渡す。起動はさらに `--no-deps` を付ける。停止は対象を広げない `stop` と `rm -f` を使う(決定 5)。そのため dev-1..N はどちらの操作の対象にも入らない。 ## 置き場所 @@ -88,13 +88,15 @@ lib/devbase/ ├── project/ │ └── runtime.py # hook_env に有効なプロファイルを足す(変更) ├── utils/ -│ └── docker.py # docker_compose_down を全プロファイル対応へ(変更) +│ └── docker.py # docker_compose が COMPOSE_PROFILES を外す、docker_compose_down を全プロファイル対応へ(変更) └── volume/ └── compose.py # profiles を保つことの確認のみ(変更なし) tests/ ├── commands/ │ └── test_container_profile.py # 新設 +├── utils/ +│ └── test_docker_profiles.py # 新設(子プロセスの env から COMPOSE_PROFILES が外れることの検査) └── volume/ └── test_compose_profiles.py # 新設 ``` @@ -109,6 +111,7 @@ tests/ | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | +| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。子プロセスの環境を組み立てる箇所で `COMPOSE_PROFILES` を取り除いてから `subprocess.run` を呼ぶ(決定 7)。コマンド列の組み立て方は変えない | | `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | @@ -146,6 +149,18 @@ tests/ `container` / `ct` は `[name]` を持たない。値は常にプロファイル名である。`container` 群のサブコマンドは現在地のプロジェクトで動く既存の規約に従う。この非対称は `project` / `container` の既存の作りと同じである。 +### プロファイルの決め方 + +有効なプロファイルは devbase が経路ごとに明示して決める。利用者の環境や `.env` には従わない(決定 7)。 + +| 経路 | `--profile` | `COMPOSE_PROFILES` | +| --- | --- | --- | +| `devbase up` の起動 | 付けない | 子プロセスの環境から外す | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 同上 | +| `profile up X` / `profile down X` | `--profile X` | 同上 | + +`COMPOSE_PROFILES` は空文字列にするのではなく、渡さない。空文字列を渡す版の扱いを調べずに済むためである。 + ### `profile list` の表 | 列 | 内容 | @@ -171,6 +186,7 @@ tests/ | `profile` サブコマンドの追加 | 無い。既存の引数の形を変えない | | `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じになる。Docker Compose v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | | `DEVBASE_ACTIVE_PROFILES` の追加 | 無い。既存のフックは読まなければ従来どおり動く | +| `COMPOSE_PROFILES` を子プロセスへ渡さない | devbase 経由の Compose だけが対象。素の `docker compose` を手で叩く経路には影響しない | `--profile '*'` の行だけは、影響の範囲が広い。この経路は `devbase down` と `devbase up` 冒頭の停止に入るため、プロファイルを使わない既存の全プロジェクトを通る。だから「退行しないこと」の受け入れ条件に直結する。 @@ -187,7 +203,7 @@ tests/ ### 検査の手段 -`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。 +`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境から `COMPOSE_PROFILES` が外れていることを検査する。 ## 処理の流れ @@ -245,29 +261,33 @@ sequenceDiagram `--profile` は subcommand より前に置く必要がある。`docker_compose` は `-f` の後に、渡された配列をそのまま並べる。そのため `--profile X` を配列の先頭へ入れる。組み立てるコマンド列は次のとおりである。`<サービス...>` はどれもプロファイル X に属するサービスの全件である(決定 2)。 -| 操作 | コマンド列 | -| --- | --- | -| 起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | -| 停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | -| 停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | +| 操作 | コマンド列 | 子プロセスの `COMPOSE_PROFILES` | +| --- | --- | --- | +| プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 外す | +| プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 外す | +| プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 外す | +| `devbase up` の起動 | `['up', '-d', ...]`(`--profile` を付けない) | 外す | +| `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 外す | -`up` と `down` の冒頭の停止(F4)は次のように変わる。 +`up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去はどの経路にも共通で効く(決定 7)。 ```mermaid graph LR - A[devbase down] --> B[compose --profile '*' down -t0] - C[devbase up] --> D[compose --profile '*' down -t0] - D --> E[compose up -d] + A[devbase down] --> B["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] + C[devbase up] --> D["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] + D --> E["env から COMPOSE_PROFILES を外す → compose up -d、--profile なし"] E --> F[既定のサービスだけが動く] ``` +この除去が無いと、`COMPOSE_PROFILES=test` を持つ環境で `devbase up` が test のサービスまで起動する。`up` 冒頭の停止は `--profile '*'` で全部を落とすため、残骸ではなく新しい起動として現れる。 + ## 非機能の実現方式 | 大項目 | 要求の条件 | 実現方式 | 確かめ方 | | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証) | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` を子プロセスの環境から外し、有効なプロファイルを devbase が決める(決定 7)。この除去は版に依らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を設定した環境で `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | 起動で既定のサービスを対象から外すのは `--no-deps` である。停止で対象から外すのは、サービスを明示した `stop` / `rm -f` である。どれも古くからある形で、版の下限を作らない。下限を決めるのは `depends_on.required` だけである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 @@ -369,6 +389,30 @@ graph LR | `devbase container profile up ` | 受けない | しない(現在地で動く) | | `devbase ct profile up ` | 受けない | しない(現在地で動く) | +### 決定 7: 有効なプロファイルは devbase が決め、`COMPOSE_PROFILES` は子プロセスへ渡さない + +`docker_compose` は現在のプロセスの環境をそのまま子へ継承する(`lib/devbase/utils/docker.py`)。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 + +そこで `docker_compose` が子プロセスの環境を組み立てる箇所で `COMPOSE_PROFILES` を取り除く。有効なプロファイルは経路ごとに `--profile` で明示する。 + +| 経路 | プロファイルの指定 | `COMPOSE_PROFILES` | +| --- | --- | --- | +| `devbase up` の起動 | 付けない(既定のサービスだけ) | 外す | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 外す | +| `profile up X` / `profile down X` | `--profile X` | 外す | + +**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 + +**`.env` は書き換えない。** Compose はプロジェクトの `.env` を自動で読み、そこに書かれた `COMPOSE_PROFILES` も効く。ただし `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。`--profile` の明示と環境変数の除去なら、影響は devbase 経由の呼び出しだけに閉じる。 + +| 案 | 効く範囲 | 判定 | +| --- | --- | --- | +| `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | +| 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | +| 環境変数を外し、`--profile` で明示する | devbase 経由のみ | 可 | + +`--profile` を明示する経路では、環境変数を外しても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 + ## テスト設計 | 受け入れ条件 | 何で確かめるか | @@ -388,7 +432,8 @@ graph LR | 未知のプロファイル名で 1 で止まる | 既知の名前が出力に並ぶことと終了コードを検査する | | `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | | `devbase down` が全プロファイルを消し、0 で終わる | 引数列に `--profile *` が含まれることを検査する。全体の削除は手動確認 | -| `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることを検査する。実際の状態は手動確認 | +| `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることと、起動の引数列に `--profile` が入らないことを検査する。実際の状態は手動確認 | +| `COMPOSE_PROFILES` が設定された環境でも `devbase up` は既定のサービスだけを起動する | `COMPOSE_PROFILES=test` を `monkeypatch.setenv` で置き、`docker_compose` が組み立てた `env` にこのキーが無いことを検査する。`up` の起動・`down`・`profile up` / `down` の各経路で見る。実際の状態は手動確認 | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | プロファイルを持たないプロジェクトの挙動が変わらない | 既存の `tests/commands/test_container_up_order.py` と `tests/cli/test_up_roundtrips.py` が通ること | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 2883792b..7396e97a 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -78,6 +78,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] プロファイル X のサービスが起動している状態で `devbase down` を実行すると、既定のサービスとプロファイル X のサービスの両方が削除され、終了コード 0 で終わる - [ ] 同じ状態で `devbase up` を実行すると、冒頭の停止でプロファイル X のサービスも止まり、起動後は既定のサービスだけが動いている +- [ ] `COMPOSE_PROFILES` が設定された環境でも `devbase up` は既定のサービスだけを起動する。環境変数でも `.env` でも同じである(設計の決定 7) フック: @@ -99,6 +100,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | | システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | +| 再現性 | 有効なプロファイルは devbase が `--profile` で決める。利用者の `COMPOSE_PROFILES` に結果が左右されない(設計の決定 7) | 最低対応版を 2.20.0 とする根拠は次のとおりである。 @@ -119,7 +121,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | -| 既存の振る舞い | `down` が全プロファイルを対象にする。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | +| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ `COMPOSE_PROFILES` を渡さなくなる。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | ## 検証手段 @@ -129,6 +131,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | | 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | | 手動確認(依存の向きが逆の構成) | dev へ `depends_on: {db: {condition: service_started, required: false}}` を書き、db をプロファイル X に入れた構成で `profile up X` → `profile down X` を通す。停止の前後で dev の Container ID と `StartedAt` が変わらないことを見る。`stop` / `rm -f` が依存元を対象に含めないことの確認である(設計の決定 5) | +| 手動確認(`COMPOSE_PROFILES` が有効な環境) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | | 手動確認(退行) | 確認済みの Docker Compose v5.1.4 で実施する。`profiles:` を持たないプロジェクトで `devbase up` と `devbase down` を通し、従来どおり動くことを確かめる。起動するコンテナの集合と順序、`down` 後に何も残らないことを見る。`--profile '*'` がこの経路に入るためである | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From 84a6329014b533ae74a8d234bdcb51d69517b947 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:41:22 +0900 Subject: [PATCH 08/22] =?UTF-8?q?docs(PLAN58):=20up=20=E3=81=AE=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E3=81=A7=E6=97=A2=E5=AE=9A=E3=81=AE=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=93=E3=82=B9=E3=82=92=E6=98=8E=E7=A4=BA=E3=81=97=E3=80=81?= =?UTF-8?q?COMPOSE=5FPROFILES=20=E3=81=AE=E6=89=B1=E3=81=84=E3=82=92?= =?UTF-8?q?=E6=AD=A3=E7=A2=BA=E3=81=AB=E3=81=99=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 環境変数を外すだけでは、プロジェクトの .env に書かれた COMPOSE_PROFILES を Compose が直接読む経路が残る。up の起動では既定のサービス名をすべて明示する。 docker_compose は env= を持たないため、新たに組み立てて渡すことも書き足した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 37 ++++++++++++++++-------- issues/PLAN58_compose-profiles.md | 3 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 985b1622..b6620e95 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -112,6 +112,9 @@ tests/ | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | | `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。子プロセスの環境を組み立てる箇所で `COMPOSE_PROFILES` を取り除いてから `subprocess.run` を呼ぶ(決定 7)。コマンド列の組み立て方は変えない | +| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | `env=` を組み立て、`COMPOSE_PROFILES` を除いて子プロセスへ渡す | +| `docker_compose_up(compose_file, services=(), ...)` | 変更(`utils/docker.py`) | 既定のサービス名を受け取り、そのまま `up -d <サービス...>` へ渡す | +| `_compose_run(subcommand, ...)` | 変更(`commands/container.py`) | 直接 `subprocess.run` する経路。同じく `COMPOSE_PROFILES` を除く | | `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | @@ -153,14 +156,16 @@ tests/ 有効なプロファイルは devbase が経路ごとに明示して決める。利用者の環境や `.env` には従わない(決定 7)。 -| 経路 | `--profile` | `COMPOSE_PROFILES` | -| --- | --- | --- | -| `devbase up` の起動 | 付けない | 子プロセスの環境から外す | -| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 同上 | -| `profile up X` / `profile down X` | `--profile X` | 同上 | +| 経路 | `--profile` | 対象の渡し方 | `COMPOSE_PROFILES` | +| --- | --- | --- | --- | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 子プロセスの環境から外す | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない | 同上 | +| `profile up X` / `profile down X` | `--profile X` | 対象のサービスをすべて明示する | 同上 | `COMPOSE_PROFILES` は空文字列にするのではなく、渡さない。空文字列を渡す版の扱いを調べずに済むためである。 +**環境変数を外すだけでは足りない。** Compose はプロジェクトの `.env` を自分で読むため、`devbase up` の起動では既定のサービス名も明示する(決定 7)。 + ### `profile list` の表 | 列 | 内容 | @@ -393,13 +398,20 @@ graph LR `docker_compose` は現在のプロセスの環境をそのまま子へ継承する(`lib/devbase/utils/docker.py`)。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 -そこで `docker_compose` が子プロセスの環境を組み立てる箇所で `COMPOSE_PROFILES` を取り除く。有効なプロファイルは経路ごとに `--profile` で明示する。 +そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** -| 経路 | プロファイルの指定 | `COMPOSE_PROFILES` | -| --- | --- | --- | -| `devbase up` の起動 | 付けない(既定のサービスだけ) | 外す | -| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 外す | -| `profile up X` / `profile down X` | `--profile X` | 外す | +`docker_compose` は現在 `subprocess.run(cmd, ...)` を `env=` なしで呼び、暗黙に `os.environ` を継承している。ここで `env=` を新たに構築し、`COMPOSE_PROFILES` を除いたうえで渡す。`_compose_run`(`lib/devbase/commands/container.py`)は `docker_compose` を経由せず直接 `subprocess.run` する経路だが、こちらも同じ扱いにする。`ps` / `logs` は起動しない読み取りの操作である。ただし `COMPOSE_PROFILES` が効くと一覧の中身が変わり、`profile list` の稼働状況の判定が狂う。 + +環境変数を外しても、プロジェクトの `.env` に書かれた `COMPOSE_PROFILES` は Compose が直接読む。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 + +| 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | +| --- | --- | --- | --- | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 外す | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 外す | +| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 外す | +| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 外す | + +**この方式には限界がある。** 対象を明示するため、生成物に無いサービスは `up` で起動しない。`docker compose run` で後から足したコンテナも対象外である。 **空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 @@ -433,7 +445,8 @@ graph LR | `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | | `devbase down` が全プロファイルを消し、0 で終わる | 引数列に `--profile *` が含まれることを検査する。全体の削除は手動確認 | | `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることと、起動の引数列に `--profile` が入らないことを検査する。実際の状態は手動確認 | -| `COMPOSE_PROFILES` が設定された環境でも `devbase up` は既定のサービスだけを起動する | `COMPOSE_PROFILES=test` を `monkeypatch.setenv` で置き、`docker_compose` が組み立てた `env` にこのキーが無いことを検査する。`up` の起動・`down`・`profile up` / `down` の各経路で見る。実際の状態は手動確認 | +| `COMPOSE_PROFILES` が環境変数に設定されていても `devbase up` は既定のサービスだけを起動する | `COMPOSE_PROFILES=test` を `monkeypatch.setenv` で置き、`docker_compose` と `_compose_run` が組み立てた `env` にこのキーが無いことを検査する。`up` の起動・`down`・`profile up` / `down` の各経路で見る。実際の状態は手動確認 | +| `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 生成物から読んだ既定のサービス名が `up -d` の引数へ並ぶことを検査する。引数にプロファイルのサービスが入らないことも見る。実際の状態は手動確認 | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | プロファイルを持たないプロジェクトの挙動が変わらない | 既存の `tests/commands/test_container_up_order.py` と `tests/cli/test_up_roundtrips.py` が通ること | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 7396e97a..025a2170 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -78,7 +78,8 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] プロファイル X のサービスが起動している状態で `devbase down` を実行すると、既定のサービスとプロファイル X のサービスの両方が削除され、終了コード 0 で終わる - [ ] 同じ状態で `devbase up` を実行すると、冒頭の停止でプロファイル X のサービスも止まり、起動後は既定のサービスだけが動いている -- [ ] `COMPOSE_PROFILES` が設定された環境でも `devbase up` は既定のサービスだけを起動する。環境変数でも `.env` でも同じである(設計の決定 7) +- [ ] `COMPOSE_PROFILES` が環境変数に設定された状態でも、`devbase up` は既定のサービスだけを起動する +- [ ] プロジェクトの `.env` に `COMPOSE_PROFILES` が書かれた状態でも、`devbase up` は既定のサービスだけを起動する(起動の対象をサービス名で明示するため。設計の決定 7) フック: From f55f4ecb76975e35f0ac5a17cb5184f85dbd5492 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:46:39 +0900 Subject: [PATCH 09/22] =?UTF-8?q?docs(PLAN58):=20=E6=B1=BA=E5=AE=9A=207=20?= =?UTF-8?q?=E3=82=92=202=20=E6=AE=B5=E3=81=AE=E5=AF=BE=E7=AD=96=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E6=95=B4=E7=90=86=E3=81=97=E3=80=81compose?= =?UTF-8?q?=20=E3=82=92=E5=91=BC=E3=81=B6=E7=B5=8C=E8=B7=AF=E3=82=92?= =?UTF-8?q?=E6=A3=9A=E5=8D=B8=E3=81=97=E3=81=99=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 環境変数の除去と、起動の対象の明示を 2 段の対策として並べ直した。docker compose を 呼ぶ 3 つの経路の扱いを表にし、cmd_scale の直接呼び出しを範囲外として明示した。 構造の表にあった docker_compose の重複行も解消した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 58 ++++++++++++++++-------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index b6620e95..c2cb7cb3 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -16,9 +16,9 @@ | 要素 | 責務 | | --- | --- | -| プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。稼働状況は持たない | +| プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。`profiles` を持たない既定のサービスの一覧も同じ場所から求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` は子プロセスの環境から外す(決定 7) | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` は子プロセスの環境から外す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | @@ -108,13 +108,14 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | | `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | +| `default_services(compose: dict) -> list[str]` | 新設(`commands/container.py`) | 構成の辞書から `profiles` を持たないサービス名を宣言順に返す。`cmd_up` が起動の対象として渡す(決定 7)。純粋な処理である | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | -| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。子プロセスの環境を組み立てる箇所で `COMPOSE_PROFILES` を取り除いてから `subprocess.run` を呼ぶ(決定 7)。コマンド列の組み立て方は変えない | -| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | `env=` を組み立て、`COMPOSE_PROFILES` を除いて子プロセスへ渡す | -| `docker_compose_up(compose_file, services=(), ...)` | 変更(`utils/docker.py`) | 既定のサービス名を受け取り、そのまま `up -d <サービス...>` へ渡す | -| `_compose_run(subcommand, ...)` | 変更(`commands/container.py`) | 直接 `subprocess.run` する経路。同じく `COMPOSE_PROFILES` を除く | +| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` を除いたうえで渡す(決定 7)。コマンド列の組み立て方は変えない | +| `docker_compose_up(compose_file, detach=True, services=())` | 変更(`utils/docker.py`) | 起動の対象を受け取り、`['up', '-d', *services]` を組む。空なら現在と同じ `['up', '-d']` になる | +| `_compose_run(subcommand, ...)` | 変更(`commands/container.py:269`) | `docker_compose` を経由せず直接 `subprocess.run` する経路(`ps` / `logs`)。同じ `env=` を組み立てて渡す(決定 7) | +| `_run_deploy_pipeline(...)` | 変更(`commands/container.py`) | 生成した `.docker-compose.scale.yml` から `default_services` を求め、`docker_compose_up` へ渡す | | `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | @@ -192,6 +193,7 @@ tests/ | `docker compose down` に `--profile '*'` を付ける | プロファイルを持たないプロジェクトでは対象が同じになる。Docker Compose v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | | `DEVBASE_ACTIVE_PROFILES` の追加 | 無い。既存のフックは読まなければ従来どおり動く | | `COMPOSE_PROFILES` を子プロセスへ渡さない | devbase 経由の Compose だけが対象。素の `docker compose` を手で叩く経路には影響しない | +| `devbase up` の起動に既定のサービス名を明示する | 対象は現在と同じ(`profiles` を持たないサービスの全件)。生成物は `up` のたびに作り直すため、一覧が古くなることはない | `--profile '*'` の行だけは、影響の範囲が広い。この経路は `devbase down` と `devbase up` 冒頭の停止に入るため、プロファイルを使わない既存の全プロジェクトを通る。だから「退行しないこと」の受け入れ条件に直結する。 @@ -208,7 +210,7 @@ tests/ ### 検査の手段 -`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境から `COMPOSE_PROFILES` が外れていることを検査する。 +`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境から `COMPOSE_PROFILES` が外れていることを検査する。同じテストで、`devbase up` の起動が既定のサービス名をすべて渡すことも検査する。 ## 処理の流れ @@ -271,20 +273,22 @@ sequenceDiagram | プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 外す | | プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 外す | | プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 外す | -| `devbase up` の起動 | `['up', '-d', ...]`(`--profile` を付けない) | 外す | +| `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 外す | | `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 外す | -`up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去はどの経路にも共通で効く(決定 7)。 +`devbase up` の `<既定のサービス...>` は、生成物のうち `profiles` を持たないサービスの全件である(決定 7)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 + +`up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去は `docker compose` を呼ぶどの経路にも共通で効く(決定 7)。 ```mermaid graph LR A[devbase down] --> B["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] C[devbase up] --> D["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] - D --> E["env から COMPOSE_PROFILES を外す → compose up -d、--profile なし"] + D --> E["env から COMPOSE_PROFILES を外す → compose up -d 既定のサービス一覧、--profile なし"] E --> F[既定のサービスだけが動く] ``` -この除去が無いと、`COMPOSE_PROFILES=test` を持つ環境で `devbase up` が test のサービスまで起動する。`up` 冒頭の停止は `--profile '*'` で全部を落とすため、残骸ではなく新しい起動として現れる。 +この除去が無いと、`COMPOSE_PROFILES=test` を持つ環境で `devbase up` が test のサービスまで起動する。`up` 冒頭の停止は `--profile '*'` で全部を落とすため、残骸ではなく新しい起動として現れる。`.env` に書かれた値には除去が効かない。そちらは既定のサービスの明示で防ぐ(決定 7)。 ## 非機能の実現方式 @@ -292,7 +296,7 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` を子プロセスの環境から外し、有効なプロファイルを devbase が決める(決定 7)。この除去は版に依らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を設定した環境で `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` を子プロセスの環境から外し、`devbase up` の起動では既定のサービスを明示する(決定 7)。サービスを明示した `up` も 2 系全般で使えるため、どちらも版の下限を作らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を環境変数と `.env` の両方に置いて `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | 起動で既定のサービスを対象から外すのは `--no-deps` である。停止で対象から外すのは、サービスを明示した `stop` / `rm -f` である。どれも古くからある形で、版の下限を作らない。下限を決めるのは `depends_on.required` だけである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 @@ -394,15 +398,17 @@ graph LR | `devbase container profile up ` | 受けない | しない(現在地で動く) | | `devbase ct profile up ` | 受けない | しない(現在地で動く) | -### 決定 7: 有効なプロファイルは devbase が決め、`COMPOSE_PROFILES` は子プロセスへ渡さない +### 決定 7: 有効なプロファイルは devbase が決め、起動の対象も明示する -`docker_compose` は現在のプロセスの環境をそのまま子へ継承する(`lib/devbase/utils/docker.py`)。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 +`docker_compose` は現在 `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ(`lib/devbase/utils/docker.py:14-55`)。子プロセスは `os.environ` を暗黙に継承する。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** -`docker_compose` は現在 `subprocess.run(cmd, ...)` を `env=` なしで呼び、暗黙に `os.environ` を継承している。ここで `env=` を新たに構築し、`COMPOSE_PROFILES` を除いたうえで渡す。`_compose_run`(`lib/devbase/commands/container.py`)は `docker_compose` を経由せず直接 `subprocess.run` する経路だが、こちらも同じ扱いにする。`ps` / `logs` は起動しない読み取りの操作である。ただし `COMPOSE_PROFILES` が効くと一覧の中身が変わり、`profile list` の稼働状況の判定が狂う。 +**1. 環境変数を外す。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製から `COMPOSE_PROFILES` を除き、それを `subprocess.run(..., env=env)` へ渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 + +**2. 起動の対象を明示する。** 1 だけでは足りない。Compose はプロジェクトの `.env` を自分で読むため、そこに書かれた `COMPOSE_PROFILES` は効いてしまう。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 -環境変数を外しても、プロジェクトの `.env` に書かれた `COMPOSE_PROFILES` は Compose が直接読む。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 +既定のサービスの一覧は `default_services` が `.docker-compose.scale.yml` から作る(決定 1)。`devbase up` は起動の直前に生成物を作り直すため、一覧は常に最新である。 | 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | @@ -411,17 +417,30 @@ graph LR | `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 外す | | `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 外す | -**この方式には限界がある。** 対象を明示するため、生成物に無いサービスは `up` で起動しない。`docker compose run` で後から足したコンテナも対象外である。 +**この方式の前提と限界。** 前提は、生成物が `up` のたびに作り直されることである。限界は、対象を明示するため、生成物に無いサービスは `up` で起動しないことである。生成物には必ず `dev-1`..`dev-N` が入るため、一覧が空になることはない。 + +**停止には要らない。** 停止は `--profile '*'` で対象を広げる向きの指定である。`.env` が別のプロファイルを有効にしても、対象が狭まることはない。 + +**`docker compose` を呼ぶ経路の棚卸し。** 現在は 3 か所ある。扱いは次のとおりである。 + +| 経路 | 場所 | 扱い | +| --- | --- | --- | +| `docker_compose` | `lib/devbase/utils/docker.py:14` | 対象。`up` / `down` / `profile` の各操作はここを通る | +| `_compose_run`(`ps` / `logs`) | `lib/devbase/commands/container.py:269` | 対象。`docker_compose` を経由せず直接 `subprocess.run` するため、同じ `env=` を別に組み立てる | +| `cmd_scale` の直接呼び出し | `lib/devbase/commands/container.py:1282` | 範囲外。要求仕様の「対象範囲・含まない」に挙げる | + +`ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 **空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 -**`.env` は書き換えない。** Compose はプロジェクトの `.env` を自動で読み、そこに書かれた `COMPOSE_PROFILES` も効く。ただし `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。`--profile` の明示と環境変数の除去なら、影響は devbase 経由の呼び出しだけに閉じる。 +**`.env` は書き換えない。** `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。環境変数の除去と起動の対象の明示なら、影響は devbase 経由の呼び出しだけに閉じる。 | 案 | 効く範囲 | 判定 | | --- | --- | --- | | `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | | 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | -| 環境変数を外し、`--profile` で明示する | devbase 経由のみ | 可 | +| 環境変数を外すだけ | devbase 経由のみ。`.env` には効かない | 足りない。単独では受け入れ条件を満たせない | +| 環境変数を外し、起動の対象を既定のサービスへ限る | devbase 経由のみ | 可 | `--profile` を明示する経路では、環境変数を外しても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 @@ -461,4 +480,5 @@ graph LR | --- | --- | | `--profile '*'` が使える最古の版 | 公式ドキュメント([profiles](https://docs.docker.com/compose/how-tos/profiles/))に版の記載が無く、確かめられなかった。最低対応版は `depends_on.required` の 2.20.0 を根拠に定めた | | 古い Compose での `--profile '*'` | 確認済みは v5.1.4 のみ。2.20.0 以上 5.x 未満は未検証。ワイルドカードを解釈しない版で `*` がリテラルのプロファイル名として扱われるか(対象が現在と同じに留まる想定)は未確認 | +| 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成 | `.env` でそのプロファイルが有効になっていると、`up -d <既定のサービス...>` が依存先として起動しうる。`up` には `--no-deps` を付けないためである。この組み合わせは未検証である | | プロファイルが複数同時に有効な場合 | 同時に 2 つ以上を起動する操作は今回作らない。よって `DEVBASE_ACTIVE_PROFILES` は常に単一値で、カンマ区切りは将来の拡張のための予約である。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | From 78512c0cfcf41cd3e2818f9734280ce11b255e15 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:46:59 +0900 Subject: [PATCH 10/22] =?UTF-8?q?docs(PLAN58):=20=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=81=AE=E8=A1=A8=E3=81=B8=E6=98=8E=E7=A4=BA?= =?UTF-8?q?=E3=81=97=E3=81=9F=20up=20=E3=82=92=E8=B6=B3=E3=81=97=E3=80=81s?= =?UTF-8?q?cale=20=E3=81=AE=E7=9B=B4=E6=8E=A5=E5=91=BC=E3=81=B3=E5=87=BA?= =?UTF-8?q?=E3=81=97=E3=82=92=E7=AF=84=E5=9B=B2=E5=A4=96=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 025a2170..d2118b03 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -41,6 +41,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - プロファイル付きサービスの scale(インスタンスごとの複製) - `project.yml` で既定の有効プロファイルを宣言する仕組み(必要になってから別 issue で扱う) +- `devbase scale` が直接呼ぶ `compose up -d --no-recreate` の `COMPOSE_PROFILES` の扱い(プロファイルの入口ではない。必要になってから別 issue で扱う) - carmo-system-console 側の `compose.yml` / フックの書き換え(volareinc/devbase-ext#31) - 新しい型・クラスの追加。実装は既存のモジュール関数の並びに足す(そのためクラス図を作らない) - 永続データの追加・変更(そのため ER 図・テーブル定義・CRUD 図を作らない) @@ -80,6 +81,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] 同じ状態で `devbase up` を実行すると、冒頭の停止でプロファイル X のサービスも止まり、起動後は既定のサービスだけが動いている - [ ] `COMPOSE_PROFILES` が環境変数に設定された状態でも、`devbase up` は既定のサービスだけを起動する - [ ] プロジェクトの `.env` に `COMPOSE_PROFILES` が書かれた状態でも、`devbase up` は既定のサービスだけを起動する(起動の対象をサービス名で明示するため。設計の決定 7) +- [ ] `devbase up` の起動は、生成物の `profiles` を持たないサービスをすべてサービス名として Compose へ渡す フック: @@ -100,21 +102,22 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | -| 再現性 | 有効なプロファイルは devbase が `--profile` で決める。利用者の `COMPOSE_PROFILES` に結果が左右されない(設計の決定 7) | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | +| 再現性 | 有効なプロファイルは devbase が `--profile` で決め、`devbase up` の起動では対象のサービスも明示する。利用者の `COMPOSE_PROFILES` に結果が左右されない。環境変数でも `.env` でも同じである(設計の決定 7) | 最低対応版を 2.20.0 とする根拠は次のとおりである。 | 使う機能 | 使える版 | 根拠 | | --- | --- | --- | | `--no-deps` | 2 系全般 | `docker compose up` の古くからある選択肢。版の下限を作らない | +| `up <サービス...>` | 2 系全般 | `docker compose up` は古くからサービス名の位置引数を受ける。版の下限を作らない | | `stop <サービス...>` / `rm -f <サービス...>` | 2 系全般 | どちらもサービス指定が古くから安定している。版の下限を作らない | | `depends_on.required` | 2.20.0 以上 | [公式仕様](https://docs.docker.com/reference/compose-file/services/#depends_on)に「Introduced in Docker Compose version 2.20.0」とある | | `--profile '*'` | v5.1.4 で確認済み。2.20.0 以上 5.x 未満は未検証 | 公式ドキュメントに版の記載が無い(設計の「未確認のまま残ること」) | `down` の `[SERVICES]` 位置引数は使わない。プロファイルの停止は `stop` と `rm -f` の 2 段で行う(設計の決定 5)。この位置引数は比較的新しい追加で、対応版を調べないと下限を決められない。加えて、指定したサービスへ `depends_on` を持つ側まで対象に含める。使わないことで、どちらの問題も起きない。 -版の下限を作る機能は `depends_on.required` だけである。`--no-deps` も `stop` / `rm -f` も 2 系全般で使える。`--profile '*'` は停止の対象を広げる向きの指定であり、解釈しない版でも対象が現在と同じになる想定のため、下限を引き上げない(未検証。設計の「未確認のまま残ること」)。よって 2.20.0 という下限は `depends_on.required` だけで閉じる。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 +版の下限を作る機能は `depends_on.required` だけである。`--no-deps` も、サービスを明示した `up` / `stop` / `rm -f` も 2 系全般で使える。`--profile '*'` は停止の対象を広げる向きの指定であり、解釈しない版でも対象が現在と同じになる想定のため、下限を引き上げない(未検証。設計の「未確認のまま残ること」)。よって 2.20.0 という下限は `depends_on.required` だけで閉じる。2.20.0 未満の v2 では、`required: false` を書いた構成の検証に失敗する。受け入れ条件と文書の例はこの属性を使う。だから「機能は落ちるが壊れない」とは言わず、対象外と定める。動作を確かめたのは Docker Compose v5.1.4 である。 ## 影響 @@ -122,7 +125,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | --- | --- | | 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | -| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ `COMPOSE_PROFILES` を渡さなくなる。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | +| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ `COMPOSE_PROFILES` を渡さなくなる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | ## 検証手段 @@ -132,7 +135,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | | 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | | 手動確認(依存の向きが逆の構成) | dev へ `depends_on: {db: {condition: service_started, required: false}}` を書き、db をプロファイル X に入れた構成で `profile up X` → `profile down X` を通す。停止の前後で dev の Container ID と `StartedAt` が変わらないことを見る。`stop` / `rm -f` が依存元を対象に含めないことの確認である(設計の決定 5) | -| 手動確認(`COMPOSE_PROFILES` が有効な環境) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | +| 手動確認(`COMPOSE_PROFILES` が有効な環境) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。`.env` の側は、起動のコマンド列に既定のサービス名が並ぶことも見る(設計の決定 7)。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | | 手動確認(退行) | 確認済みの Docker Compose v5.1.4 で実施する。`profiles:` を持たないプロジェクトで `devbase up` と `devbase down` を通し、従来どおり動くことを確かめる。起動するコンテナの集合と順序、`down` 後に何も残らないことを見る。`--profile '*'` がこの経路に入るためである | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From 50827effcc3150c252b246393ca28f52c20f91fc Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 12:47:33 +0900 Subject: [PATCH 11/22] =?UTF-8?q?docs(PLAN58):=20=E7=BD=AE=E3=81=8D?= =?UTF-8?q?=E5=A0=B4=E6=89=80=E3=81=AE=E6=B3=A8=E8=A8=98=E3=82=92=E6=B1=BA?= =?UTF-8?q?=E5=AE=9A=207=20=E3=81=AE=E5=A4=89=E6=9B=B4=E7=82=B9=E3=81=B8?= =?UTF-8?q?=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker.py と container.py の変更点に、env= の組み立てと対象サービスの 受け渡し、default_services を書き足す。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index c2cb7cb3..87653e6e 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -84,11 +84,11 @@ graph TD lib/devbase/ ├── cli.py # profile サブコマンドの登録(変更) ├── commands/ -│ └── container.py # cmd_profile_up / down / list、dispatch(変更) +│ └── container.py # cmd_profile_up / down / list、default_services、_compose_run、dispatch(変更) ├── project/ │ └── runtime.py # hook_env に有効なプロファイルを足す(変更) ├── utils/ -│ └── docker.py # docker_compose が COMPOSE_PROFILES を外す、docker_compose_down を全プロファイル対応へ(変更) +│ └── docker.py # docker_compose が env= を組んで COMPOSE_PROFILES を外す、docker_compose_up が対象サービスを受ける、docker_compose_down を全プロファイル対応へ(変更) └── volume/ └── compose.py # profiles を保つことの確認のみ(変更なし) @@ -155,7 +155,7 @@ tests/ ### プロファイルの決め方 -有効なプロファイルは devbase が経路ごとに明示して決める。利用者の環境や `.env` には従わない(決定 7)。 +有効なプロファイルは devbase が経路ごとに明示して決める。利用者の環境や `.env` の値で、起動する対象が変わらないようにする(決定 7)。 | 経路 | `--profile` | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | @@ -278,7 +278,7 @@ sequenceDiagram `devbase up` の `<既定のサービス...>` は、生成物のうち `profiles` を持たないサービスの全件である(決定 7)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 -`up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去は `docker compose` を呼ぶどの経路にも共通で効く(決定 7)。 +`up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去は `docker_compose` と `_compose_run` の両方に共通で効く(決定 7)。 ```mermaid graph LR From 38f969b8c5b8208145d1d0d855e5190bcaaf55e8 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:03:07 +0900 Subject: [PATCH 12/22] =?UTF-8?q?docs(PLAN58):=20=E4=B8=80=E8=A6=A7?= =?UTF-8?q?=E3=81=AE=E6=93=8D=E4=BD=9C=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=81=8B=E3=82=89=E8=B5=B7=E5=8B=95=E3=83=BB=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=80=81=E6=B1=BA=E5=AE=9A=E3=82=92=E5=88=A5=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=B8=E5=88=86=E3=81=91=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devbase list の起動中の行へ「テスト用サーバ起動 / 停止」の 2 項目を足す設計を 加えた。項目はプロファイルを持つプロジェクトにだけ出す(決定 8)。 設計文書が 500 行を超えたため、決定の記録を別ファイルへ分けた。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 147 +++++++++++++++++ issues/PLAN58_compose-profiles-design.md | 166 ++++---------------- issues/PLAN58_compose-profiles.md | 38 +++-- 3 files changed, 206 insertions(+), 145 deletions(-) create mode 100644 issues/PLAN58_compose-profiles-decisions.md diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md new file mode 100644 index 00000000..59715b6a --- /dev/null +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -0,0 +1,147 @@ +# 189: 決定の記録 + +設計は [PLAN58_compose-profiles-design.md](PLAN58_compose-profiles-design.md) にある。このファイルは、その設計で選んだ結論と理由、採らなかった案だけを持つ。 + +### 決定 1: プロファイルの解決は生成済みの `.docker-compose.scale.yml` を読んで行う + +`devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 + +要求仕様が `compose.yml` と書く箇所との対応は次のとおりである。利用者がプロファイルを宣言するのは `compose.yml` であり、devbase が読むのはその宣言を引き継いだ生成物である。宣言の内容は同じなので、受け入れ条件の文言は変えない。 + +`docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 + +### 決定 2: プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付ける + +サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示する。 + +**サービス名の明示だけでは足りない。** `_build_scaled_services` は非 dev サービスにも `_rewrite_depends_on` を適用する(`lib/devbase/volume/compose.py:488`)。プロファイルのサービスが `depends_on: dev` を書いていると、生成物では `dev-1`..`dev-N` になる。Compose は依存先を解決して対象へ取り込む。受け入れ条件の「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。 + +**`required: false` はこれを止めない。** この属性が緩めるのは「依存先が不在のときのエラー」だけである。依存先を操作の対象から外す働きは持たない。Compose v5.1.4 の dry-run でも dev の起動が含まれる。依存先の構成が変わったときに再作成する実装のため、機密などの値が変わった状態では Container ID が変わりうる。 + +そこで `--no-deps` を使う。起動のコマンド列は `--profile X up -d --no-deps <対象サービス...>` になる。この選択肢は依存先を操作の対象から外す。 + +**`--no-deps` は依存先を自動起動しない。** そのため、プロファイルに属するサービスをすべて明示して渡すことが前提になる(要求仕様の前提 5)。1 つでも落とすと、そのサービスは起動しない。渡す集合は「プロファイルの解決」が生成物から求めるため、取りこぼしは起きない。 + +| 組み立て方 | `profile up X` が触るもの | 判定 | +| --- | --- | --- | +| `--profile X up -d`(サービス名なし) | 既定のサービスを含む全体 | 不可。dev を再作成しうる | +| `--profile X up -d <一部のサービス>` | 渡したサービスだけ | 不可。残りが起動しない | +| `--profile X up -d <対象サービス...>`(`--no-deps` なし) | そのサービスと依存先の dev-1..N | 不可。dev を再作成しうる | +| `--profile X up -d --no-deps <対象サービス...>` | そのサービスだけ | 可 | + +`depends_on` の書き方は前提にしない。`depends_on: {dev: {condition: service_started, required: false}}` でも `depends_on: [dev]` でも、`--no-deps` を付ければ dev は対象に入らない。 + +生成のときに devbase が `required: false` を補う案は採らない。依存が必須かどうかはプロジェクトが決める意図であり、生成物が黙って緩めると `devbase up` の起動順の意図が読めなくなる。対象から外す役目は `--no-deps` が担うため、補う必要もない。 + +### 決定 3: プロファイル起動の後は `./deploy` を呼び直す。新しいフックは作らない + +`./deploy` は既にインスタンスごとに呼ばれ、プロジェクト側で冪等に書かれている。プロファイルの有無は `DEVBASE_ACTIVE_PROFILES` で分岐できる。フック名を増やす理由がない。 + +`./post-profile-up` のような新しい名前を足す案は採らない。プロジェクトが持つ約束の数が増える。どちらに書くべきかの判断も各プロジェクトに生まれる。 + +`hook_env` は `_run_pre_up_hook` も呼んでいる。そのため `./pre-up` にも `DEVBASE_ACTIVE_PROFILES` が渡る。`./pre-up` を呼ぶのは `cmd_up` だけなので、値は常に空である。`profile up` は `./pre-up` を呼ばない。コンテナの起動前に済ませる準備は `up` の役目だからである。要求仕様が `./pre-up` を挙げるのは、この空の値を含めた約束のことである。 + +| フック | 呼ぶ経路 | `DEVBASE_ACTIVE_PROFILES` | +| --- | --- | --- | +| `./pre-up` | `cmd_up` のみ | 常に空 | +| `./deploy` | `cmd_up` | 空 | +| `./deploy` | `cmd_profile_up` | 起動したプロファイル名 1 つ | + +### 決定 4: 停止は全プロファイルを対象にし、`up` は既定の状態へ揃える + +`devbase up` は「その構成で開発環境を作り直す」操作である。プロファイルのサービスだけが前の状態のまま残ると、`up` の後の状態が直前の操作に依存する。 + +`up` の冒頭の停止を dev-1..N だけに絞る案は採らない(利用者の指示、2026-09-16)。テストを続けたい場合は `up` の後にもう一度プロファイルを起動する。 + +### 決定 5: プロファイルの停止は `down` を使わず、`stop` と `rm -f` の 2 段で行う + +`cmd_profile_down` はまず `docker_compose(['--profile', X, 'stop', <サービス...>])` を呼ぶ。続けて `docker_compose(['--profile', X, 'rm', '-f', <サービス...>])` を呼ぶ。`<サービス...>` はプロファイル X の全件である(決定 2)。1 段目が失敗したら 2 段目は呼ばず、1 を返す。 + +`down <サービス...>` を渡す案は採らない。理由は 2 つある。 + +| 理由 | 中身 | +| --- | --- | +| 対象が広がらない | `stop` / `rm` のサービス指定は古くから安定しており、依存元をたどって対象を広げない | +| 版の下限を作らない | `down [SERVICES]` の対応版を調べる必要がなくなり、最低対応版の根拠が `depends_on.required` の 2.20.0 だけで閉じる | + +**`down <サービス...>` は依存元も削除する。** Compose の対象選択は、指定したサービスの祖先も含める。祖先とは、そのサービスへ `depends_on` を持つ側である。根拠は [v5.1.4 の実装](https://github.com/docker/compose/blob/v5.1.4/pkg/compose/dependencies.go#L104-L129)である。dev が `depends_on: {db: {condition: service_started, required: false}}` を持つ構成では、`down db` が dev も消す。受け入れ条件「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。`required: false` はこれを止めない(決定 2 と同じ理由)。 + +**`down [SERVICES]` は版の下限を左右する。** この位置引数は比較的新しい追加で、宣言している最低対応版 2.20.0 が受け付ける保証が無い。受け付けない版では、`down` がプロジェクト全体(dev を含む)を落とす。`stop` / `rm` へ寄せると、対応版を調べる必要そのものが消える。 + +**猶予は既定の 10 秒とする。** プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行うため、`stop` に `-t` を付けず、既定の猶予で落とす。 + +**ボリュームは消さない。** `rm` には `-f` だけを付け、`-v` は付けない。`-v` は匿名ボリュームを消す選択肢である。名前付きボリュームが残ることは受け入れ条件にある。`-f` は削除の確認を省くためだけに要る。 + +`docker_compose_down` に `services` と `timeout` の引数を足す案は採らない。この関数の呼び出し側は `devbase down` と `up` 冒頭の停止だけであり、どちらも全体を `-t0` で落とす。引数を増やすと、使われない組み合わせが関数の表に残る。変更は `--profile '*'` を足すことに留める(決定 4)。 + +| 関数 | 用途 | 使う subcommand | 猶予 | サービスの指定 | +| --- | --- | --- | --- | --- | +| `docker_compose_down` | `devbase down` / `up` 冒頭の停止 | `down` | `-t0` | しない(全体) | +| `cmd_profile_down` | プロファイルの停止 | `stop` → `rm -f` | 既定(10 秒) | する | + +### 決定 6: プロジェクト名は位置引数で受け、`bin/devbase` は変えない + +プロジェクト名を受けるのは `project profile` だけである。既存の `scale` と同じ並び(`[name] <値>`)に揃える。`container profile` は受けない。`container` 群のサブコマンドは現在地のプロジェクトで動く、という既存の規約に従う。`project` でも `login` / `build` が同じ理由で `[name]` を持たない。 + +`bin/devbase` は変えない。`_PROJECT_NAME_SUBCOMMANDS`(`up down ps logs scale rebuild`)の名前解決は 3 番目の引数だけを見る。`profile` をその一覧へ足すと、`up` / `down` / `list` がプロジェクト名として解決されうる。Python 側の `_dispatch_lifecycle` には名前を解決して移動する経路が既にある。そちらに寄せる。 + +| 入口 | `[name]` | 名前の解決 | +| --- | --- | --- | +| `devbase project profile up [name] ` | 受ける | `_dispatch_lifecycle`(Python 側) | +| `devbase container profile up ` | 受けない | しない(現在地で動く) | +| `devbase ct profile up ` | 受けない | しない(現在地で動く) | + +### 決定 7: 有効なプロファイルは devbase が決め、起動の対象も明示する + +`docker_compose` は現在 `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ(`lib/devbase/utils/docker.py:14-55`)。子プロセスは `os.environ` を暗黙に継承する。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 + +そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** + +**1. 環境変数を外す。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製から `COMPOSE_PROFILES` を除き、それを `subprocess.run(..., env=env)` へ渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 + +**2. 起動の対象を明示する。** 1 だけでは足りない。Compose はプロジェクトの `.env` を自分で読むため、そこに書かれた `COMPOSE_PROFILES` は効いてしまう。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 + +既定のサービスの一覧は `default_services` が `.docker-compose.scale.yml` から作る(決定 1)。`devbase up` は起動の直前に生成物を作り直すため、一覧は常に最新である。 + +| 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | +| --- | --- | --- | --- | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 外す | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 外す | +| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 外す | +| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 外す | + +**この方式の前提と限界。** 前提は、生成物が `up` のたびに作り直されることである。限界は、対象を明示するため、生成物に無いサービスは `up` で起動しないことである。生成物には必ず `dev-1`..`dev-N` が入るため、一覧が空になることはない。 + +**停止には要らない。** 停止は `--profile '*'` で対象を広げる向きの指定である。`.env` が別のプロファイルを有効にしても、対象が狭まることはない。 + +**`docker compose` を呼ぶ経路の棚卸し。** 現在は 3 か所ある。扱いは次のとおりである。 + +| 経路 | 場所 | 扱い | +| --- | --- | --- | +| `docker_compose` | `lib/devbase/utils/docker.py:14` | 対象。`up` / `down` / `profile` の各操作はここを通る | +| `_compose_run`(`ps` / `logs`) | `lib/devbase/commands/container.py:269` | 対象。`docker_compose` を経由せず直接 `subprocess.run` するため、同じ `env=` を別に組み立てる | +| `cmd_scale` の直接呼び出し | `lib/devbase/commands/container.py:1282` | 範囲外。要求仕様の「対象範囲・含まない」に挙げる | + +`ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 + +**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 + +**`.env` は書き換えない。** `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。環境変数の除去と起動の対象の明示なら、影響は devbase 経由の呼び出しだけに閉じる。 + +| 案 | 効く範囲 | 判定 | +| --- | --- | --- | +| `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | +| 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | +| 環境変数を外すだけ | devbase 経由のみ。`.env` には効かない | 足りない。単独では受け入れ条件を満たせない | +| 環境変数を外し、起動の対象を既定のサービスへ限る | devbase 経由のみ | 可 | + +`--profile` を明示する経路では、環境変数を外しても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 + +### 決定 8: 一覧の 2 項目は、プロファイルを持つプロジェクトにだけ出す + +操作の一覧は固定の並びだったが、この 2 項目だけ出し分ける。プロファイルを持たないプロジェクトで選べてしまうと、選んだ後に「プロファイルがありません」と戻ることになる。選べる操作は、選べば動くものに限る。 + +常に出して選んだ後に知らせる案は採らない。一覧の項目が 8 個から 10 個に増え、そのうち 2 個が多くのプロジェクトで動かない状態になる。 + +実行後は一覧へ戻す(`_BACK_TO_TOP_OPS` に入れる)。起動と停止はコンテナの数を変えるため、一覧の状態表示を更新して見せる。 + diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 87653e6e..1a98d01a 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -11,6 +11,7 @@ | F3 | プロファイルの一覧と稼働状況を見る | プロジェクトの利用者 | | F4 | 停止(`down` と `up` 冒頭)を全プロファイルへ効かせる | プロジェクトの利用者 | | F5 | 有効なプロファイルをフックへ伝える | プロジェクトの作者 | +| F6 | `devbase list` の一覧から起動・停止する | プロジェクトの利用者 | ## 構成要素 @@ -21,11 +22,13 @@ | Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` は子プロセスの環境から外す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | +| 一覧の操作メニュー | `devbase list` の起動中の行で、プロファイルの起動・停止を選ばせる。選ばせた後は共有のハンドラへ委譲する(F6) | ```mermaid graph TD subgraph 入口 CLI[引数の受け口] + TUI[一覧の操作メニュー] end subgraph 操作 OP[プロファイルの操作] @@ -36,6 +39,7 @@ graph TD DC[Compose の呼び出し] end CLI --> OP + TUI --> CLI OP --> RS OP --> DC OP --> HK @@ -87,12 +91,17 @@ lib/devbase/ │ └── container.py # cmd_profile_up / down / list、default_services、_compose_run、dispatch(変更) ├── project/ │ └── runtime.py # hook_env に有効なプロファイルを足す(変更) +├── tui/ +│ └── actions_project.py # 起動中の行の操作へ profile の 2 項目を足す(変更) ├── utils/ │ └── docker.py # docker_compose が env= を組んで COMPOSE_PROFILES を外す、docker_compose_up が対象サービスを受ける、docker_compose_down を全プロファイル対応へ(変更) └── volume/ └── compose.py # profiles を保つことの確認のみ(変更なし) tests/ +├── cli/ +│ └── tui/ +│ └── test_profile_menu.py # 新設(項目の出し分けと委譲の属性の検査) ├── commands/ │ └── test_container_profile.py # 新設 ├── utils/ @@ -130,7 +139,7 @@ tests/ `_run_deploy_script_for_instances` は受け取った値を加工せず `hook_env(config, active_profiles=active_profiles)` へ渡す。環境変数の名前と区切り(カンマ)を決めるのは `hook_env` だけである。 -今回の範囲では、渡る値は常にプロファイル名 1 つである。`cmd_profile_up` が受ける名前が 1 つだけで、同時に 2 つ以上を起動する操作を作らないためである。カンマ区切りは将来の拡張のための予約であり、現時点でその形になる経路は無い(「未確認のまま残ること」)。 +この変更では、渡る値は常にプロファイル名 1 つである。`cmd_profile_up` が受ける名前が 1 つだけで、同時に 2 つ以上を起動する操作を作らないためである。カンマ区切りは将来の拡張のための予約であり、現時点でその形になる経路は無い(「未確認のまま残ること」)。 `_run_deploy_script_for_instances` へ渡す `indices` は `range(1, scale + 1)` である。`scale` は `project.yml` の `config.scale` から取る。未指定なら `project_runtime.DEFAULT_SCALE`(現在は 2)を使う。`cmd_up` が使っている解決の式をそのまま再利用する。`.docker-compose.scale.yml` の `dev-*` を数え直すことはしない。 @@ -153,6 +162,21 @@ tests/ `container` / `ct` は `[name]` を持たない。値は常にプロファイル名である。`container` 群のサブコマンドは現在地のプロジェクトで動く既存の規約に従う。この非対称は `project` / `container` の既存の作りと同じである。 +### 一覧の操作メニュー + +`devbase list` で起動中の行を選ぶと操作の一覧が出る。そこへ 2 項目を足す。 + +| 項目 | 選んだ後 | 実行後 | +| --- | --- | --- | +| テスト用サーバ起動 (profile up) | プロファイル名を選ばせ、`profile up` を実行する | 一覧へ戻る | +| テスト用サーバ停止 (profile down) | 同じくプロファイル名を選ばせ、`profile down` を実行する | 一覧へ戻る | + +**2 項目が出るのは、そのプロジェクトがプロファイルを持つときだけである**(決定 8)。持たないプロジェクトでは一覧の中身が現在と同じになる。 + +プロファイル名の選択は、名前が 1 つだけのときも選択として出す。名前は `.docker-compose.scale.yml` から読む。生成物が無い(`devbase up` の前)ときは 2 項目を出さない。 + +実行は `dispatch_lifecycle('profile', name, profile_subcommand='up', profile='<名前>')` で共有のハンドラへ渡す。TUI はコマンドの中身を持たない。 + ### プロファイルの決め方 有効なプロファイルは devbase が経路ごとに明示して決める。利用者の環境や `.env` の値で、起動する対象が変わらないようにする(決定 7)。 @@ -309,140 +333,7 @@ graph LR ## 決定の記録 -### 決定 1: プロファイルの解決は生成済みの `.docker-compose.scale.yml` を読んで行う - -`devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 - -要求仕様が `compose.yml` と書く箇所との対応は次のとおりである。利用者がプロファイルを宣言するのは `compose.yml` であり、devbase が読むのはその宣言を引き継いだ生成物である。宣言の内容は同じなので、受け入れ条件の文言は変えない。 - -`docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 - -### 決定 2: プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付ける - -サービス名を省いて `--profile X up -d` とすると、既定のサービスも照合の対象に入る。機密は名前だけを列挙する形で生成物に書かれる。値はプロセスの環境から解決される。**照合の対象に入った時点で dev の構成が変わりうる。** そのため対象を明示する。 - -**サービス名の明示だけでは足りない。** `_build_scaled_services` は非 dev サービスにも `_rewrite_depends_on` を適用する(`lib/devbase/volume/compose.py:488`)。プロファイルのサービスが `depends_on: dev` を書いていると、生成物では `dev-1`..`dev-N` になる。Compose は依存先を解決して対象へ取り込む。受け入れ条件の「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。 - -**`required: false` はこれを止めない。** この属性が緩めるのは「依存先が不在のときのエラー」だけである。依存先を操作の対象から外す働きは持たない。Compose v5.1.4 の dry-run でも dev の起動が含まれる。依存先の構成が変わったときに再作成する実装のため、機密などの値が変わった状態では Container ID が変わりうる。 - -そこで `--no-deps` を使う。起動のコマンド列は `--profile X up -d --no-deps <対象サービス...>` になる。この選択肢は依存先を操作の対象から外す。 - -**`--no-deps` は依存先を自動起動しない。** そのため、プロファイルに属するサービスをすべて明示して渡すことが前提になる(要求仕様の前提 5)。1 つでも落とすと、そのサービスは起動しない。渡す集合は「プロファイルの解決」が生成物から求めるため、取りこぼしは起きない。 - -| 組み立て方 | `profile up X` が触るもの | 判定 | -| --- | --- | --- | -| `--profile X up -d`(サービス名なし) | 既定のサービスを含む全体 | 不可。dev を再作成しうる | -| `--profile X up -d <一部のサービス>` | 渡したサービスだけ | 不可。残りが起動しない | -| `--profile X up -d <対象サービス...>`(`--no-deps` なし) | そのサービスと依存先の dev-1..N | 不可。dev を再作成しうる | -| `--profile X up -d --no-deps <対象サービス...>` | そのサービスだけ | 可 | - -`depends_on` の書き方は前提にしない。`depends_on: {dev: {condition: service_started, required: false}}` でも `depends_on: [dev]` でも、`--no-deps` を付ければ dev は対象に入らない。 - -生成のときに devbase が `required: false` を補う案は採らない。依存が必須かどうかはプロジェクトが決める意図であり、生成物が黙って緩めると `devbase up` の起動順の意図が読めなくなる。対象から外す役目は `--no-deps` が担うため、補う必要もない。 - -### 決定 3: プロファイル起動の後は `./deploy` を呼び直す。新しいフックは作らない - -`./deploy` は既にインスタンスごとに呼ばれ、プロジェクト側で冪等に書かれている。プロファイルの有無は `DEVBASE_ACTIVE_PROFILES` で分岐できる。フック名を増やす理由がない。 - -`./post-profile-up` のような新しい名前を足す案は採らない。プロジェクトが持つ約束の数が増える。どちらに書くべきかの判断も各プロジェクトに生まれる。 - -`hook_env` は `_run_pre_up_hook` も呼んでいる。そのため `./pre-up` にも `DEVBASE_ACTIVE_PROFILES` が渡る。`./pre-up` を呼ぶのは `cmd_up` だけなので、値は常に空である。`profile up` は `./pre-up` を呼ばない。コンテナの起動前に済ませる準備は `up` の役目だからである。要求仕様が `./pre-up` を挙げるのは、この空の値を含めた約束のことである。 - -| フック | 呼ぶ経路 | `DEVBASE_ACTIVE_PROFILES` | -| --- | --- | --- | -| `./pre-up` | `cmd_up` のみ | 常に空 | -| `./deploy` | `cmd_up` | 空 | -| `./deploy` | `cmd_profile_up` | 起動したプロファイル名 1 つ | - -### 決定 4: 停止は全プロファイルを対象にし、`up` は既定の状態へ揃える - -`devbase up` は「その構成で開発環境を作り直す」操作である。プロファイルのサービスだけが前の状態のまま残ると、`up` の後の状態が直前の操作に依存する。 - -`up` の冒頭の停止を dev-1..N だけに絞る案は採らない(利用者の指示、2026-09-16)。テストを続けたい場合は `up` の後にもう一度プロファイルを起動する。 - -### 決定 5: プロファイルの停止は `down` を使わず、`stop` と `rm -f` の 2 段で行う - -`cmd_profile_down` はまず `docker_compose(['--profile', X, 'stop', <サービス...>])` を呼ぶ。続けて `docker_compose(['--profile', X, 'rm', '-f', <サービス...>])` を呼ぶ。`<サービス...>` はプロファイル X の全件である(決定 2)。1 段目が失敗したら 2 段目は呼ばず、1 を返す。 - -`down <サービス...>` を渡す案は採らない。理由は 2 つある。 - -| 理由 | 中身 | -| --- | --- | -| 対象が広がらない | `stop` / `rm` のサービス指定は古くから安定しており、依存元をたどって対象を広げない | -| 版の下限を作らない | `down [SERVICES]` の対応版を調べる必要がなくなり、最低対応版の根拠が `depends_on.required` の 2.20.0 だけで閉じる | - -**`down <サービス...>` は依存元も削除する。** Compose の対象選択は、指定したサービスの祖先も含める。祖先とは、そのサービスへ `depends_on` を持つ側である。根拠は [v5.1.4 の実装](https://github.com/docker/compose/blob/v5.1.4/pkg/compose/dependencies.go#L104-L129)である。dev が `depends_on: {db: {condition: service_started, required: false}}` を持つ構成では、`down db` が dev も消す。受け入れ条件「既定のサービスの Container ID と `StartedAt` が変わらない」はこれで崩れる。`required: false` はこれを止めない(決定 2 と同じ理由)。 - -**`down [SERVICES]` は版の下限を左右する。** この位置引数は比較的新しい追加で、宣言している最低対応版 2.20.0 が受け付ける保証が無い。受け付けない版では、`down` がプロジェクト全体(dev を含む)を落とす。`stop` / `rm` へ寄せると、対応版を調べる必要そのものが消える。 - -**猶予は既定の 10 秒とする。** プロファイルに入るのはデータベースのような状態を持つサービスである。`devbase down` は開発環境ごと畳む操作のため `-t0` で即座に落とす。プロファイルの停止は稼働中の開発環境を残したまま行うため、`stop` に `-t` を付けず、既定の猶予で落とす。 - -**ボリュームは消さない。** `rm` には `-f` だけを付け、`-v` は付けない。`-v` は匿名ボリュームを消す選択肢である。名前付きボリュームが残ることは受け入れ条件にある。`-f` は削除の確認を省くためだけに要る。 - -`docker_compose_down` に `services` と `timeout` の引数を足す案は採らない。この関数の呼び出し側は `devbase down` と `up` 冒頭の停止だけであり、どちらも全体を `-t0` で落とす。引数を増やすと、使われない組み合わせが関数の表に残る。変更は `--profile '*'` を足すことに留める(決定 4)。 - -| 関数 | 用途 | 使う subcommand | 猶予 | サービスの指定 | -| --- | --- | --- | --- | --- | -| `docker_compose_down` | `devbase down` / `up` 冒頭の停止 | `down` | `-t0` | しない(全体) | -| `cmd_profile_down` | プロファイルの停止 | `stop` → `rm -f` | 既定(10 秒) | する | - -### 決定 6: プロジェクト名は位置引数で受け、`bin/devbase` は変えない - -プロジェクト名を受けるのは `project profile` だけである。既存の `scale` と同じ並び(`[name] <値>`)に揃える。`container profile` は受けない。`container` 群のサブコマンドは現在地のプロジェクトで動く、という既存の規約に従う。`project` でも `login` / `build` が同じ理由で `[name]` を持たない。 - -`bin/devbase` は変えない。`_PROJECT_NAME_SUBCOMMANDS`(`up down ps logs scale rebuild`)の名前解決は 3 番目の引数だけを見る。`profile` をその一覧へ足すと、`up` / `down` / `list` がプロジェクト名として解決されうる。Python 側の `_dispatch_lifecycle` には名前を解決して移動する経路が既にある。そちらに寄せる。 - -| 入口 | `[name]` | 名前の解決 | -| --- | --- | --- | -| `devbase project profile up [name] ` | 受ける | `_dispatch_lifecycle`(Python 側) | -| `devbase container profile up ` | 受けない | しない(現在地で動く) | -| `devbase ct profile up ` | 受けない | しない(現在地で動く) | - -### 決定 7: 有効なプロファイルは devbase が決め、起動の対象も明示する - -`docker_compose` は現在 `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ(`lib/devbase/utils/docker.py:14-55`)。子プロセスは `os.environ` を暗黙に継承する。`COMPOSE_PROFILES=test` が設定された端末では、`devbase up` の `compose up -d` が test のサービスまで起動する。受け入れ条件「`up` の後は既定のサービスだけが動く」はこれで崩れる。v5.1.4 の最小構成で確認済みである。 - -そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** - -**1. 環境変数を外す。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製から `COMPOSE_PROFILES` を除き、それを `subprocess.run(..., env=env)` へ渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 - -**2. 起動の対象を明示する。** 1 だけでは足りない。Compose はプロジェクトの `.env` を自分で読むため、そこに書かれた `COMPOSE_PROFILES` は効いてしまう。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 - -既定のサービスの一覧は `default_services` が `.docker-compose.scale.yml` から作る(決定 1)。`devbase up` は起動の直前に生成物を作り直すため、一覧は常に最新である。 - -| 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | -| --- | --- | --- | --- | -| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 外す | -| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 外す | -| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 外す | -| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 外す | - -**この方式の前提と限界。** 前提は、生成物が `up` のたびに作り直されることである。限界は、対象を明示するため、生成物に無いサービスは `up` で起動しないことである。生成物には必ず `dev-1`..`dev-N` が入るため、一覧が空になることはない。 - -**停止には要らない。** 停止は `--profile '*'` で対象を広げる向きの指定である。`.env` が別のプロファイルを有効にしても、対象が狭まることはない。 - -**`docker compose` を呼ぶ経路の棚卸し。** 現在は 3 か所ある。扱いは次のとおりである。 - -| 経路 | 場所 | 扱い | -| --- | --- | --- | -| `docker_compose` | `lib/devbase/utils/docker.py:14` | 対象。`up` / `down` / `profile` の各操作はここを通る | -| `_compose_run`(`ps` / `logs`) | `lib/devbase/commands/container.py:269` | 対象。`docker_compose` を経由せず直接 `subprocess.run` するため、同じ `env=` を別に組み立てる | -| `cmd_scale` の直接呼び出し | `lib/devbase/commands/container.py:1282` | 範囲外。要求仕様の「対象範囲・含まない」に挙げる | - -`ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 - -**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 - -**`.env` は書き換えない。** `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。環境変数の除去と起動の対象の明示なら、影響は devbase 経由の呼び出しだけに閉じる。 - -| 案 | 効く範囲 | 判定 | -| --- | --- | --- | -| `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | -| 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | -| 環境変数を外すだけ | devbase 経由のみ。`.env` には効かない | 足りない。単独では受け入れ条件を満たせない | -| 環境変数を外し、起動の対象を既定のサービスへ限る | devbase 経由のみ | 可 | - -`--profile` を明示する経路では、環境変数を外しても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 +結論と理由、採らなかった案は [PLAN58_compose-profiles-decisions.md](PLAN58_compose-profiles-decisions.md) にある。決定は 8 件である。 ## テスト設計 @@ -468,6 +359,9 @@ graph LR | `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 生成物から読んだ既定のサービス名が `up -d` の引数へ並ぶことを検査する。引数にプロファイルのサービスが入らないことも見る。実際の状態は手動確認 | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | +| 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | +| プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | +| 一覧から実行しても dev が変わらない | 委譲へ渡る属性が `profile` のサブコマンドと名前であることを検査する。実際の状態は手動確認 | | プロファイルを持たないプロジェクトの挙動が変わらない | 既存の `tests/commands/test_container_up_order.py` と `tests/cli/test_up_roundtrips.py` が通ること | | プロファイルを持たないプロジェクトで `up` / `down` が従来どおり動く | 手動確認(確認済みの v5.1.4 で実施)。`profiles:` を持たない既存のプロジェクトで `devbase up` と `devbase down` を通し、起動するコンテナの集合と `down` 後に残らないことを確かめる。`--profile '*'` がこの経路に入るため | | 生成物が `profiles` を保つ | `generate_scaled_compose` の出力を読み、非 dev サービスの `profiles` が残ることを検査する | @@ -481,4 +375,4 @@ graph LR | `--profile '*'` が使える最古の版 | 公式ドキュメント([profiles](https://docs.docker.com/compose/how-tos/profiles/))に版の記載が無く、確かめられなかった。最低対応版は `depends_on.required` の 2.20.0 を根拠に定めた | | 古い Compose での `--profile '*'` | 確認済みは v5.1.4 のみ。2.20.0 以上 5.x 未満は未検証。ワイルドカードを解釈しない版で `*` がリテラルのプロファイル名として扱われるか(対象が現在と同じに留まる想定)は未確認 | | 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成 | `.env` でそのプロファイルが有効になっていると、`up -d <既定のサービス...>` が依存先として起動しうる。`up` には `--no-deps` を付けないためである。この組み合わせは未検証である | -| プロファイルが複数同時に有効な場合 | 同時に 2 つ以上を起動する操作は今回作らない。よって `DEVBASE_ACTIVE_PROFILES` は常に単一値で、カンマ区切りは将来の拡張のための予約である。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | +| プロファイルが複数同時に有効な場合 | 同時に 2 つ以上を起動する操作は作らない。よって `DEVBASE_ACTIVE_PROFILES` は常に単一値で、カンマ区切りは将来の拡張のための予約である。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index d2118b03..a9b194de 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -35,6 +35,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - プロファイル単位で起動・停止するコマンド - 停止(`devbase down` と `up` 冒頭の停止)を全プロファイルへ効かせること - プロジェクトのフックへ、有効なプロファイルを伝えること +- `devbase list` の TUI から、プロファイルを起動・停止できること - プロファイルを使うプロジェクト作者向けの文書 含まない: @@ -62,18 +63,26 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 起動と停止: - [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない -- [ ] `devbase container profile up X` を実行すると、プロファイル X のサービスだけが起動する。既定のサービス(dev を含む)の Container ID と `StartedAt` は実行の前後で変わらない -- [ ] `devbase container profile down X` を実行すると、プロファイル X のサービスのコンテナが削除される。既定のサービスの Container ID と `StartedAt` は実行の前後で変わらない +- [ ] 前提: `devbase up` が済み、既定のサービスだけが動いている + 操作: `devbase container profile up X` を実行する + 結果: プロファイル X のサービスだけが起動し、既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] 前提: プロファイル X のサービスが動いている + 操作: `devbase container profile down X` を実行する + 結果: プロファイル X のサービスのコンテナだけが削除され、既定のサービスの Container ID と `StartedAt` は変わらない - [ ] `devbase container profile up X` は、プロファイル X に属するサービスをすべて Compose へ渡し、`--no-deps` を付ける -- [ ] `depends_on: {dev: {condition: service_started, required: false}}` を持つプロファイル X のサービスを `devbase container profile up X` で起動しても、既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] 前提: プロファイル X のサービスが `depends_on: {dev: {condition: service_started, required: false}}` を持つ + 操作: `devbase container profile up X` を実行する + 結果: 既定のサービスの Container ID と `StartedAt` が変わらない - [ ] `depends_on: [dev]`(`required` を書かない形)を持つサービスでも、`devbase container profile up X` で既定のサービスの Container ID と `StartedAt` は変わらない - [ ] dev の環境変数の値を変えた後でも、`devbase container profile up X` は dev を再作成しない -- [ ] dev が `depends_on: {db: {condition: service_started, required: false}}` を持ち、db をプロファイル X に入れた構成でも、`devbase container profile down X` の前後で dev の Container ID と `StartedAt` が変わらない +- [ ] 前提: dev が `depends_on: {db: {condition: service_started, required: false}}` を持ち、db はプロファイル X に属する + 操作: `devbase container profile down X` を実行する + 結果: dev の Container ID と `StartedAt` が前後で変わらない - [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る - [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない - [ ] `compose.yml` に無いプロファイル名を `up` / `down` へ渡すと、存在する名前の一覧を出して終了コード 1 で止まる -- [ ] `devbase project profile up <プロジェクト> X` は、そのプロジェクトのディレクトリで `devbase container profile up X` を実行したのと同じ結果になる(`down` / `list` も同じ) +- [ ] `devbase project profile up <プロジェクト> X` の結果は、そのプロジェクトのディレクトリで `devbase container profile up X` を実行した場合と同じになる。`down` と `list` も同じである 停止の網羅: @@ -83,11 +92,20 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] プロジェクトの `.env` に `COMPOSE_PROFILES` が書かれた状態でも、`devbase up` は既定のサービスだけを起動する(起動の対象をサービス名で明示するため。設計の決定 7) - [ ] `devbase up` の起動は、生成物の `profiles` を持たないサービスをすべてサービス名として Compose へ渡す +TUI: + +- [ ] `devbase list` で起動中のプロジェクトを選ぶと、操作のメニューに「テスト用サーバ起動 (profile up)」と「テスト用サーバ停止 (profile down)」が並ぶ +- [ ] `compose.yml` にプロファイルを 1 つも持たないプロジェクトでは、その 2 項目が出ない +- [ ] 項目を選ぶとプロファイル名の選択が出る。名前が 1 つだけのときもその 1 件の選択として出す +- [ ] TUI から起動・停止した後は一覧へ戻り、STATUS のコンテナ数が実際の数に変わる +- [ ] TUI から起動・停止しても、dev-1..N の Container ID と `StartedAt` は変わらない +- [ ] questionary が無い端末の代替経路(番号入力して `up`)の挙動は変わらない + フック: - [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` から呼ばれるときは、どちらも空である -- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値は常にプロファイル名 1 つである -- [ ] 同時に 2 つ以上のプロファイルを起動する操作は今回作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この範囲では受け入れ条件にしない +- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値はプロファイル名 1 つである +- [ ] 同時に 2 つ以上のプロファイルを起動する操作は作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この変更では受け入れ条件にしない - [ ] `devbase container profile up X` は `./pre-up` を呼ばない - [ ] `devbase container profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる @@ -123,7 +141,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | 対象 | 影響 | | --- | --- | -| 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | +| 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。`devbase list` の TUI の操作メニューに 2 項目を足す。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | | 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ `COMPOSE_PROFILES` を渡さなくなる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | @@ -131,11 +149,13 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | 項目 | 手段 | | --- | --- | +| テスト(TUI) | `uv run pytest tests/cli/tui -q`(メニューの項目と、委譲へ渡す属性の検査) | | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | | 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | | 手動確認(依存の向きが逆の構成) | dev へ `depends_on: {db: {condition: service_started, required: false}}` を書き、db をプロファイル X に入れた構成で `profile up X` → `profile down X` を通す。停止の前後で dev の Container ID と `StartedAt` が変わらないことを見る。`stop` / `rm -f` が依存元を対象に含めないことの確認である(設計の決定 5) | -| 手動確認(`COMPOSE_PROFILES` が有効な環境) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。`.env` の側は、起動のコマンド列に既定のサービス名が並ぶことも見る(設計の決定 7)。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | +| 手動確認(`COMPOSE_PROFILES` が設定された端末) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。`.env` の側は、起動のコマンド列に既定のサービス名が並ぶことも見る(設計の決定 7)。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | +| 手動確認(TUI) | `devbase list` を開き、起動中のプロジェクトで「テスト用サーバ起動」→ 一覧の STATUS のコンテナ数が増えることを見る。続けて「テスト用サーバ停止」で戻ることも見る。前後で dev-1..N の Container ID と `StartedAt` を比べる | | 手動確認(退行) | 確認済みの Docker Compose v5.1.4 で実施する。`profiles:` を持たないプロジェクトで `devbase up` と `devbase down` を通し、従来どおり動くことを確かめる。起動するコンテナの集合と順序、`down` 後に何も残らないことを見る。`--profile '*'` がこの経路に入るためである | 自動テストで dev の Container ID の不変を確かめることはできない(実コンテナが要る)。この条件は手動確認で判定する。 From bd53005890264202ac6fab84f2eb217b6feda9d8 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:03:31 +0900 Subject: [PATCH 13/22] =?UTF-8?q?docs(PLAN58):=20=E6=B1=BA=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=B8=E7=AF=80?= =?UTF-8?q?=E3=81=AE=E8=A6=8B=E5=87=BA=E3=81=97=E3=82=92=E7=BD=AE=E3=81=8F?= =?UTF-8?q?=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 59715b6a..81966baa 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -2,6 +2,8 @@ 設計は [PLAN58_compose-profiles-design.md](PLAN58_compose-profiles-design.md) にある。このファイルは、その設計で選んだ結論と理由、採らなかった案だけを持つ。 +## 決定の記録 + ### 決定 1: プロファイルの解決は生成済みの `.docker-compose.scale.yml` を読んで行う `devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 From 070bad47e853c3d26ddfe056da1483b7432b64f8 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:10:05 +0900 Subject: [PATCH 14/22] =?UTF-8?q?docs(PLAN58):=20COMPOSE=5FPROFILES=20?= =?UTF-8?q?=E3=82=92=E7=95=AA=E5=85=B5=E3=81=AE=E5=90=8D=E5=89=8D=E3=81=A7?= =?UTF-8?q?=E4=B8=8A=E6=9B=B8=E3=81=8D=E3=81=99=E3=82=8B=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E3=81=B8=E6=94=B9=E3=82=81=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit キーを外すだけでは、Compose が .env の値を読み直すため依存先のサービスが起動する (v5.1.4 で実測)。どのプロジェクトも定義しない番兵の名前を入れて上書きする。 依存の待ち合わせを失う --no-deps の案は採らない。 docker_compose_down の引数は増やさず、内部で --profile '*' を足す形に戻した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 34 ++++++++++++++---- issues/PLAN58_compose-profiles-design.md | 38 ++++++++++----------- issues/PLAN58_compose-profiles.md | 11 +++--- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 81966baa..4ebb2f13 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -99,9 +99,29 @@ そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** -**1. 環境変数を外す。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製から `COMPOSE_PROFILES` を除き、それを `subprocess.run(..., env=env)` へ渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 +**1. 環境変数を devbase の値で上書きする。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製の `COMPOSE_PROFILES` へ、どのプロジェクトも定義しない番兵の名前 `__devbase_none__` を入れて渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 -**2. 起動の対象を明示する。** 1 だけでは足りない。Compose はプロジェクトの `.env` を自分で読むため、そこに書かれた `COMPOSE_PROFILES` は効いてしまう。そこで `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。プロファイルが `.env` 経由で有効になっても、対象に入らないサービスは起動しない。 +キーを外すだけでは足りない。**Compose はプロジェクトの `.env` を自分で読み、環境変数が無ければその値を採る。** 値を入れておけば、環境変数がファイルより優先される規則に乗って `.env` の指定を無効にできる。 + +**2. 起動の対象を明示する。** `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。1 が効かない形でプロファイルが有効になっても、対象に入らないサービスは起動しない。 + +2 つは役割が違う。 + +| 対策 | 何を防ぐか | +| --- | --- | +| 番兵の名前を渡す | 端末の環境変数と `.env` の両方によるプロファイルの有効化。依存の待ち合わせはそのまま残る | +| 対象を明示する | 対象の広がり。`up` に名前を渡すことで、一覧に無いサービスは起動しない | + +**番兵だけで `depends_on` 経由の起動も止まる。** 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成では、プロファイルが有効なかぎり依存先として起動する。対象の明示では止まらない。番兵で無効にすれば起動しない。`--no-deps` で止める案は採らない。既定のサービスどうしの `depends_on` の待ち合わせまで失う。 + +実測(Docker Compose v5.1.4、`.env` に `COMPOSE_PROFILES=test`、dev が `depends_on: {db: {condition: service_started, required: false}}` を持つ構成): + +| 渡し方 | 起動したもの | +| --- | --- | +| そのまま `up -d dev` | dev と db | +| キーを外して `up -d dev` | dev と db | +| `COMPOSE_PROFILES=__devbase_none__` で `up -d dev` | dev だけ | +| `--no-deps` を付けて `up -d dev` | dev だけ(依存の待ち合わせを失う) | 既定のサービスの一覧は `default_services` が `.docker-compose.scale.yml` から作る(決定 1)。`devbase up` は起動の直前に生成物を作り直すため、一覧は常に最新である。 @@ -126,18 +146,20 @@ `ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 -**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。キーごと外せばその判断が要らない。 +**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。番兵の名前なら、どちらに解釈されても「その名前のプロファイルは無い」という同じ結果になる。 + +**番兵の名前は `__devbase_none__` とする。** プロジェクトがこの名前のプロファイルを定義すると、そのプロファイルが常に有効になる。前置きの下線 2 つは Compose の慣習的な名前と衝突しにくく、文書でこの名前を予約として案内する。 **`.env` は書き換えない。** `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。環境変数の除去と起動の対象の明示なら、影響は devbase 経由の呼び出しだけに閉じる。 | 案 | 効く範囲 | 判定 | | --- | --- | --- | | `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | +| 環境変数のキーを外す | devbase 経由のみ。`.env` には効かない | 足りない。`.env` の値がそのまま効く | | 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | -| 環境変数を外すだけ | devbase 経由のみ。`.env` には効かない | 足りない。単独では受け入れ条件を満たせない | -| 環境変数を外し、起動の対象を既定のサービスへ限る | devbase 経由のみ | 可 | +| 環境変数へ番兵の名前を入れ、起動の対象も既定のサービスへ限る | devbase 経由のみ | 可 | -`--profile` を明示する経路では、環境変数を外しても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 +`--profile` を明示する経路では、番兵を入れても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 ### 決定 8: 一覧の 2 項目は、プロファイルを持つプロジェクトにだけ出す diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 1a98d01a..5abcaf2e 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -19,7 +19,7 @@ | --- | --- | | プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。`profiles` を持たない既定のサービスの一覧も同じ場所から求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` は子プロセスの環境から外す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` には番兵の名前を入れて渡す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | | 一覧の操作メニュー | `devbase list` の起動中の行で、プロファイルの起動・停止を選ばせる。選ばせた後は共有のハンドラへ委譲する(F6) | @@ -94,7 +94,7 @@ lib/devbase/ ├── tui/ │ └── actions_project.py # 起動中の行の操作へ profile の 2 項目を足す(変更) ├── utils/ -│ └── docker.py # docker_compose が env= を組んで COMPOSE_PROFILES を外す、docker_compose_up が対象サービスを受ける、docker_compose_down を全プロファイル対応へ(変更) +│ └── docker.py # docker_compose が env= を組んで番兵を入れる、docker_compose_up が対象サービスを受ける、docker_compose_down が全プロファイルを対象にする(変更) └── volume/ └── compose.py # profiles を保つことの確認のみ(変更なし) @@ -121,11 +121,11 @@ tests/ | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | -| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` を除いたうえで渡す(決定 7)。コマンド列の組み立て方は変えない | +| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` へ番兵の名前 `__devbase_none__` を入れて渡す(決定 7)。コマンド列の組み立て方は変えない | | `docker_compose_up(compose_file, detach=True, services=())` | 変更(`utils/docker.py`) | 起動の対象を受け取り、`['up', '-d', *services]` を組む。空なら現在と同じ `['up', '-d']` になる | | `_compose_run(subcommand, ...)` | 変更(`commands/container.py:269`) | `docker_compose` を経由せず直接 `subprocess.run` する経路(`ps` / `logs`)。同じ `env=` を組み立てて渡す(決定 7) | | `_run_deploy_pipeline(...)` | 変更(`commands/container.py`) | 生成した `.docker-compose.scale.yml` から `default_services` を求め、`docker_compose_up` へ渡す | -| `docker_compose_down(compose_file, all_profiles=True)` | 変更(`utils/docker.py`) | F4。`--profile '*'` を付けて呼ぶ。`['down', '-t0']` という固定の形は変えない | +| `docker_compose_down(compose_file)` | 変更(`utils/docker.py`) | F4。引数は増やさず、内部で無条件に `--profile '*'` を足す。`['down', '-t0']` という固定の形は変えない(決定 5) | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | | `_dispatch_lifecycle` | 変更 | `profile` を handlers へ 1 つ足し、`args.profile_subcommand` で 3 つの入口へ振り分ける | @@ -183,13 +183,13 @@ tests/ | 経路 | `--profile` | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | -| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 子プロセスの環境から外す | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 番兵の名前 `__devbase_none__` を入れる | | `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない | 同上 | | `profile up X` / `profile down X` | `--profile X` | 対象のサービスをすべて明示する | 同上 | -`COMPOSE_PROFILES` は空文字列にするのではなく、渡さない。空文字列を渡す版の扱いを調べずに済むためである。 +`COMPOSE_PROFILES` は空文字列にしない。どのプロジェクトも定義しない番兵の名前を入れる。空文字列の扱いが版で違う可能性を調べずに済むためである。 -**環境変数を外すだけでは足りない。** Compose はプロジェクトの `.env` を自分で読むため、`devbase up` の起動では既定のサービス名も明示する(決定 7)。 +**キーを外すだけでは足りない。** Compose は環境変数が無ければプロジェクトの `.env` を読む。値を入れて上書きし、あわせて `devbase up` の起動では既定のサービス名も明示する(決定 7)。 ### `profile list` の表 @@ -294,11 +294,11 @@ sequenceDiagram | 操作 | コマンド列 | 子プロセスの `COMPOSE_PROFILES` | | --- | --- | --- | -| プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 外す | -| プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 外す | -| プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 外す | -| `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 外す | -| `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 外す | +| プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 番兵 | +| プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 番兵 | +| プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 番兵 | +| `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 番兵 | +| `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 番兵 | `devbase up` の `<既定のサービス...>` は、生成物のうち `profiles` を持たないサービスの全件である(決定 7)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 @@ -306,13 +306,13 @@ sequenceDiagram ```mermaid graph LR - A[devbase down] --> B["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] - C[devbase up] --> D["env から COMPOSE_PROFILES を外す → compose --profile '*' down -t0"] - D --> E["env から COMPOSE_PROFILES を外す → compose up -d 既定のサービス一覧、--profile なし"] + A[devbase down] --> B["env の COMPOSE_PROFILES へ番兵を入れる → compose --profile '*' down -t0"] + C[devbase up] --> D["env の COMPOSE_PROFILES へ番兵を入れる → compose --profile '*' down -t0"] + D --> E["env の COMPOSE_PROFILES へ番兵を入れる → compose up -d 既定のサービス一覧、--profile なし"] E --> F[既定のサービスだけが動く] ``` -この除去が無いと、`COMPOSE_PROFILES=test` を持つ環境で `devbase up` が test のサービスまで起動する。`up` 冒頭の停止は `--profile '*'` で全部を落とすため、残骸ではなく新しい起動として現れる。`.env` に書かれた値には除去が効かない。そちらは既定のサービスの明示で防ぐ(決定 7)。 +この上書きが無いと、`COMPOSE_PROFILES=test` を持つ端末で `devbase up` が test のサービスまで起動する。`up` 冒頭の停止は `--profile '*'` で全部を落とすため、残骸ではなく新しい起動として現れる。`.env` に書かれた値も、環境変数が優先される規則で無効になる(決定 7)。 ## 非機能の実現方式 @@ -320,7 +320,7 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` を子プロセスの環境から外し、`devbase up` の起動では既定のサービスを明示する(決定 7)。サービスを明示した `up` も 2 系全般で使えるため、どちらも版の下限を作らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を環境変数と `.env` の両方に置いて `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` へ番兵の名前を入れ、`devbase up` の起動では既定のサービスを明示する(決定 7)。サービスを明示した `up` も 2 系全般で使えるため、どちらも版の下限を作らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を環境変数と `.env` の両方に置いて `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | 起動で既定のサービスを対象から外すのは `--no-deps` である。停止で対象から外すのは、サービスを明示した `stop` / `rm -f` である。どれも古くからある形で、版の下限を作らない。下限を決めるのは `depends_on.required` だけである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 @@ -355,8 +355,8 @@ graph LR | `project profile up <名前> X` が同じ結果になる | 引数の解釈(`[name] ` の割り当て)を `tests/cli` の既存の書き方で検査する | | `devbase down` が全プロファイルを消し、0 で終わる | 引数列に `--profile *` が含まれることを検査する。全体の削除は手動確認 | | `devbase up` の後は既定のサービスだけが動く | 冒頭の停止が `--profile *` を通ることと、起動の引数列に `--profile` が入らないことを検査する。実際の状態は手動確認 | -| `COMPOSE_PROFILES` が環境変数に設定されていても `devbase up` は既定のサービスだけを起動する | `COMPOSE_PROFILES=test` を `monkeypatch.setenv` で置き、`docker_compose` と `_compose_run` が組み立てた `env` にこのキーが無いことを検査する。`up` の起動・`down`・`profile up` / `down` の各経路で見る。実際の状態は手動確認 | -| `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 生成物から読んだ既定のサービス名が `up -d` の引数へ並ぶことを検査する。引数にプロファイルのサービスが入らないことも見る。実際の状態は手動確認 | +| `COMPOSE_PROFILES` が環境変数に設定されていても `devbase up` は既定のサービスだけを起動する | `COMPOSE_PROFILES=test` を `monkeypatch.setenv` で置き、`docker_compose` と `_compose_run` が組み立てた `env` の値が `__devbase_none__` であることを検査する。`up` の起動・`down`・`profile up` / `down` の各経路で見る。実際の状態は手動確認 | +| `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 組み立てた `env` の値と、`up -d` の引数へ並ぶ既定のサービス名を検査する。既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成での実際の起動は手動確認で見る | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index a9b194de..63105c30 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -88,8 +88,11 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] プロファイル X のサービスが起動している状態で `devbase down` を実行すると、既定のサービスとプロファイル X のサービスの両方が削除され、終了コード 0 で終わる - [ ] 同じ状態で `devbase up` を実行すると、冒頭の停止でプロファイル X のサービスも止まり、起動後は既定のサービスだけが動いている -- [ ] `COMPOSE_PROFILES` が環境変数に設定された状態でも、`devbase up` は既定のサービスだけを起動する -- [ ] プロジェクトの `.env` に `COMPOSE_PROFILES` が書かれた状態でも、`devbase up` は既定のサービスだけを起動する(起動の対象をサービス名で明示するため。設計の決定 7) +- [ ] `COMPOSE_PROFILES` が端末の環境変数に設定された状態でも、`devbase up` は既定のサービスだけを起動する +- [ ] プロジェクトの `.env` に `COMPOSE_PROFILES` が書かれた状態でも、`devbase up` は既定のサービスだけを起動する(設計の決定 7) +- [ ] 前提: 既定のサービスがプロファイル X のサービスへ `depends_on` を持ち、`.env` に `COMPOSE_PROFILES=X` が書かれている + 操作: `devbase up` を実行する + 結果: プロファイル X のサービスは起動しない - [ ] `devbase up` の起動は、生成物の `profiles` を持たないサービスをすべてサービス名として Compose へ渡す TUI: @@ -121,7 +124,7 @@ TUI: | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | | システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | -| 再現性 | 有効なプロファイルは devbase が `--profile` で決め、`devbase up` の起動では対象のサービスも明示する。利用者の `COMPOSE_PROFILES` に結果が左右されない。環境変数でも `.env` でも同じである(設計の決定 7) | +| 再現性 | 有効なプロファイルは devbase が決める。子プロセスの `COMPOSE_PROFILES` へ番兵の名前を入れ、`devbase up` の起動では対象のサービスも明示する。利用者の設定に結果が左右されない。端末の環境変数でも `.env` でも同じである(設計の決定 7) | 最低対応版を 2.20.0 とする根拠は次のとおりである。 @@ -143,7 +146,7 @@ TUI: | --- | --- | | 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。`devbase list` の TUI の操作メニューに 2 項目を足す。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | -| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ `COMPOSE_PROFILES` を渡さなくなる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | +| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ渡る `COMPOSE_PROFILES` が番兵の名前になる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | ## 検証手段 From 15d56c475b20c1d6bd07699456348100f40808a2 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:10:49 +0900 Subject: [PATCH 15/22] =?UTF-8?q?docs(PLAN58):=20=E7=95=AA=E5=85=B5?= =?UTF-8?q?=E3=81=A7=E8=A7=A3=E6=B1=BA=E3=81=97=E3=81=9F=E9=A0=85=E7=9B=AE?= =?UTF-8?q?=E3=82=92=E6=9C=AA=E7=A2=BA=E8=AA=8D=E3=81=AE=E8=A1=A8=E3=81=8B?= =?UTF-8?q?=E3=82=89=E5=A4=96=E3=81=99=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 1 - 1 file changed, 1 deletion(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 5abcaf2e..6544e5e1 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -374,5 +374,4 @@ graph LR | --- | --- | | `--profile '*'` が使える最古の版 | 公式ドキュメント([profiles](https://docs.docker.com/compose/how-tos/profiles/))に版の記載が無く、確かめられなかった。最低対応版は `depends_on.required` の 2.20.0 を根拠に定めた | | 古い Compose での `--profile '*'` | 確認済みは v5.1.4 のみ。2.20.0 以上 5.x 未満は未検証。ワイルドカードを解釈しない版で `*` がリテラルのプロファイル名として扱われるか(対象が現在と同じに留まる想定)は未確認 | -| 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成 | `.env` でそのプロファイルが有効になっていると、`up -d <既定のサービス...>` が依存先として起動しうる。`up` には `--no-deps` を付けないためである。この組み合わせは未検証である | | プロファイルが複数同時に有効な場合 | 同時に 2 つ以上を起動する操作は作らない。よって `DEVBASE_ACTIVE_PROFILES` は常に単一値で、カンマ区切りは将来の拡張のための予約である。`profile up` を 2 回呼ぶと、2 回目のフックへ渡るのは 2 つ目の名前だけになる | From 9e830d03242b10ab2bc00aa367d7bfb34969bba9 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:15:00 +0900 Subject: [PATCH 16/22] =?UTF-8?q?docs(PLAN58):=20=E7=95=AA=E5=85=B5?= =?UTF-8?q?=E3=81=AE=E5=A5=91=E7=B4=84=E3=81=B8=E8=A1=A8=E7=8F=BE=E3=82=92?= =?UTF-8?q?=E7=B5=B1=E4=B8=80=E3=81=97=E3=80=81compose=20=E3=82=92?= =?UTF-8?q?=E5=91=BC=E3=81=B6=206=20=E7=B5=8C=E8=B7=AF=E3=82=92=E6=A3=9A?= =?UTF-8?q?=E5=8D=B8=E3=81=97=E3=81=99=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 決定 7 の表に残っていた「外す」を番兵の名前へ揃えた。config --format json を 呼ぶ 2 経路と、エディタを開く経路の ps も対象に含めた。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 17 +++++++++++------ issues/PLAN58_compose-profiles-design.md | 6 ++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 4ebb2f13..57d6a5c2 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -127,22 +127,27 @@ | 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | -| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 外す | -| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 外す | -| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 外す | -| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 外す | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 番兵の名前を入れる | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 番兵の名前を入れる | +| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 番兵の名前を入れる | +| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 番兵の名前を入れる | **この方式の前提と限界。** 前提は、生成物が `up` のたびに作り直されることである。限界は、対象を明示するため、生成物に無いサービスは `up` で起動しないことである。生成物には必ず `dev-1`..`dev-N` が入るため、一覧が空になることはない。 **停止には要らない。** 停止は `--profile '*'` で対象を広げる向きの指定である。`.env` が別のプロファイルを有効にしても、対象が狭まることはない。 -**`docker compose` を呼ぶ経路の棚卸し。** 現在は 3 か所ある。扱いは次のとおりである。 +**`docker compose` を呼ぶ経路の棚卸し。** 現在は 6 か所ある。扱いは次のとおりである。 | 経路 | 場所 | 扱い | | --- | --- | --- | | `docker_compose` | `lib/devbase/utils/docker.py:14` | 対象。`up` / `down` / `profile` の各操作はここを通る | | `_compose_run`(`ps` / `logs`) | `lib/devbase/commands/container.py:269` | 対象。`docker_compose` を経由せず直接 `subprocess.run` するため、同じ `env=` を別に組み立てる | -| `cmd_scale` の直接呼び出し | `lib/devbase/commands/container.py:1282` | 範囲外。要求仕様の「対象範囲・含まない」に挙げる | +| `_resolve_dev_service`(`config --format json`) | `lib/devbase/commands/container.py:1430` | 対象。`config` は有効なプロファイルのサービスを解決結果へ含めるため、読む集合が利用者の設定で変わる | +| `_read_compose_services`(`config --format json`) | `lib/devbase/commands/container.py:1608` | 対象。同上。イメージの確認が読むサービスの集合を devbase が決める | +| エディタを開く経路(`ps --format json`) | `lib/devbase/editor/opener.py:395` | 対象。`ps` の解釈もプロファイルに依るため、`_compose_run` と同じ扱いにする | +| `cmd_scale` の直接呼び出し | `lib/devbase/commands/container.py:1282` | 範囲外。プロファイルの入口ではない。要求仕様の「対象範囲・含まない」に挙げる | + +**5 か所を対象にする。** どれも「devbase が読むサービスの集合」か「devbase が起動するサービスの集合」を決める経路である。集合が利用者の設定で変わると、同じプロジェクトでも devbase の判断が端末ごとに変わる。 `ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 6544e5e1..19d2eec4 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -105,7 +105,7 @@ tests/ ├── commands/ │ └── test_container_profile.py # 新設 ├── utils/ -│ └── test_docker_profiles.py # 新設(子プロセスの env から COMPOSE_PROFILES が外れることの検査) +│ └── test_docker_profiles.py # 新設(子プロセスの env の COMPOSE_PROFILES が番兵の名前になることの検査) └── volume/ └── test_compose_profiles.py # 新設 ``` @@ -124,6 +124,8 @@ tests/ | `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` へ番兵の名前 `__devbase_none__` を入れて渡す(決定 7)。コマンド列の組み立て方は変えない | | `docker_compose_up(compose_file, detach=True, services=())` | 変更(`utils/docker.py`) | 起動の対象を受け取り、`['up', '-d', *services]` を組む。空なら現在と同じ `['up', '-d']` になる | | `_compose_run(subcommand, ...)` | 変更(`commands/container.py:269`) | `docker_compose` を経由せず直接 `subprocess.run` する経路(`ps` / `logs`)。同じ `env=` を組み立てて渡す(決定 7) | +| `_resolve_dev_service` / `_read_compose_services` | 変更(`commands/container.py`) | `config --format json` を直接呼ぶ経路。同じ `env=` を渡し、読むサービスの集合を devbase が決める(決定 7) | +| エディタを開く経路の `ps` | 変更(`editor/opener.py:395`) | `ps --format json` を直接呼ぶ経路。同じ `env=` を渡す(決定 7) | | `_run_deploy_pipeline(...)` | 変更(`commands/container.py`) | 生成した `.docker-compose.scale.yml` から `default_services` を求め、`docker_compose_up` へ渡す | | `docker_compose_down(compose_file)` | 変更(`utils/docker.py`) | F4。引数は増やさず、内部で無条件に `--profile '*'` を足す。`['down', '-t0']` という固定の形は変えない(決定 5) | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | @@ -234,7 +236,7 @@ tests/ ### 検査の手段 -`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境から `COMPOSE_PROFILES` が外れていることを検査する。同じテストで、`devbase up` の起動が既定のサービス名をすべて渡すことも検査する。 +`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境の `COMPOSE_PROFILES` が番兵の名前になっていることを検査する。同じテストで、`devbase up` の起動が既定のサービス名をすべて渡すことも検査する。 ## 処理の流れ From 66c6fbd841845dab13ccd1ac69e886090257e03b Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:19:48 +0900 Subject: [PATCH 17/22] =?UTF-8?q?docs(PLAN58):=20=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E5=90=8D=E3=82=92=E5=B1=95?= =?UTF-8?q?=E9=96=8B=E3=81=97=E3=81=A6=E3=81=8B=E3=82=89=E4=BD=BF=E3=81=84?= =?UTF-8?q?=E3=80=81=E5=8F=97=E3=81=91=E5=85=A5=E3=82=8C=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E3=82=92=20project=20=E8=A1=A8=E8=A8=98=E3=81=B8=E6=8F=83?= =?UTF-8?q?=E3=81=88=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生成物は profiles の変数の式をそのまま持つため、読み取った名前へ Compose と同じ 値で展開を掛ける(決定 1)。受け入れ条件の主語を推奨の devbase project profile へ 揃え、container / ct は等価であることの条件で担保する。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 6 ++++ issues/PLAN58_compose-profiles-design.md | 3 +- issues/PLAN58_compose-profiles.md | 31 +++++++++++---------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 57d6a5c2..4290f2f6 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -10,6 +10,12 @@ 要求仕様が `compose.yml` と書く箇所との対応は次のとおりである。利用者がプロファイルを宣言するのは `compose.yml` であり、devbase が読むのはその宣言を引き継いだ生成物である。宣言の内容は同じなので、受け入れ条件の文言は変えない。 +**名前は展開してから使う。** 生成物は元の `compose.yml` の値をそのまま持つため、`profiles: ["${TEST_PROFILE:-test}"]` のような変数の式が残る。Compose は実行時にこれを展開するので、devbase が式のまま扱うと、`profile list` が式を表示し、`profile up test` が未知の名前として弾かれる。 + +そこで読み取った名前へ、devbase が Compose へ渡すのと同じ値で変数の展開を掛ける。展開には既存の `_expand_env_vars`(`commands/container.py:416`)を使い、参照する値はプロジェクトの `env` と `.env` を読み込んだ後の環境とする。Compose が同じ順序で読む値と一致する。 + +展開しきれない値が残った場合(`$` が消えない)は、その名前を一覧から外し、警告を 1 行出す。式のままの名前は、どのプロファイル名とも一致しないためである。 + `docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 ### 決定 2: プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付ける diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 19d2eec4..93b40f7d 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -116,7 +116,7 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | -| `profile_services(compose: dict) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。純粋な処理で、終了コードも出力も持たない | +| `profile_services(compose: dict, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。名前は `_expand_env_vars` で展開してから使う(決定 1)。純粋な処理で、終了コードも出力も持たない | | `default_services(compose: dict) -> list[str]` | 新設(`commands/container.py`) | 構成の辞書から `profiles` を持たないサービス名を宣言順に返す。`cmd_up` が起動の対象として渡す(決定 7)。純粋な処理である | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | @@ -361,6 +361,7 @@ graph LR | `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 組み立てた `env` の値と、`up -d` の引数へ並ぶ既定のサービス名を検査する。既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成での実際の起動は手動確認で見る | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | +| `profiles` に変数の式が書かれていても名前が一致する | `profiles: ["${TEST_PROFILE:-test}"]` を持つ構成と `TEST_PROFILE` 未設定の環境を与え、`profile_services` が `test` を返すことを検査する。`list` の表示と `profile up test` が同じ名前で通ることも見る | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | | プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | | 一覧から実行しても dev が変わらない | 委譲へ渡る属性が `profile` のサブコマンドと名前であることを検査する。実際の状態は手動確認 | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 63105c30..85525b4d 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -64,25 +64,26 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `profiles: [X]` を持つサービスは `devbase up` で起動せず、`docker ps` に現れない - [ ] 前提: `devbase up` が済み、既定のサービスだけが動いている - 操作: `devbase container profile up X` を実行する + 操作: `devbase project profile up X` を実行する 結果: プロファイル X のサービスだけが起動し、既定のサービスの Container ID と `StartedAt` は変わらない - [ ] 前提: プロファイル X のサービスが動いている - 操作: `devbase container profile down X` を実行する + 操作: `devbase project profile down X` を実行する 結果: プロファイル X のサービスのコンテナだけが削除され、既定のサービスの Container ID と `StartedAt` は変わらない -- [ ] `devbase container profile up X` は、プロファイル X に属するサービスをすべて Compose へ渡し、`--no-deps` を付ける +- [ ] `devbase project profile up X` は、プロファイル X に属するサービスをすべて Compose へ渡し、`--no-deps` を付ける - [ ] 前提: プロファイル X のサービスが `depends_on: {dev: {condition: service_started, required: false}}` を持つ - 操作: `devbase container profile up X` を実行する + 操作: `devbase project profile up X` を実行する 結果: 既定のサービスの Container ID と `StartedAt` が変わらない -- [ ] `depends_on: [dev]`(`required` を書かない形)を持つサービスでも、`devbase container profile up X` で既定のサービスの Container ID と `StartedAt` は変わらない -- [ ] dev の環境変数の値を変えた後でも、`devbase container profile up X` は dev を再作成しない +- [ ] `depends_on: [dev]`(`required` を書かない形)を持つサービスでも、`devbase project profile up X` で既定のサービスの Container ID と `StartedAt` は変わらない +- [ ] dev の環境変数の値を変えた後でも、`devbase project profile up X` は dev を再作成しない - [ ] 前提: dev が `depends_on: {db: {condition: service_started, required: false}}` を持ち、db はプロファイル X に属する - 操作: `devbase container profile down X` を実行する + 操作: `devbase project profile down X` を実行する 結果: dev の Container ID と `StartedAt` が前後で変わらない -- [ ] `devbase container profile down X` の後も、そのサービスが使う名前付きボリュームは残る -- [ ] `devbase container profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す -- [ ] `.docker-compose.scale.yml` が無い状態では、`devbase container profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない +- [ ] `devbase project profile down X` の後も、そのサービスが使う名前付きボリュームは残る +- [ ] `devbase project profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す +- [ ] `.docker-compose.scale.yml` が無い状態では、`devbase project profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない - [ ] `compose.yml` に無いプロファイル名を `up` / `down` へ渡すと、存在する名前の一覧を出して終了コード 1 で止まる -- [ ] `devbase project profile up <プロジェクト> X` の結果は、そのプロジェクトのディレクトリで `devbase container profile up X` を実行した場合と同じになる。`down` と `list` も同じである +- [ ] `devbase project profile up <プロジェクト> X` の結果は、そのプロジェクトのディレクトリで `devbase project profile up X` を実行した場合と同じになる。`down` と `list` も同じである +- [ ] `devbase container profile ...` と `devbase ct profile ...` は `devbase project profile ...` と同じ結果になる。非推奨の警告を 1 行出す 停止の網羅: @@ -107,10 +108,10 @@ TUI: フック: - [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` から呼ばれるときは、どちらも空である -- [ ] `devbase container profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値はプロファイル名 1 つである +- [ ] `devbase project profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値はプロファイル名 1 つである - [ ] 同時に 2 つ以上のプロファイルを起動する操作は作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この変更では受け入れ条件にしない -- [ ] `devbase container profile up X` は `./pre-up` を呼ばない -- [ ] `devbase container profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる +- [ ] `devbase project profile up X` は `./pre-up` を呼ばない +- [ ] `devbase project profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる 退行しないこと: @@ -155,7 +156,7 @@ TUI: | テスト(TUI) | `uv run pytest tests/cli/tui -q`(メニューの項目と、委譲へ渡す属性の検査) | | テスト | `uv run pytest tests/ -q`(コマンドの組み立てと生成物の検証。実 docker には触れない) | | 静的解析 | `uv run ruff check lib/ tests/`(設定がある場合。無ければ省く) | -| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase container profile up X` → `devbase container profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | +| 手動確認 | `profiles` を付けた最小の compose(dev / app、`alpine:3`)で `devbase up` → `devbase project profile up X` → `devbase project profile down X` → `devbase down` を通し、各段で `docker ps` の Container ID と `StartedAt` を記録する。app へ `depends_on: {dev: {condition: service_started, required: false}}` を付けた版と、`depends_on: [dev]` を付けた版でも同じ手順を通す。さらに dev の環境変数の値を変えてから `profile up X` を実行し、dev が再作成されないことを確かめる | | 手動確認(依存の向きが逆の構成) | dev へ `depends_on: {db: {condition: service_started, required: false}}` を書き、db をプロファイル X に入れた構成で `profile up X` → `profile down X` を通す。停止の前後で dev の Container ID と `StartedAt` が変わらないことを見る。`stop` / `rm -f` が依存元を対象に含めないことの確認である(設計の決定 5) | | 手動確認(`COMPOSE_PROFILES` が設定された端末) | `COMPOSE_PROFILES=X` を環境変数に設定した状態と、プロジェクトの `.env` に書いた状態の両方で `devbase up` を通す。`docker ps` に既定のサービスだけが並ぶことを見る。`.env` の側は、起動のコマンド列に既定のサービス名が並ぶことも見る(設計の決定 7)。続けて `profile up X` → `profile down X` が従来どおり効くことも確かめる | | 手動確認(TUI) | `devbase list` を開き、起動中のプロジェクトで「テスト用サーバ起動」→ 一覧の STATUS のコンテナ数が増えることを見る。続けて「テスト用サーバ停止」で戻ることも見る。前後で dev-1..N の Container ID と `StartedAt` を比べる | From fa871e731d1878e72a7373c690bfc262b7ea843d Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:24:41 +0900 Subject: [PATCH 18/22] =?UTF-8?q?docs(PLAN58):=20=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E8=A7=A3=E6=B1=BA?= =?UTF-8?q?=E3=82=92=20Compose=20=E3=81=B8=E5=A7=94=E3=81=AD=E3=80=81?= =?UTF-8?q?=E5=A4=89=E6=95=B0=E3=81=AE=E5=B1=95=E9=96=8B=E3=82=92=E8=87=AA?= =?UTF-8?q?=E5=89=8D=E3=81=A7=E6=8C=81=E3=81=9F=E3=81=AA=E3=81=84=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _expand_env_vars は ${VAR:-default} を解釈しないため、生成物を自分で読む方式では 既定値付きの式を持つ構成を解決できない。config --profiles と config --services の 差分で対応を作る方式へ決定 1 を差し替えた。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 30 +++++++++++++++------ issues/PLAN58_compose-profiles-design.md | 14 +++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 4290f2f6..3d0406b1 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -4,19 +4,33 @@ ## 決定の記録 -### 決定 1: プロファイルの解決は生成済みの `.docker-compose.scale.yml` を読んで行う +### 決定 1: プロファイルの解決は Compose に行わせる -`devbase` が Compose へ渡すのはこのファイルである。プロファイルの割り当てもここで確定している。元の `compose.yml` を読むと、生成の過程で加わる差を二重に解釈することになる。その差は機密の列挙と dev の複製である。 +プロファイル名とサービスの対応は、`docker compose` へ問い合わせて得る。読むのは生成済みの `.docker-compose.scale.yml` で、`-f` で渡す。 -要求仕様が `compose.yml` と書く箇所との対応は次のとおりである。利用者がプロファイルを宣言するのは `compose.yml` であり、devbase が読むのはその宣言を引き継いだ生成物である。宣言の内容は同じなので、受け入れ条件の文言は変えない。 +| 求めるもの | 呼び方 | +| --- | --- | +| プロファイル名の一覧 | `config --profiles` | +| 既定のサービス | `config --services`(番兵を入れた環境で呼ぶ) | +| プロファイル X のサービス | `--profile X config --services` から既定のサービスを差し引く | + +**生成物を自分で読んで解決する案は採らない。** 生成物は元の `compose.yml` の値をそのまま持ち、`profiles: ["${TEST_PROFILE:-test}"]` のような変数の式が残る。Compose は実行時にこれを展開するため、式のまま扱うと `profile list` が式を表示し、`profile up test` が未知の名前になる。 + +**既存の `_expand_env_vars`(`commands/container.py:416`)で展開する案も採らない。** この関数が解釈するのは `$VAR` と `${VAR}` だけで、既定値付きの `${VAR:-default}` は残る。Compose の変数展開は既定値・必須・入れ子を持つ仕様であり、その部分実装を devbase 側に持つと、書ける構成と解決できる構成が食い違う。 -**名前は展開してから使う。** 生成物は元の `compose.yml` の値をそのまま持つため、`profiles: ["${TEST_PROFILE:-test}"]` のような変数の式が残る。Compose は実行時にこれを展開するので、devbase が式のまま扱うと、`profile list` が式を表示し、`profile up test` が未知の名前として弾かれる。 +実測(Docker Compose v5.1.4、`profiles: ["${TEST_PROFILE:-test}"]` と `profiles: [test]` を持つ構成、`.env` に `COMPOSE_PROFILES=test`): + +| 呼び方 | 返った値 | +| --- | --- | +| `config --profiles` | `test`(`TEST_PROFILE=demo` を与えると `demo`) | +| `config --services`(番兵あり) | `dev` | +| `--profile test config --services`(番兵あり) | `cache` `db` `dev` | -そこで読み取った名前へ、devbase が Compose へ渡すのと同じ値で変数の展開を掛ける。展開には既存の `_expand_env_vars`(`commands/container.py:416`)を使い、参照する値はプロジェクトの `env` と `.env` を読み込んだ後の環境とする。Compose が同じ順序で読む値と一致する。 +差し引きで `test` のサービスは `cache` と `db` になる。 -展開しきれない値が残った場合(`$` が消えない)は、その名前を一覧から外し、警告を 1 行出す。式のままの名前は、どのプロファイル名とも一致しないためである。 +**この解決には Docker への接続が要る。** `profile list` も同じである。プロファイルの起動と停止は元から Docker を使うため、新しく要るのは `list` だけである。接続できないときは、その旨を出して終了コード 1 で止まる。 -`docker compose config --services` を 2 回呼んで差を取る案は採らない。`list` のような読むだけの操作でも Docker デーモンへの接続が要る。変数の展開に失敗すると一覧すら出せない。 +`config --format json` を 1 回だけ呼ぶ案は採らない。**この出力は、有効でないプロファイルのサービスを含まない**(v5.1.4 で確認)。1 回の呼び出しでは対応を作れない。 ### 決定 2: プロファイルの操作はサービス名をすべて明示し、`--no-deps` を付ける @@ -109,7 +123,7 @@ キーを外すだけでは足りない。**Compose はプロジェクトの `.env` を自分で読み、環境変数が無ければその値を採る。** 値を入れておけば、環境変数がファイルより優先される規則に乗って `.env` の指定を無効にできる。 -**2. 起動の対象を明示する。** `devbase up` の起動は、**生成物から読んだ「`profiles` を持たないサービス」をサービス名としてすべて渡す**。1 が効かない形でプロファイルが有効になっても、対象に入らないサービスは起動しない。 +**2. 起動の対象を明示する。** `devbase up` の起動は、**`config --services` で得た既定のサービスをすべて渡す**(決定 1)。1 が効かない形でプロファイルが有効になっても、対象に入らないサービスは起動しない。 2 つは役割が違う。 diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 93b40f7d..9829fa19 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -17,7 +17,7 @@ | 要素 | 責務 | | --- | --- | -| プロファイルの解決 | 生成済みの構成ファイルを読み、プロファイル名からサービス名の集合を求める。`profiles` を持たない既定のサービスの一覧も同じ場所から求める。稼働状況は持たない | +| プロファイルの解決 | 生成済みの構成ファイルを `-f` で渡して `docker compose config` を呼び、プロファイル名とサービス名の対応を得る。既定のサービスの一覧も同じ経路で求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | | Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` には番兵の名前を入れて渡す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | @@ -116,8 +116,8 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | -| `profile_services(compose: dict, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | 構成の辞書から「プロファイル名 → サービス名」を作る。名前は `_expand_env_vars` で展開してから使う(決定 1)。純粋な処理で、終了コードも出力も持たない | -| `default_services(compose: dict) -> list[str]` | 新設(`commands/container.py`) | 構成の辞書から `profiles` を持たないサービス名を宣言順に返す。`cmd_up` が起動の対象として渡す(決定 7)。純粋な処理である | +| `profile_services(compose_file, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | `config --profiles` と `config --services` を呼び、「プロファイル名 → サービス名」を作る(決定 1)。Docker へ接続できないときは `DockerError` を送出する | +| `default_services(compose_file, environ) -> list[str]` | 新設(`commands/container.py`) | `config --services` を番兵入りの環境で呼び、既定のサービス名を返す。`cmd_up` が起動の対象として渡す(決定 1・決定 7) | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | @@ -126,7 +126,7 @@ tests/ | `_compose_run(subcommand, ...)` | 変更(`commands/container.py:269`) | `docker_compose` を経由せず直接 `subprocess.run` する経路(`ps` / `logs`)。同じ `env=` を組み立てて渡す(決定 7) | | `_resolve_dev_service` / `_read_compose_services` | 変更(`commands/container.py`) | `config --format json` を直接呼ぶ経路。同じ `env=` を渡し、読むサービスの集合を devbase が決める(決定 7) | | エディタを開く経路の `ps` | 変更(`editor/opener.py:395`) | `ps --format json` を直接呼ぶ経路。同じ `env=` を渡す(決定 7) | -| `_run_deploy_pipeline(...)` | 変更(`commands/container.py`) | 生成した `.docker-compose.scale.yml` から `default_services` を求め、`docker_compose_up` へ渡す | +| `_run_deploy_pipeline(...)` | 変更(`commands/container.py`) | 生成した `.docker-compose.scale.yml` を `config --services` へ渡して既定のサービスを求め、`docker_compose_up` へ渡す | | `docker_compose_down(compose_file)` | 変更(`utils/docker.py`) | F4。引数は増やさず、内部で無条件に `--profile '*'` を足す。`['down', '-t0']` という固定の形は変えない(決定 5) | | `hook_env(config, active_profiles=())` | 変更(`project/runtime.py`) | F5。`DEVBASE_ACTIVE_PROFILES` を足す。`_run_pre_up_hook` と `_run_deploy_script_for_instances` の両方に効く | | `_run_deploy_script_for_instances(deploy_script, indices, config=None, active_profiles=()) -> bool` | 変更(`commands/container.py`) | 全インスタンスで成功したかを返す。`active_profiles` を受け取り、加工せず `hook_env` へ渡す。`cmd_up` は戻り値を使わず、現在の警告だけの扱いを保つ | @@ -175,7 +175,7 @@ tests/ **2 項目が出るのは、そのプロジェクトがプロファイルを持つときだけである**(決定 8)。持たないプロジェクトでは一覧の中身が現在と同じになる。 -プロファイル名の選択は、名前が 1 つだけのときも選択として出す。名前は `.docker-compose.scale.yml` から読む。生成物が無い(`devbase up` の前)ときは 2 項目を出さない。 +プロファイル名の選択は、名前が 1 つだけのときも選択として出す。名前は `profile_services` が返す一覧を使う(決定 1)。生成物が無い(`devbase up` の前)ときと、Docker へ接続できないときは 2 項目を出さない。 実行は `dispatch_lifecycle('profile', name, profile_subcommand='up', profile='<名前>')` で共有のハンドラへ渡す。TUI はコマンドの中身を持たない。 @@ -302,7 +302,7 @@ sequenceDiagram | `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 番兵 | | `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 番兵 | -`devbase up` の `<既定のサービス...>` は、生成物のうち `profiles` を持たないサービスの全件である(決定 7)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 +`devbase up` の `<既定のサービス...>` は、`config --services` が返す既定のサービスの全件である(決定 1)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 `up` と `down` の冒頭の停止(F4)は次のように変わる。`COMPOSE_PROFILES` の除去は `docker_compose` と `_compose_run` の両方に共通で効く(決定 7)。 @@ -361,7 +361,7 @@ graph LR | `.env` に `COMPOSE_PROFILES` が書かれていても `devbase up` は既定のサービスだけを起動する | 組み立てた `env` の値と、`up -d` の引数へ並ぶ既定のサービス名を検査する。既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成での実際の起動は手動確認で見る | | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | -| `profiles` に変数の式が書かれていても名前が一致する | `profiles: ["${TEST_PROFILE:-test}"]` を持つ構成と `TEST_PROFILE` 未設定の環境を与え、`profile_services` が `test` を返すことを検査する。`list` の表示と `profile up test` が同じ名前で通ることも見る | +| `profiles` に変数の式が書かれていても名前が一致する | `config --profiles` と `config --services` の出力を差し替え、`profile_services` が展開後の名前で対応を作ることを検査する。実際の展開は Compose が行うため、式を持つ構成での `list` と `profile up test` は手動確認で見る | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | | プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | | 一覧から実行しても dev が変わらない | 委譲へ渡る属性が `profile` のサブコマンドと名前であることを検査する。実際の状態は手動確認 | From 4d67c078dd03e6953a72cd2e651ebc3e65593145 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:28:43 +0900 Subject: [PATCH 19/22] =?UTF-8?q?docs(PLAN58):=20=E3=83=95=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=81=AE=E5=AF=BE=E8=B1=A1=E3=82=92=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=89=A9=E3=81=AE=20dev-*=20=E3=81=AB=E5=90=88=E3=82=8F?= =?UTF-8?q?=E3=81=9B=E3=80=81=E5=A4=B1=E6=95=97=E7=B5=8C=E8=B7=AF=E3=81=AE?= =?UTF-8?q?=E6=A4=9C=E6=9F=BB=E3=82=92=E8=B6=B3=E3=81=99=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit project.yml の scale を使うと、up の後に設定を書き換えた場合に稼働中の インスタンスと食い違う。操作に使う生成物の dev-* を数える形へ改めた。 非推奨の警告と、Docker へ接続できないときの検査も足した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 13 ++++++++++++- issues/PLAN58_compose-profiles.md | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 9829fa19..ae6f18db 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -143,7 +143,16 @@ tests/ この変更では、渡る値は常にプロファイル名 1 つである。`cmd_profile_up` が受ける名前が 1 つだけで、同時に 2 つ以上を起動する操作を作らないためである。カンマ区切りは将来の拡張のための予約であり、現時点でその形になる経路は無い(「未確認のまま残ること」)。 -`_run_deploy_script_for_instances` へ渡す `indices` は `range(1, scale + 1)` である。`scale` は `project.yml` の `config.scale` から取る。未指定なら `project_runtime.DEFAULT_SCALE`(現在は 2)を使う。`cmd_up` が使っている解決の式をそのまま再利用する。`.docker-compose.scale.yml` の `dev-*` を数え直すことはしない。 +`_run_deploy_script_for_instances` へ渡す `indices` は、**操作に使う生成物が持つ `dev-*` の番号**である。`.docker-compose.scale.yml` の `services` から `dev-1`..`dev-N` を読み、その番号を渡す。 + +`project.yml` の `config.scale` は使わない。`up` の後に `project.yml` を書き換えてから `profile up X` を呼ぶと、設定の値と稼働中のインスタンスが食い違う。減らした後なら稼働中の `dev-2` へフックが走らず、増やした後なら作られていない番号へ走る。生成物は `up` が作ったもので、稼働中の構成と一致する。 + +| 何を数えるか | 減らした後 | 増やした後 | +| --- | --- | --- | +| `project.yml` の `config.scale` | 稼働中の `dev-2` を飛ばす | 未作成の番号へ走る | +| 生成物の `dev-*` | 稼働中の全インスタンスへ走る | 同左 | + +`cmd_up` は生成の直後に呼ぶため、どちらの数え方でも同じ値になる。こちらは現在の実装を変えない。 `profile` の subparser は `dest` を親と分ける。親の `project` / `container` は `dest='subcommand'` のままとし、入れ子側は `dest='profile_subcommand'` を使う。`cli.py` の `_dispatch` は `args.subcommand == 'list'` を見て `project list`(プロジェクト一覧)へ振り分けるためである。入れ子で `subcommand` を再利用すると、`devbase project profile list` がそちらへ流れてしまう。`_dispatch_lifecycle` の handlers には `'profile'` を 1 つだけ足す。その中で `profile_subcommand` を見て up / down / list を選ぶ。 @@ -362,6 +371,8 @@ graph LR | フックが `DEVBASE_ACTIVE_PROFILES` を受け取る | `hook_env` の戻り値と、`subprocess.run` へ渡された `env` を検査する。`cmd_up` 経由は空文字列、`cmd_profile_up` 経由はプロファイル名 1 つになることを見る。複数値はこの範囲では作れないため検査しない | | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | `profiles` に変数の式が書かれていても名前が一致する | `config --profiles` と `config --services` の出力を差し替え、`profile_services` が展開後の名前で対応を作ることを検査する。実際の展開は Compose が行うため、式を持つ構成での `list` と `profile up test` は手動確認で見る | +| `container profile` / `ct profile` が非推奨の警告を出す | 両方の入口を呼び、警告が 1 行だけ出ることと、委譲先の引数が `project profile` と同じであることを `caplog` で検査する | +| Docker へ接続できないとき `profile list` が 1 で止まる | `config --profiles` が失敗する状態を与え、終了コードとメッセージを検査する。コンテナを作る呼び出しが発生しないことも見る | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | | プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | | 一覧から実行しても dev が変わらない | 委譲へ渡る属性が `profile` のサブコマンドと名前であることを検査する。実際の状態は手動確認 | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 85525b4d..9644c9b5 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -109,6 +109,9 @@ TUI: - [ ] `./pre-up` と `./deploy` は `DEVBASE_ACTIVE_PROFILES` を受け取る。`devbase up` から呼ばれるときは、どちらも空である - [ ] `devbase project profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値はプロファイル名 1 つである +- [ ] 前提: `devbase up` を scale 2 で通した後、`project.yml` の `scale` を 1 へ書き換える + 操作: `devbase project profile up X` を実行する + 結果: 稼働中の dev-1 と dev-2 の両方で `./deploy` が実行される - [ ] 同時に 2 つ以上のプロファイルを起動する操作は作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この変更では受け入れ条件にしない - [ ] `devbase project profile up X` は `./pre-up` を呼ばない - [ ] `devbase project profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる From 3fec1f258d14be42fcf243dac0adde764e65eba8 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:33:27 +0900 Subject: [PATCH 20/22] =?UTF-8?q?docs(PLAN58):=20=E9=96=8B=E7=99=BA?= =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9=E5=90=8D=E3=82=92=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E3=81=9B=E3=81=9A=E3=80=81Docker=20=E6=9C=AA=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A=E3=81=AE=E5=8F=97=E3=81=91=E5=85=A5=E3=82=8C=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=82=92=E8=B6=B3=E3=81=99=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DEV_SERVICE_NAME を既定以外にしたプロジェクトでは生成物のサービス名が変わるため、 dev-* を固定で探すとフックが 1 件も走らない。get_dev_service_name() が返す名前を 使う契約にした。要求仕様に欠けていた Docker 未接続の条件も足した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-design.md | 9 ++++++--- issues/PLAN58_compose-profiles.md | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index ae6f18db..5c2be8e4 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -143,14 +143,16 @@ tests/ この変更では、渡る値は常にプロファイル名 1 つである。`cmd_profile_up` が受ける名前が 1 つだけで、同時に 2 つ以上を起動する操作を作らないためである。カンマ区切りは将来の拡張のための予約であり、現時点でその形になる経路は無い(「未確認のまま残ること」)。 -`_run_deploy_script_for_instances` へ渡す `indices` は、**操作に使う生成物が持つ `dev-*` の番号**である。`.docker-compose.scale.yml` の `services` から `dev-1`..`dev-N` を読み、その番号を渡す。 +`_run_deploy_script_for_instances` へ渡す `indices` は、**操作に使う生成物が持つ開発コンテナの番号**である。`.docker-compose.scale.yml` の `services` から `<開発サービス名>-1`..`<開発サービス名>-N` を読み、その番号を渡す。 + +**開発サービス名は固定ではない。** `get_dev_service_name()`(`DEV_SERVICE_NAME` または既定の `dev`)が返す名前を使う。生成処理も同じ名前で複製するため(`volume/compose.py` の `_build_scaled_services`)、`dev-*` を固定で探すと、既定以外の名前を使うプロジェクトで 1 件も拾えない。 `project.yml` の `config.scale` は使わない。`up` の後に `project.yml` を書き換えてから `profile up X` を呼ぶと、設定の値と稼働中のインスタンスが食い違う。減らした後なら稼働中の `dev-2` へフックが走らず、増やした後なら作られていない番号へ走る。生成物は `up` が作ったもので、稼働中の構成と一致する。 | 何を数えるか | 減らした後 | 増やした後 | | --- | --- | --- | -| `project.yml` の `config.scale` | 稼働中の `dev-2` を飛ばす | 未作成の番号へ走る | -| 生成物の `dev-*` | 稼働中の全インスタンスへ走る | 同左 | +| `project.yml` の `config.scale` | 稼働中の 2 台目を飛ばす | 未作成の番号へ走る | +| 生成物の開発コンテナ | 稼働中の全インスタンスへ走る | 同左 | `cmd_up` は生成の直後に呼ぶため、どちらの数え方でも同じ値になる。こちらは現在の実装を変えない。 @@ -372,6 +374,7 @@ graph LR | フックの失敗が終了コードへ出る | 失敗する `./deploy` を置き、戻り値が 1 になることを検査する | | `profiles` に変数の式が書かれていても名前が一致する | `config --profiles` と `config --services` の出力を差し替え、`profile_services` が展開後の名前で対応を作ることを検査する。実際の展開は Compose が行うため、式を持つ構成での `list` と `profile up test` は手動確認で見る | | `container profile` / `ct profile` が非推奨の警告を出す | 両方の入口を呼び、警告が 1 行だけ出ることと、委譲先の引数が `project profile` と同じであることを `caplog` で検査する | +| `DEV_SERVICE_NAME` が既定以外でもフックが全インスタンスへ走る | `DEV_SERVICE_NAME=workspace` と scale 2 の生成物を与え、`_run_deploy_script_for_instances` へ渡る番号が 1 と 2 になることを検査する | | Docker へ接続できないとき `profile list` が 1 で止まる | `config --profiles` が失敗する状態を与え、終了コードとメッセージを検査する。コンテナを作る呼び出しが発生しないことも見る | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | | プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 9644c9b5..19466b37 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -82,6 +82,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `devbase project profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase project profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない - [ ] `compose.yml` に無いプロファイル名を `up` / `down` へ渡すと、存在する名前の一覧を出して終了コード 1 で止まる +- [ ] Docker へ接続できない状態では、`devbase project profile up X` / `down X` / `list` のいずれも、その旨を出して終了コード 1 で止まる - [ ] `devbase project profile up <プロジェクト> X` の結果は、そのプロジェクトのディレクトリで `devbase project profile up X` を実行した場合と同じになる。`down` と `list` も同じである - [ ] `devbase container profile ...` と `devbase ct profile ...` は `devbase project profile ...` と同じ結果になる。非推奨の警告を 1 行出す @@ -111,7 +112,8 @@ TUI: - [ ] `devbase project profile up X` の後の `./deploy` は `DEVBASE_ACTIVE_PROFILES=X` を受け取る。値はプロファイル名 1 つである - [ ] 前提: `devbase up` を scale 2 で通した後、`project.yml` の `scale` を 1 へ書き換える 操作: `devbase project profile up X` を実行する - 結果: 稼働中の dev-1 と dev-2 の両方で `./deploy` が実行される + 結果: 稼働中の 2 つの開発コンテナの両方で `./deploy` が実行される +- [ ] `DEV_SERVICE_NAME` を既定以外(例: `workspace`)にしたプロジェクトでも、scale 2 の状態で `devbase project profile up X` の後に 2 インスタンスとも `./deploy` が実行される - [ ] 同時に 2 つ以上のプロファイルを起動する操作は作らない。よって複数の値が渡る経路は無い。カンマ区切りは将来の拡張のための予約であり、この変更では受け入れ条件にしない - [ ] `devbase project profile up X` は `./pre-up` を呼ばない - [ ] `devbase project profile up X` は、サービスの起動が終わった後にプロジェクトのフックを呼ぶ。フックが終了コード 0 以外を返したら、コマンドも 0 以外で終わる From 86dc3ec4748846abcdadb0d693bd8aac05a81aa3 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 13:37:40 +0900 Subject: [PATCH 21/22] =?UTF-8?q?docs(PLAN58):=20=E8=A7=A3=E6=B1=BA?= =?UTF-8?q?=E3=81=AB=E3=83=87=E3=83=BC=E3=83=A2=E3=83=B3=E3=81=8C=E8=A6=81?= =?UTF-8?q?=E3=82=89=E3=81=AA=E3=81=84=E3=81=93=E3=81=A8=E3=82=92=E5=AE=9F?= =?UTF-8?q?=E6=B8=AC=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=80=81list=20?= =?UTF-8?q?=E3=81=AE=E5=A4=B1=E6=95=97=E3=81=AE=E5=BD=A2=E3=82=92=E5=88=86?= =?UTF-8?q?=E3=81=91=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config はデーモンへ問い合わせないため、接続できない状態でも 0 で返る(v5.1.4 で 実測)。list は名前と対応を出し、稼働状況を不明にして 0 で終わる契約にした。 profile_services の責務にプロファイルごとの呼び出しを明記した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 15 ++++++++++++++- issues/PLAN58_compose-profiles-design.md | 9 +++++---- issues/PLAN58_compose-profiles.md | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 3d0406b1..1a523432 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -28,7 +28,20 @@ 差し引きで `test` のサービスは `cache` と `db` になる。 -**この解決には Docker への接続が要る。** `profile list` も同じである。プロファイルの起動と停止は元から Docker を使うため、新しく要るのは `list` だけである。接続できないときは、その旨を出して終了コード 1 で止まる。 +**この解決にデーモンへの接続は要らない。** `config` は構成を解釈するだけで、デーモンへ問い合わせない。実測(Docker Compose v5.1.4、`DOCKER_HOST` を存在しないソケットへ向けた状態)では次のようになった。 + +| 呼び方 | 終了コード | +| --- | --- | +| `config --profiles` / `config --services` | 0 | +| `ps` | 1 | +| `up -d <サービス>` | 1 | + +そこで失敗の扱いを 2 つに分ける。**名前と対応は接続できなくても出せる。** 稼働状況は `ps` が要るため出せない。 + +| 操作 | デーモンへ接続できないとき | +| --- | --- | +| `profile list` | 名前と対応を出し、稼働状況の列を `不明` にして終了コード 0 | +| `profile up` / `profile down` | Compose が 1 で落ちる。その終了コードをそのまま返す | `config --format json` を 1 回だけ呼ぶ案は採らない。**この出力は、有効でないプロファイルのサービスを含まない**(v5.1.4 で確認)。1 回の呼び出しでは対応を作れない。 diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 5c2be8e4..7ef3ef32 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -116,7 +116,7 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | -| `profile_services(compose_file, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | `config --profiles` と `config --services` を呼び、「プロファイル名 → サービス名」を作る(決定 1)。Docker へ接続できないときは `DockerError` を送出する | +| `profile_services(compose_file, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | `config --profiles` で名前を取り、各プロファイル X について `--profile X config --services` を呼び、`config --services`(既定のサービス)を差し引いて対応を作る(決定 1)。デーモンへの接続は要らない | | `default_services(compose_file, environ) -> list[str]` | 新設(`commands/container.py`) | `config --services` を番兵入りの環境で呼び、既定のサービス名を返す。`cmd_up` が起動の対象として渡す(決定 1・決定 7) | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | @@ -168,7 +168,7 @@ tests/ | --- | --- | --- | --- | | `devbase project profile up [name] ` | プロファイル名(必須)、プロジェクト名(省略時は現在地) | 対象サービスを起動し、フックを実行して 0 | 構成ファイルが無い / 未知のプロファイル / Compose かフックが失敗 → 1 | | `devbase project profile down [name] ` | 同上 | 対象サービスを停止し、そのコンテナを削除して 0 | 同上。`stop` と `rm -f` のどちらかが失敗すれば 1(フックは呼ばない) | -| `devbase project profile list [name]` | プロジェクト名(省略可) | プロファイルと稼働状況を表で出して 0 | 構成ファイルが無い → 1 | +| `devbase project profile list [name]` | プロジェクト名(省略可) | プロファイルと稼働状況を表で出して 0 | 構成ファイルが無い → 1。デーモンへ接続できないときは稼働状況を `不明` にして 0 | | `devbase container profile up ` / `down ` / `list`(`ct` も同じ) | プロファイル名のみ。プロジェクト名は受け付けない | 同上。非推奨の警告を 1 行出す | 同上 | `project` の `[name]` と `` の並びは既存の `scale` と同じ規則に従う。値が 1 個ならプロファイル名に割り当てられる。2 個なら(プロジェクト名、プロファイル名)になる。 @@ -186,7 +186,7 @@ tests/ **2 項目が出るのは、そのプロジェクトがプロファイルを持つときだけである**(決定 8)。持たないプロジェクトでは一覧の中身が現在と同じになる。 -プロファイル名の選択は、名前が 1 つだけのときも選択として出す。名前は `profile_services` が返す一覧を使う(決定 1)。生成物が無い(`devbase up` の前)ときと、Docker へ接続できないときは 2 項目を出さない。 +プロファイル名の選択は、名前が 1 つだけのときも選択として出す。名前は `profile_services` が返す一覧を使う(決定 1)。生成物が無い(`devbase up` の前)ときは 2 項目を出さない。解決にデーモンは要らないため、接続できない状態でも項目は出る。 実行は `dispatch_lifecycle('profile', name, profile_subcommand='up', profile='<名前>')` で共有のハンドラへ渡す。TUI はコマンドの中身を持たない。 @@ -375,7 +375,8 @@ graph LR | `profiles` に変数の式が書かれていても名前が一致する | `config --profiles` と `config --services` の出力を差し替え、`profile_services` が展開後の名前で対応を作ることを検査する。実際の展開は Compose が行うため、式を持つ構成での `list` と `profile up test` は手動確認で見る | | `container profile` / `ct profile` が非推奨の警告を出す | 両方の入口を呼び、警告が 1 行だけ出ることと、委譲先の引数が `project profile` と同じであることを `caplog` で検査する | | `DEV_SERVICE_NAME` が既定以外でもフックが全インスタンスへ走る | `DEV_SERVICE_NAME=workspace` と scale 2 の生成物を与え、`_run_deploy_script_for_instances` へ渡る番号が 1 と 2 になることを検査する | -| Docker へ接続できないとき `profile list` が 1 で止まる | `config --profiles` が失敗する状態を与え、終了コードとメッセージを検査する。コンテナを作る呼び出しが発生しないことも見る | +| デーモンへ接続できないとき `profile list` が稼働状況を `不明` にして 0 で終わる | `ps` が失敗する状態を与え、名前と対応が出ること、稼働状況の列が `不明` になること、終了コードが 0 であることを検査する | +| デーモンへ接続できないとき `profile up` / `down` が 1 で終わる | Compose の終了コード 1 をそのまま返すことを検査する | | 一覧の操作に 2 項目が並ぶ | プロファイルを持つ構成で `_running_ops` の戻り値を検査する | | プロファイルを持たないプロジェクトでは 2 項目が出ない | 同じ関数へプロファイルの無い構成を与え、現在と同じ並びになることを検査する | | 一覧から実行しても dev が変わらない | 委譲へ渡る属性が `profile` のサブコマンドと名前であることを検査する。実際の状態は手動確認 | diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index 19466b37..af5afacf 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -82,7 +82,8 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 - [ ] `devbase project profile list` は、`compose.yml` に書かれたプロファイルの名前と、そのサービスが稼働しているかを出す - [ ] `.docker-compose.scale.yml` が無い状態では、`devbase project profile up X` / `down X` / `list` のいずれも終了コード 1 で止まる。`devbase up` を促すメッセージを出し、コンテナは作らない - [ ] `compose.yml` に無いプロファイル名を `up` / `down` へ渡すと、存在する名前の一覧を出して終了コード 1 で止まる -- [ ] Docker へ接続できない状態では、`devbase project profile up X` / `down X` / `list` のいずれも、その旨を出して終了コード 1 で止まる +- [ ] Docker のデーモンへ接続できない状態では、`devbase project profile up X` / `down X` は Compose の終了コード 1 をそのまま返す +- [ ] 同じ状態で `devbase project profile list` は、プロファイル名と対応を出し、稼働状況の列を `不明` にして終了コード 0 で終わる - [ ] `devbase project profile up <プロジェクト> X` の結果は、そのプロジェクトのディレクトリで `devbase project profile up X` を実行した場合と同じになる。`down` と `list` も同じである - [ ] `devbase container profile ...` と `devbase ct profile ...` は `devbase project profile ...` と同じ結果になる。非推奨の警告を 1 行出す From 175425fcece7e38f7feb60c0be9623ba9778fe2f Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Wed, 16 Sep 2026 22:34:06 +0900 Subject: [PATCH 22/22] =?UTF-8?q?docs(PLAN58):=20=E7=95=AA=E5=85=B5?= =?UTF-8?q?=E3=81=A8=E3=81=84=E3=81=86=E8=AA=9E=E3=82=92=E6=89=93=E3=81=A1?= =?UTF-8?q?=E6=B6=88=E3=81=97=E7=94=A8=E3=81=AE=E3=83=97=E3=83=AD=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E5=90=8D=E3=81=B8=E8=A8=80=E3=81=84?= =?UTF-8?q?=E6=8F=9B=E3=81=88=E3=82=8B=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 読み手が知らない語を説明の主語に使わない(markdown-writing のルール 1)。 用語表へ定義を置き、初出で「以下ではこれを打ち消し用のプロファイル名と呼ぶ」と 断ってから使う形にした。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDhpEuWLgcBNgfFvmeFSP1 --- issues/PLAN58_compose-profiles-decisions.md | 28 ++++++++--------- issues/PLAN58_compose-profiles-design.md | 34 ++++++++++----------- issues/PLAN58_compose-profiles.md | 5 +-- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/issues/PLAN58_compose-profiles-decisions.md b/issues/PLAN58_compose-profiles-decisions.md index 1a523432..ef7febd5 100644 --- a/issues/PLAN58_compose-profiles-decisions.md +++ b/issues/PLAN58_compose-profiles-decisions.md @@ -11,7 +11,7 @@ | 求めるもの | 呼び方 | | --- | --- | | プロファイル名の一覧 | `config --profiles` | -| 既定のサービス | `config --services`(番兵を入れた環境で呼ぶ) | +| 既定のサービス | `config --services`(打ち消し用のプロファイル名を入れた環境で呼ぶ) | | プロファイル X のサービス | `--profile X config --services` から既定のサービスを差し引く | **生成物を自分で読んで解決する案は採らない。** 生成物は元の `compose.yml` の値をそのまま持ち、`profiles: ["${TEST_PROFILE:-test}"]` のような変数の式が残る。Compose は実行時にこれを展開するため、式のまま扱うと `profile list` が式を表示し、`profile up test` が未知の名前になる。 @@ -23,8 +23,8 @@ | 呼び方 | 返った値 | | --- | --- | | `config --profiles` | `test`(`TEST_PROFILE=demo` を与えると `demo`) | -| `config --services`(番兵あり) | `dev` | -| `--profile test config --services`(番兵あり) | `cache` `db` `dev` | +| `config --services`(打ち消し用のプロファイル名あり) | `dev` | +| `--profile test config --services`(打ち消し用のプロファイル名あり) | `cache` `db` `dev` | 差し引きで `test` のサービスは `cache` と `db` になる。 @@ -132,7 +132,7 @@ そこで対策を 2 つ重ねる。**環境変数を子へ渡さないことと、起動の対象をサービス名で明示することである。** -**1. 環境変数を devbase の値で上書きする。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製の `COMPOSE_PROFILES` へ、どのプロジェクトも定義しない番兵の名前 `__devbase_none__` を入れて渡す。有効なプロファイルは経路ごとに `--profile` で明示する。 +**1. 環境変数を devbase の値で上書きする。** `docker_compose` で `env=` を新たに構築する。`os.environ` の複製の `COMPOSE_PROFILES` へ、**どのプロジェクトも定義しない名前 `__devbase_none__`** を入れて渡す。以下ではこれを打ち消し用のプロファイル名と呼ぶ。有効なプロファイルは経路ごとに `--profile` で明示する。 キーを外すだけでは足りない。**Compose はプロジェクトの `.env` を自分で読み、環境変数が無ければその値を採る。** 値を入れておけば、環境変数がファイルより優先される規則に乗って `.env` の指定を無効にできる。 @@ -142,10 +142,10 @@ | 対策 | 何を防ぐか | | --- | --- | -| 番兵の名前を渡す | 端末の環境変数と `.env` の両方によるプロファイルの有効化。依存の待ち合わせはそのまま残る | +| 打ち消し用のプロファイル名を渡す | 端末の環境変数と `.env` の両方によるプロファイルの有効化。依存の待ち合わせはそのまま残る | | 対象を明示する | 対象の広がり。`up` に名前を渡すことで、一覧に無いサービスは起動しない | -**番兵だけで `depends_on` 経由の起動も止まる。** 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成では、プロファイルが有効なかぎり依存先として起動する。対象の明示では止まらない。番兵で無効にすれば起動しない。`--no-deps` で止める案は採らない。既定のサービスどうしの `depends_on` の待ち合わせまで失う。 +**打ち消し用のプロファイル名を入れるだけで `depends_on` 経由の起動も止まる。** 既定のサービスがプロファイルのサービスへ `depends_on` を持つ構成では、プロファイルが有効なかぎり依存先として起動する。対象の明示では止まらない。打ち消し用のプロファイル名で無効にすれば起動しない。`--no-deps` で止める案は採らない。既定のサービスどうしの `depends_on` の待ち合わせまで失う。 実測(Docker Compose v5.1.4、`.env` に `COMPOSE_PROFILES=test`、dev が `depends_on: {db: {condition: service_started, required: false}}` を持つ構成): @@ -160,10 +160,10 @@ | 経路 | プロファイルの指定 | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | -| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 番兵の名前を入れる | -| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 番兵の名前を入れる | -| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 番兵の名前を入れる | -| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 番兵の名前を入れる | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 打ち消し用のプロファイル名を入れる | +| `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない(全体が対象) | 打ち消し用のプロファイル名を入れる | +| `profile up X` | `--profile X` | そのプロファイルのサービスをすべて明示し、`--no-deps` を付ける | 打ち消し用のプロファイル名を入れる | +| `profile down X` | `--profile X` | 同じ一覧を `stop` と `rm -f` へ渡す | 打ち消し用のプロファイル名を入れる | **この方式の前提と限界。** 前提は、生成物が `up` のたびに作り直されることである。限界は、対象を明示するため、生成物に無いサービスは `up` で起動しないことである。生成物には必ず `dev-1`..`dev-N` が入るため、一覧が空になることはない。 @@ -184,9 +184,9 @@ `ps` / `logs` はコンテナを起動しない読み取りの操作である。それでも対象に含めるのは、`COMPOSE_PROFILES` が効くと Compose が解釈するサービスの集合が変わり、表示の中身が利用者の環境に左右されるためである。devbase の表示は、devbase が決めたプロファイルに揃える。 -**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。番兵の名前なら、どちらに解釈されても「その名前のプロファイルは無い」という同じ結果になる。 +**空文字列にはしない。** `COMPOSE_PROFILES=` を渡す形は、版によって「空の一覧」と「未設定」のどちらに解釈されるかを調べる必要が出る。打ち消し用のプロファイル名なら、どちらに解釈されても「その名前のプロファイルは無い」という同じ結果になる。 -**番兵の名前は `__devbase_none__` とする。** プロジェクトがこの名前のプロファイルを定義すると、そのプロファイルが常に有効になる。前置きの下線 2 つは Compose の慣習的な名前と衝突しにくく、文書でこの名前を予約として案内する。 +**打ち消し用のプロファイル名は `__devbase_none__` とする。** プロジェクトがこの名前のプロファイルを定義すると、そのプロファイルが常に有効になる。前置きの下線 2 つは Compose の慣習的な名前と衝突しにくく、文書でこの名前を予約として案内する。 **`.env` は書き換えない。** `.env` は利用者とプロジェクトの持ち物であり、devbase が値を消すと素の `docker compose` を叩いたときの挙動まで変わる。環境変数の除去と起動の対象の明示なら、影響は devbase 経由の呼び出しだけに閉じる。 @@ -195,9 +195,9 @@ | `.env` から `COMPOSE_PROFILES` を消す | 素の `docker compose` にも及ぶ | 不可。利用者の持ち物を変える | | 環境変数のキーを外す | devbase 経由のみ。`.env` には効かない | 足りない。`.env` の値がそのまま効く | | 環境変数を空文字列にする | devbase 経由のみ | 不可。空の解釈が版に依る | -| 環境変数へ番兵の名前を入れ、起動の対象も既定のサービスへ限る | devbase 経由のみ | 可 | +| 環境変数へ打ち消し用のプロファイル名を入れ、起動の対象も既定のサービスへ限る | devbase 経由のみ | 可 | -`--profile` を明示する経路では、番兵を入れても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 +`--profile` を明示する経路では、打ち消し用のプロファイル名を入れても対象は変わらない。`--profile` と `COMPOSE_PROFILES` は和集合として扱われるため、外して困るのは「環境変数だけでプロファイルを有効にしていた」場合である。devbase はその使い方を約束していない。プロファイルの起動は `profile up` が唯一の入口である。 ### 決定 8: 一覧の 2 項目は、プロファイルを持つプロジェクトにだけ出す diff --git a/issues/PLAN58_compose-profiles-design.md b/issues/PLAN58_compose-profiles-design.md index 7ef3ef32..723df0d6 100644 --- a/issues/PLAN58_compose-profiles-design.md +++ b/issues/PLAN58_compose-profiles-design.md @@ -19,7 +19,7 @@ | --- | --- | | プロファイルの解決 | 生成済みの構成ファイルを `-f` で渡して `docker compose config` を呼び、プロファイル名とサービス名の対応を得る。既定のサービスの一覧も同じ経路で求める。稼働状況は持たない | | プロファイルの操作 | 起動・停止・一覧の 3 つの入口。接続先の反映と機密の注入を済ませてから Compose を呼ぶ | -| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` には番兵の名前を入れて渡す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | +| Compose の呼び出し | `docker compose` のコマンド列を組み立てて実行する。起動には `--no-deps` を付ける。プロファイルの停止は `stop` と `rm -f` の 2 段で行う。全体の停止には全プロファイルを指定する。有効なプロファイルは devbase が `--profile` で決め、`COMPOSE_PROFILES` には打ち消し用のプロファイル名を入れて渡す。`devbase up` の起動は既定のサービスを明示して渡す(決定 7) | | フックの実行 | プロジェクトの `./deploy` を、有効なプロファイルを環境変数へ載せて呼ぶ。同じ環境変数は `./pre-up` にも渡る | | 引数の受け口 | `project` / `container` の配下に `profile` のサブコマンドを足す | | 一覧の操作メニュー | `devbase list` の起動中の行で、プロファイルの起動・停止を選ばせる。選ばせた後は共有のハンドラへ委譲する(F6) | @@ -94,7 +94,7 @@ lib/devbase/ ├── tui/ │ └── actions_project.py # 起動中の行の操作へ profile の 2 項目を足す(変更) ├── utils/ -│ └── docker.py # docker_compose が env= を組んで番兵を入れる、docker_compose_up が対象サービスを受ける、docker_compose_down が全プロファイルを対象にする(変更) +│ └── docker.py # docker_compose が env= を組んで打ち消し用のプロファイル名を入れる、docker_compose_up が対象サービスを受ける、docker_compose_down が全プロファイルを対象にする(変更) └── volume/ └── compose.py # profiles を保つことの確認のみ(変更なし) @@ -105,7 +105,7 @@ tests/ ├── commands/ │ └── test_container_profile.py # 新設 ├── utils/ -│ └── test_docker_profiles.py # 新設(子プロセスの env の COMPOSE_PROFILES が番兵の名前になることの検査) +│ └── test_docker_profiles.py # 新設(子プロセスの env の COMPOSE_PROFILES が打ち消し用のプロファイル名になることの検査) └── volume/ └── test_compose_profiles.py # 新設 ``` @@ -117,11 +117,11 @@ tests/ | 関数 | 変更 | 責務 | | --- | --- | --- | | `profile_services(compose_file, environ) -> dict[str, list[str]]` | 新設(`commands/container.py`) | `config --profiles` で名前を取り、各プロファイル X について `--profile X config --services` を呼び、`config --services`(既定のサービス)を差し引いて対応を作る(決定 1)。デーモンへの接続は要らない | -| `default_services(compose_file, environ) -> list[str]` | 新設(`commands/container.py`) | `config --services` を番兵入りの環境で呼び、既定のサービス名を返す。`cmd_up` が起動の対象として渡す(決定 1・決定 7) | +| `default_services(compose_file, environ) -> list[str]` | 新設(`commands/container.py`) | `config --services` を打ち消し用のプロファイル名を入れた環境で呼び、既定のサービス名を返す。`cmd_up` が起動の対象として渡す(決定 1・決定 7) | | `cmd_profile_up(profile, context)` | 新設 | F1。プロファイルのサービスをすべて明示し、`--no-deps` を付けて起動する(決定 2)。終了コードを返す | | `cmd_profile_down(profile, context)` | 新設 | F2。`docker_compose_down` は通さず、`stop` と `rm -f` の 2 段をサービス名付きで組む(決定 5)。終了コードを返す | | `cmd_profile_list(context)` | 新設 | F3。終了コードを返す | -| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` へ番兵の名前 `__devbase_none__` を入れて渡す(決定 7)。コマンド列の組み立て方は変えない | +| `docker_compose(command, ...)` | 変更(`utils/docker.py`) | F1 / F2 / F4 の共通の土台。現在は `subprocess.run(cmd, ...)` を `env=` なしで呼ぶ。`env=` を新たに構築し、`COMPOSE_PROFILES` へ打ち消し用のプロファイル名 `__devbase_none__` を入れて渡す(決定 7)。コマンド列の組み立て方は変えない | | `docker_compose_up(compose_file, detach=True, services=())` | 変更(`utils/docker.py`) | 起動の対象を受け取り、`['up', '-d', *services]` を組む。空なら現在と同じ `['up', '-d']` になる | | `_compose_run(subcommand, ...)` | 変更(`commands/container.py:269`) | `docker_compose` を経由せず直接 `subprocess.run` する経路(`ps` / `logs`)。同じ `env=` を組み立てて渡す(決定 7) | | `_resolve_dev_service` / `_read_compose_services` | 変更(`commands/container.py`) | `config --format json` を直接呼ぶ経路。同じ `env=` を渡し、読むサービスの集合を devbase が決める(決定 7) | @@ -196,11 +196,11 @@ tests/ | 経路 | `--profile` | 対象の渡し方 | `COMPOSE_PROFILES` | | --- | --- | --- | --- | -| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 番兵の名前 `__devbase_none__` を入れる | +| `devbase up` の起動 | 付けない | 既定のサービスをすべて明示する | 打ち消し用のプロファイル名 `__devbase_none__` を入れる | | `devbase down` と `up` 冒頭の停止 | `--profile '*'` | 渡さない | 同上 | | `profile up X` / `profile down X` | `--profile X` | 対象のサービスをすべて明示する | 同上 | -`COMPOSE_PROFILES` は空文字列にしない。どのプロジェクトも定義しない番兵の名前を入れる。空文字列の扱いが版で違う可能性を調べずに済むためである。 +`COMPOSE_PROFILES` は空文字列にしない。どのプロジェクトも定義しない打ち消し用のプロファイル名を入れる。空文字列の扱いが版で違う可能性を調べずに済むためである。 **キーを外すだけでは足りない。** Compose は環境変数が無ければプロジェクトの `.env` を読む。値を入れて上書きし、あわせて `devbase up` の起動では既定のサービス名も明示する(決定 7)。 @@ -247,7 +247,7 @@ tests/ ### 検査の手段 -`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境の `COMPOSE_PROFILES` が番兵の名前になっていることを検査する。同じテストで、`devbase up` の起動が既定のサービス名をすべて渡すことも検査する。 +`uv run pytest tests/commands/test_container_profile.py -q` が、組み立てたコマンド列と終了コードを検査する。`uv run pytest tests/utils/test_docker_profiles.py -q` が、子プロセスへ渡す環境の `COMPOSE_PROFILES` が打ち消し用のプロファイル名になっていることを検査する。同じテストで、`devbase up` の起動が既定のサービス名をすべて渡すことも検査する。 ## 処理の流れ @@ -307,11 +307,11 @@ sequenceDiagram | 操作 | コマンド列 | 子プロセスの `COMPOSE_PROFILES` | | --- | --- | --- | -| プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 番兵 | -| プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 番兵 | -| プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 番兵 | -| `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 番兵 | -| `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 番兵 | +| プロファイルの起動 | `['--profile', X, 'up', '-d', '--no-deps', <サービス...>]` | 打ち消し用の名前 | +| プロファイルの停止(1 段目) | `['--profile', X, 'stop', <サービス...>]` | 打ち消し用の名前 | +| プロファイルの停止(2 段目) | `['--profile', X, 'rm', '-f', <サービス...>]` | 打ち消し用の名前 | +| `devbase up` の起動 | `['up', '-d', <既定のサービス...>]`(`--profile` を付けない) | 打ち消し用の名前 | +| `devbase down` / `up` 冒頭の停止 | `['--profile', '*', 'down', '-t0']` | 打ち消し用の名前 | `devbase up` の `<既定のサービス...>` は、`config --services` が返す既定のサービスの全件である(決定 1)。`--profile` を付けないことと合わせて、プロファイルのサービスは対象に入らない。 @@ -319,9 +319,9 @@ sequenceDiagram ```mermaid graph LR - A[devbase down] --> B["env の COMPOSE_PROFILES へ番兵を入れる → compose --profile '*' down -t0"] - C[devbase up] --> D["env の COMPOSE_PROFILES へ番兵を入れる → compose --profile '*' down -t0"] - D --> E["env の COMPOSE_PROFILES へ番兵を入れる → compose up -d 既定のサービス一覧、--profile なし"] + A[devbase down] --> B["env の COMPOSE_PROFILES へ打ち消し用の名前を入れる → compose --profile '*' down -t0"] + C[devbase up] --> D["env の COMPOSE_PROFILES へ打ち消し用の名前を入れる → compose --profile '*' down -t0"] + D --> E["env の COMPOSE_PROFILES へ打ち消し用の名前を入れる → compose up -d 既定のサービス一覧、--profile なし"] E --> F[既定のサービスだけが動く] ``` @@ -333,7 +333,7 @@ graph LR | --- | --- | --- | --- | | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | 対象サービス名とプロファイル名を `logger.info` で 1 行ずつ出す。Compose の出力はそのまま標準出力へ流す | `caplog` で起動・停止の各 1 行を検査する | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る | 生成済みの `.docker-compose.scale.yml` をそのまま使う。機密の列挙はこのファイルが既に持つため、プロファイルの操作は新しい注入経路を作らない。実行前に `_prepare_compose` で既存と同じ注入を通す | 生成物のサービスごとの `environment` が、プロファイルの有無で変わらないことをテストで検査する | -| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` へ番兵の名前を入れ、`devbase up` の起動では既定のサービスを明示する(決定 7)。サービスを明示した `up` も 2 系全般で使えるため、どちらも版の下限を作らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を環境変数と `.env` の両方に置いて `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | +| システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である | 最低対応版を 2.20.0 とする。`--no-deps` は起動にだけ、`--profile '*'` は全体の停止にだけ使う。プロファイルの停止は `stop` と `rm -f` で行い、`down` のサービス指定は使わない(決定 5)。ワイルドカードを解釈しない版では「`*` という名前のプロファイル」として扱われ、対象が現在と同じになる想定である(未検証)。あわせて `docker_compose` が `COMPOSE_PROFILES` へ打ち消し用のプロファイル名を入れ、`devbase up` の起動では既定のサービスを明示する(決定 7)。サービスを明示した `up` も 2 系全般で使えるため、どちらも版の下限を作らない | v5.1.4 で全サービスが消えることを手動で確かめる。同じ版で、プロファイルを持たないプロジェクトの `up` / `down` が従来どおり動くことも確かめる。`COMPOSE_PROFILES=test` を環境変数と `.env` の両方に置いて `devbase up` を通し、既定のサービスだけが動くことも確かめる。`--profile '*'` が使える最古の版は公式ドキュメントに記載が無く、2.20.0 以上 5.x 未満は未検証のまま「未確認のまま残ること」に載せる | 起動で既定のサービスを対象から外すのは `--no-deps` である。停止で対象から外すのは、サービスを明示した `stop` / `rm -f` である。どれも古くからある形で、版の下限を作らない。下限を決めるのは `depends_on.required` だけである。この属性は 2.20.0 からの機能で、受け入れ条件と文書の構成例が使う。そのため 2.20.0 未満では、構成の検証そのものに失敗する。 diff --git a/issues/PLAN58_compose-profiles.md b/issues/PLAN58_compose-profiles.md index af5afacf..7fefee43 100644 --- a/issues/PLAN58_compose-profiles.md +++ b/issues/PLAN58_compose-profiles.md @@ -55,6 +55,7 @@ dev のほかに app / db などのサービスを持つプロジェクトで、 | プロファイル | Compose の `profiles:` に書いた名前。付随サービス群をまとめる単位 | | 既定のサービス | `profiles:` を持たないサービス。`devbase up` で起動する | | プロファイルのサービス | そのプロファイルに属し、既定のサービスに含まれないサービス | +| 打ち消し用のプロファイル名 | `__devbase_none__`。どのプロジェクトも定義しない名前で、devbase が `COMPOSE_PROFILES` へ入れて利用者の指定を無効にする(設計の決定 7) | ## 受け入れ条件 @@ -131,7 +132,7 @@ TUI: | 運用・保守性 | プロファイルのサービスを起動・停止した記録が、既存のログと同じ体裁(`logger.info`)で残る | | セキュリティ | プロファイルのサービスへ渡す機密は、そのサービスが元々 `env_file` で参照していた由来のキーだけに限る(`_services_receiving_secrets` の現在の規則を変えない)。素の `docker compose` を使わず devbase を通すのは、機密の注入と対象サービスの限定をこの規則の中で行うためである | | システム環境 | Docker Compose 2.20.0 以上で動く。devbase が使うのは `--profile '*'`、`--no-deps`、サービスを明示した `up` / `stop` / `rm -f` である。案内する `depends_on.required` が 2.20.0 以上を要するため、2.20.0 未満は対象外とする | -| 再現性 | 有効なプロファイルは devbase が決める。子プロセスの `COMPOSE_PROFILES` へ番兵の名前を入れ、`devbase up` の起動では対象のサービスも明示する。利用者の設定に結果が左右されない。端末の環境変数でも `.env` でも同じである(設計の決定 7) | +| 再現性 | 有効なプロファイルは devbase が決める。子プロセスの `COMPOSE_PROFILES` へ打ち消し用のプロファイル名を入れ、`devbase up` の起動では対象のサービスも明示する。利用者の設定に結果が左右されない。端末の環境変数でも `.env` でも同じである(設計の決定 7) | 最低対応版を 2.20.0 とする根拠は次のとおりである。 @@ -153,7 +154,7 @@ TUI: | --- | --- | | 公開インタフェース | `devbase container profile` と `devbase project profile` を追加する。`devbase list` の TUI の操作メニューに 2 項目を足す。既存のコマンドの引数は変えない。`devbase down` は内部で `--profile '*'` を付ける | | データ | なし(スキーマも名前付きボリュームの構成も変えない) | -| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ渡る `COMPOSE_PROFILES` が番兵の名前になる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | +| 既存の振る舞い | `down` が全プロファイルを対象にする。devbase 経由の Compose へ渡る `COMPOSE_PROFILES` が打ち消し用のプロファイル名になる。`up` の起動は既定のサービスを明示して渡す(対象は現在と同じ)。フックへ渡す環境変数が 1 つ増える。`profiles:` を使っていないプロジェクトでは、どちらも対象が変わらない。`--profile '*'` を確認済みなのは v5.1.4 で、2.20.0 以上 5.x 未満は未検証である | ## 検証手段