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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@
実体の無いパスが `env` に残っていると ADC がユーザー認証へフォールバックできません。

### Fixed
- **`devbase build <image>` が必ず失敗する問題**を修正しました (#139)。`<image>` の位置引数が
剥がされないまま `docker buildx build` へ渡り、PATH が 2 つになって
`docker: 'docker buildx build' requires 1 argument` で落ちていました。CLI リファレンスに
正式な構文として載っているにもかかわらず、**ベースイメージだけを再ビルドする手段が
無い**状態でした。

`bin/devbase` の dispatch が位置引数を検出し、`--expires` と同じく Python 側へ振り分けます。
`devbase build` / `--no-cache` / `--project-no-cache` は従来どおり shell の 2 段ビルドです。

あわせて `devbase project build <image>` / `devbase container build <image>` が作るタグを
`<image>:latest` から **`devbase-<image>:latest`** へ直しました。旧タグは他の Dockerfile の
`FROM devbase-base:latest` から解決できず、ビルドしても使われませんでした。ビルドコマンドも
shell 側と同じ `docker buildx build --load` に揃えています。旧タグはリポジトリ内のどこからも
参照されていないため、移行の手当ては要りません。

`<image>` にはディレクトリ名を渡してください (`devbase build base`)。`devbase-base` のように
接頭辞込みで渡すと `containers/devbase-base` を探して見つからず、終了コード 1 で終わります。
`<image>` が `projects/` に実在する名前と一致する場合は、そのプロジェクトへの操作として
解釈されます (#142)。この場合は `devbase project build <image>` を使ってください。

- **使われない GCP サービスアカウント鍵をコンテナへ渡さない**ようにしました (#134)。
`GCP_AUTH_MODE=adc` が止めるのは「鍵をファイルへ書き出すこと」だけで、鍵を運ぶ
`GCP_CREDENTIALS_BASE64__*` と `GOOGLE_APPLICATION_CREDENTIALS_BASE64` は生成 compose の
Expand Down
19 changes: 14 additions & 5 deletions bin/devbase
Original file line number Diff line number Diff line change
Expand Up @@ -421,18 +421,27 @@ case "$_resolved_cmd" in
run_python "${_resolved_cmd}" "${_DEVBASE_ARGS[@]}" ;;
# Shell-implemented commands
#
# build: 既定 / --no-cache / <image> は shell の cmd_build (devbase-base の
# 2 段ビルド) で処理する。--expires はイメージ作成日の判定が必要で、shell では
# RFC3339 日付パースが非可搬なため Python (project build) へ委譲する
# (i07: build --expires=N / rebuild / up が共通の期限リゾルバを使う)。
# build: 既定 / --no-cache / --project-no-cache は shell の cmd_build
# (devbase-base の 2 段ビルド) で処理する。次の 2 つは Python (project build)
# へ委譲する (PLAN49 / i07):
# - <image> 指定の単体ビルド: `devbase project build <image>` /
# `devbase container build <image>` と同じ実装へ届ける。逆向き (Python から
# shell を呼ぶ) にすると、この wrapper 冒頭の name 解決を通ってしまい、
# containers/ と projects/ に同名がある場合 (bi-tools) に別のものを
# ビルドしてしまう。
# - --expires: イメージ作成日の判定が必要で、shell では RFC3339 日付パースが
# 非可搬なため (build --expires=N / rebuild / up が共通の期限リゾルバを使う)。
build)
_has_expires=0
_build_image=""
for _ba in "${_DEVBASE_ARGS[@]}"; do
case "$_ba" in
--expires|--expires=*) _has_expires=1 ;;
-*) ;;
*) _build_image="$_ba" ;;
esac
done
if [ "$_has_expires" = 1 ]; then
if [ "$_has_expires" = 1 ] || [ -n "$_build_image" ]; then
run_python project build "${_DEVBASE_ARGS[@]}"
else
cmd_build "${_DEVBASE_ARGS[@]}"
Expand Down
32 changes: 26 additions & 6 deletions docs/developer/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ flowchart TB

BinDevbase --> ResolveCmd{"resolve_command<br/>プレフィックスマッチ"}

ResolveCmd -->|"build"| CmdBuild["cmd_build()<br/>Bash で直接実行"]
ResolveCmd -->|"build (フラグのみ)"| CmdBuild["cmd_build()<br/>Bash で直接実行"]
ResolveCmd -->|"build &lt;image&gt; / --expires"| RunPython
ResolveCmd -->|"その他すべて"| RunPython["run_python()<br/>uv run → python -m devbase.cli"]

RunPython --> CliPy["cli.py<br/>_expand_argv → _create_parser → _dispatch"]
Expand All @@ -29,16 +30,35 @@ flowchart TB
Project -->|"list (TTY)"| Tui["tui/<br/>(階層メニュー TUI)"]

CmdBuild --> Docker["docker buildx build / docker compose build"]
Container --> DockerSingle["docker buildx build --load<br/>(単体ビルド)"]
```

### なぜ二層構成なのか

| 層 | 担当 | 利点 |
|----|------|------|
| **Bash** (`bin/devbase`) | PATH 設定、シェル補完登録、環境変数エクスポート、`build` コマンド | シェル環境へのネイティブ統合。`source` で `.env` を読み込み、`DEVBASE_ROOT` を確定してから Python に渡せる |
| **Python** (`lib/devbase/`) | init, status, project, env, plugin, snapshot, tui | 複雑なロジック(YAML パース、Git 操作、差分バックアップ等)を安全かつ保守的に実装できる |
| **Bash** (`bin/devbase`) | PATH 設定、シェル補完登録、環境変数エクスポート、`build` の compose ビルド | シェル環境へのネイティブ統合。`source` で `.env` を読み込み、`DEVBASE_ROOT` を確定してから Python に渡せる |
| **Python** (`lib/devbase/`) | init, status, project, env, plugin, snapshot, tui、`build` の単体ビルドと期限判定 | 複雑なロジック(YAML パース、Git 操作、差分バックアップ等)を安全かつ保守的に実装できる |

`build` コマンドだけが Bash 側に残っている理由は、Docker buildx の制御と compose.yml のパース処理がシェルスクリプトで完結するためである。
### `build` の振り分け

`build` だけは引数によって層が分かれる。`bin/devbase` の dispatch が判定する。

| 引数 | 実行する層 | 実体 | 理由 |
|------|-----------|------|------|
| なし / `--no-cache` / `--project-no-cache` | Bash | `cmd_build()` | compose.yml のパースと `FROM devbase-*` の依存検出、2 段ビルドの制御がシェルで完結する |
| `<image>` | Python | `container._build_single_image()` | `devbase project build <image>` / `devbase container build <image>` と同じ実装へ届ける。逆向きに Python から `bin/devbase build <image>` を呼ぶと、wrapper 冒頭の name 解決を通ってしまい、`containers/` と `projects/` に同名がある場合に別のものをビルドする |
| `--expires[=DAYS]` | Python | `container.cmd_build()` → `_build_resolved()` | イメージ作成日の判定に RFC3339 の日付パースが要り、シェルでは非可搬 |

単体ビルド(`<image>` 指定)は `$DEVBASE_ROOT/containers/<image>` を
`docker buildx build --load -t devbase-<image>:latest` で作る。タグはディレクトリ名から
一意に決まり、接頭辞は剥がさない。剥がすと `containers/xxx` と `containers/devbase-xxx` が
同じタグを取り合うためである。`image` はディレクトリ名 1 つとして妥当な文字だけを
受け付ける(先頭は英数字、以降は英数字・`.`・`-`・`_`)。

期限判定の経路(`--expires`)は Python から `_run_build()` で `bin/devbase build` を
呼び戻すが、そこでは位置引数を渡さないため Bash の `cmd_build()` へ入る。単体ビルドの
振り分けと再帰しない。

## モジュール構成

Expand All @@ -55,7 +75,7 @@ Python 側のエントリーポイント。以下の責務を持つ。

| 定数 | 役割 |
|------|------|
| `SHORTCUTS` | トップレベルショートカット → サブコマンドのマッピング。`up`, `down`, `login`, `ps`, `scale`, `rebuild` が `project` グループへ転送される(`build` は shell 実装へ委譲するため除外、`list` は lifecycle ではないため `_dispatch` で個別 routing) |
| `SHORTCUTS` | トップレベルショートカット → サブコマンドのマッピング。`up`, `down`, `login`, `ps`, `scale`, `rebuild` が `project` グループへ転送される(`build` は引数によって shell / Python へ分かれるため除外、`list` は lifecycle ではないため `_dispatch` で個別 routing) |
| `GROUP_ALIASES` | グループのエイリアス。`ct` → `container`, `pl` → `plugin`, `ss` → `snapshot` |
| `SUBCMD_MAP` | 各グループが受け付けるサブコマンド一覧。プレフィックスマッチの候補として使用される |

Expand Down Expand Up @@ -203,7 +223,7 @@ sequenceDiagram

User->>Bash: devbase con u
Bash->>Bash: resolve_command("con") → "container"
alt build コマンド
alt build コマンド (フラグのみ)
Bash->>Bash: cmd_build() を直接実行
else その他のコマンド
Bash->>Python: uv run python -m devbase.cli container u
Expand Down
24 changes: 24 additions & 0 deletions docs/user/cli-reference/02-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,30 @@ devbase build [image] [--no-cache | --expires[=DAYS]]
> 単体ビルドでは `--no-cache` のみ反映され、`--expires` は対象外です。`--expires` 付きビルドは
> 作成日判定のため Python 経路(`project build`)で処理されます。

### 単体ビルドが作るイメージ

`image` を指定すると、`$DEVBASE_ROOT/containers/<image>` を次のコマンドでビルドします。

```
docker buildx build --load -t devbase-<image>:latest $DEVBASE_ROOT/containers/<image>
```

タグは必ず `devbase-` を前置します。`containers/` 配下のイメージは他の Dockerfile から
`FROM devbase-base:latest` の形で参照されるため、前置しないタグではビルドしても解決できません。
タグは `containers/` 配下のディレクトリ名から一意に決まります。`<image>` にはディレクトリ名を
渡してください。`devbase build devbase-base` のように接頭辞込みで渡すと `containers/devbase-base`
を探して見つからず、終了コード 1 で終わります。

ビルドは 1 回だけで、compose イメージは巻き込みません。`containers/<image>` または
その `Dockerfile` が無い場合は、探したパスを表示して終了コード 1 で終わります。

> **`<image>` が `$DEVBASE_ROOT/projects/` に実在する名前と一致する場合、トップレベルの
> `devbase build <image>` はそのプロジェクトへの操作として解釈されます。** これは
> `devbase build <プロジェクト名>` を「そのプロジェクトをビルドする」と読む設計によるもので、
> イメージ指定は失われます。該当するときは `devbase project build <image>` を使ってください
> (こちらは常にイメージ名として扱います)。詳細は
> [#142](https://github.com/devbasex/devbase/issues/142) を参照してください。

## `devbase project rebuild`

`devbase build --expires=7` のシノニムです(既定 7 日)。プロジェクトイメージが 7 日以上古ければ
Expand Down
Loading
Loading