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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,15 @@
> 適用には `devbase build --no-cache` によるベースイメージの再ビルドが必要です。

### Added
- **ライフサイクルフックへ `project.yml` の値を環境変数で渡す**ようにしました。`pre-up` /
`deploy` に `DEVBASE_PRIMARY_DIR` (primary の clone 先ディレクトリ名) /
`DEVBASE_PRIMARY_URL` (primary の clone URL) / `DEVBASE_WORK_DIR` (コンテナ内の既定の
作業ディレクトリ) / `DEVBASE_REPO_DIRS` (全リポジトリのディレクトリ名・宣言順の空白
区切り) が渡ります。フックはホスト側で動くため `env` を読み込めず、`GIT_REPO` /
`WORK_DIR` の `project.yml` 移行によって `source ./env` に依存していたフックが値を
取れなくなるためです。値は子プロセス限定で、親プロセスの環境は汚しません。
詳細は [フックへ渡る環境変数](docs/plugin-dev/quickstart.md#フックへ渡る環境変数)
を参照してください。
- **`devbase project migrate-config`** を追加しました。旧 `env` 形式のプロジェクト定義を
`project.yml` へ機械的に変換します。`--dry-run` で変換結果を確認でき、既存の
`project.yml` は上書きしないため何度実行しても同じ状態に収束します。
Expand Down
32 changes: 29 additions & 3 deletions docs/plugin-dev/quickstart.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -170,19 +170,45 @@ MY_SECRET_API_KEY=sk-xxxxxxxxxxxx
| `pre-up` | `devbase up` 開始直後(`docker compose up` の前) | `build.context` 用ソースリポジトリの clone、設定ファイルの生成など、イメージビルド前に完了させたい準備 |
| `deploy` | コンテナ起動完了後、各スケールインスタンスごとに実行 | S3 からの `.env` 取得、コンテナ起動後に必要な外部リソースの初期化など |

#### フックへ渡る環境変数

フックは**ホスト側**で動くため、コンテナへ渡る `env` / `.env` は読み込まれません。フックが必要とする `project.yml` の値は、devbase が環境変数として明示的に渡します。

| 変数 | 内容 | `pre-up` | `deploy` |
|------|------|:---:|:---:|
| `DEVBASE_PRIMARY_DIR` | primary リポジトリの `/work` 配下ディレクトリ名(`repos[].dir`。未指定ならリポジトリ名) | ✓ | ✓ |
| `DEVBASE_PRIMARY_URL` | primary リポジトリの clone URL(`https://<host>/<owner>/<repo>.git`) | ✓ | ✓ |
| `DEVBASE_WORK_DIR` | コンテナ内の既定の作業ディレクトリ(`work_dir`。未指定なら `/work/$DEVBASE_PRIMARY_DIR`) | ✓ | ✓ |
| `DEVBASE_REPO_DIRS` | 全リポジトリのディレクトリ名を `project.yml` の宣言順に空白区切りで並べたもの | ✓ | ✓ |
| `DEVBASE_INSTANCE_INDEX` | 実行対象のインスタンス番号(1 始まり)。`pre-up` はインスタンスごとに実行されないため渡りません | -- | ✓ |

primary は `repos` の先頭(または `primary: true` を付けた 1 件)で、常にちょうど 1 件です。primary 以外も含めて全リポジトリを回したい場合は `DEVBASE_REPO_DIRS` を使います。

```bash
for dir in $DEVBASE_REPO_DIRS; do
echo "populate /work/$dir"
done
```

> **Note:** これらは**子プロセスにだけ**渡ります。フック内で `export` しても、後続の `docker compose up` や別プロジェクトの実行へは伝播しません。

#### `pre-up` の例

```bash
#!/bin/bash
# projects/my-project/pre-up
set -e

# build context に使うリポジトリが無ければ clone
# (pre-up はホスト側で動くフックなので、clone 先も URL もここに直接書く)
# (clone 先も URL も devbase が project.yml から渡してくれる)
if [ ! -d "./repo" ]; then
git clone "https://github.com/your-github-user/my-repo.git" repo
git clone "$DEVBASE_PRIMARY_URL" repo
fi

echo "コンテナ内の作業ディレクトリ: $DEVBASE_WORK_DIR" # 例: /work/my-repo
```

> **Note:** どちらのフックも `bash` で実行されます。`chmod +x` で実行可能ビットを立てておいてください。`pre-up` が非ゼロ終了すると `devbase up` は中断します。`deploy` は各インスタンスに対して `DEVBASE_INSTANCE_INDEX` を環境変数として渡しますが、失敗してもデプロイは続行されます
> **Note:** どちらのフックも `bash` で実行されます。`chmod +x` で実行可能ビットを立てておいてください。`pre-up` が非ゼロ終了すると `devbase up` は中断します。`deploy` は失敗してもデプロイは続行されます

> **応用:** 外部リポジトリを共有 work ボリュームへ取り込み、app / nginx / db など複数コンテナで動かすプロジェクトでは、`pre-up` で clone/pull と work ボリュームへの populate を行い、2 回目以降はコンテナ側を上書きしないよう冪等にスキップするのが定石です。詳細は [repo 連携プロジェクトと pre-up populate パターン](repo-backed-projects.md) を参照してください。

Expand Down
46 changes: 43 additions & 3 deletions docs/plugin-dev/repo-backed-projects.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,10 +88,40 @@ volumes:

| # | 処理 | 内容 |
|---|------|------|
| ① | `repo/` の clone / pull | 無ければ `git clone`、あれば `git pull --ff-only`(app ビルドコンテキストの最新化) |
| ① | `repo/` の clone / pull | 無ければ `git clone "$DEVBASE_PRIMARY_URL"`、あれば `git pull --ff-only`(app ビルドコンテキストの最新化) |
| ② | `.env` の取得 | S3 等から取得してホスト `./.env` に配置(`docker compose` の変数展開前に必要) |
| ③ | work ボリュームへ populate | `repo/` の内容を `/work/<リポジトリ名>` へコピー |
| ④ | `.env` を work ボリュームへ配置 | Laravel 等のランタイムが `/work/<リポジトリ名>/.env` を参照するため |
| ③ | work ボリュームへ populate | `repo/` の内容を `/work/$DEVBASE_PRIMARY_DIR` へコピー |
| ④ | `.env` を work ボリュームへ配置 | Laravel 等のランタイムが `/work/$DEVBASE_PRIMARY_DIR/.env` を参照するため |

### clone 先と URL の受け取り方

`pre-up` はホスト側で動くフックなので、コンテナへ渡る `env` は読み込まれません。populate 先のディレクトリ名と clone URL は、devbase が `project.yml` から解決して環境変数で渡します。

```bash
#!/bin/bash
# projects/<name>/pre-up
set -e

REPO_DIR="$DEVBASE_PRIMARY_DIR" # 例: carmo-system-console
WORK_VOLUME="${DEVBASE_WORK_VOLUME:-devbase_work_1}"

# ① ビルドコンテキストの clone / pull
if [ ! -d "./repo/.git" ]; then
git clone "$DEVBASE_PRIMARY_URL" repo
elif [ "${DEVBASE_REPO_PULL:-1}" = "1" ]; then
git -C repo pull --ff-only
fi

# populate 済み判定は work ボリューム上の /work/$REPO_DIR/.git で行う
if docker run --rm -v "$WORK_VOLUME:/work" alpine test -d "/work/$REPO_DIR/.git"; then
echo "populate 済みのためスキップ"
exit 0
fi

# ②③④ (.env 取得 / populate / .env 配置) は /work/$REPO_DIR を対象に行う
```

変数の一覧と `deploy` への渡り方は [クイックスタート「フックへ渡る環境変数」](quickstart.md#フックへ渡る環境変数) を参照してください。`project.yml` に複数リポジトリを書いている場合は、`DEVBASE_REPO_DIRS`(宣言順・空白区切り)で全 clone 先を回せます。

② を `deploy`(`up` 後フック)ではなく `pre-up` で行うのは、`compose.yml` の `MYSQL_DATABASE: ${DB_DATABASE:-...}` のような変数展開が `docker compose` パース時(= MySQL コンテナ初回起動前)に `.env` を要求するためです。`deploy` 段階では間に合わず、DB がデフォルト名で初期化されてしまいます。

Expand DownExpand Up@@ -185,6 +215,15 @@ devbase up
| `DEVBASE_WORK_VOLUME` | `devbase_work_<index>` | `compose.yml` が参照する共有 work ボリューム名の明示指定。未指定なら `DEVBASE_INSTANCE_INDEX` から解決。ただし効くのは **app / nginx など非 dev サービスだけ**で、dev サービスの `/work` は scale 生成時に `devbase_work_<index>` へ無条件に差し替えられます。dev から実行時ソースを触る本パターンでは **既定名のまま**にしてください([スケール前提](#スケール前提-scale-1) を参照) |
| `DEVBASE_INSTANCE_INDEX` | `1` | work ボリューム名のインデックス。**devbase 本体が渡すのは `deploy` フックに対してのみ**で、`pre-up` や `docker compose` のプロセス環境には渡りません。`compose.yml` の `${DEVBASE_INSTANCE_INDEX:-1}` は `.env` に書かれた値、無ければ `1` に解決されます([スケール前提](#スケール前提-scale-1) を参照) |

上記はプロジェクト側が設定する変数です。これに対し、次の 4 つは **devbase が `project.yml` から解決して `pre-up` / `deploy` の両方へ渡す**読み取り専用の値です(詳細は [クイックスタート「フックへ渡る環境変数」](quickstart.md#フックへ渡る環境変数))。

| 変数 | 内容 |
|------|------|
| `DEVBASE_PRIMARY_DIR` | primary リポジトリの `/work` 配下ディレクトリ名(populate 先) |
| `DEVBASE_PRIMARY_URL` | primary リポジトリの clone URL |
| `DEVBASE_WORK_DIR` | コンテナ内の既定の作業ディレクトリ |
| `DEVBASE_REPO_DIRS` | 全リポジトリのディレクトリ名(宣言順・空白区切り) |

> **Note:** `.env` の環境選択(例: `s3://.../env/local.env` の `local` 部分)など、S3 パスやプロファイルはプロジェクト固有の変数(例: `CARMO_ENV`)で制御することがあります。プロジェクトの `pre-up` 冒頭コメントを参照してください。

---
Expand All@@ -196,6 +235,7 @@ devbase up
- [ ] `compose.yml` で work ボリュームを `external: true` + `name: ${DEVBASE_WORK_VOLUME:-devbase_work_${DEVBASE_INSTANCE_INDEX:-1}}` で宣言した
- [ ] app サービスの `build.context` をホスト `./repo` にした
- [ ] `pre-up` で ①clone/pull → ②`.env`取得 → ③populate → ④`.env`配置 を実装した
- [ ] `pre-up` が clone 先・URL を `DEVBASE_PRIMARY_DIR` / `DEVBASE_PRIMARY_URL` から受け取っている(`source ./env` でも値を直書きでもなく)
- [ ] `pre-up` が `/work/<リポジトリ名>/.git` の有無で populate 済みを判定し、②③④ をスキップする
- [ ] populate 時の owner を `1000:1000`(コンテナ内ユーザー)に設定した
- [ ] `storage/` / `vendor/` / `node_modules/` 等、初回のみ生成され上書きしたくないパスの扱いを決めた
Expand Down
18 changes: 18 additions & 0 deletions docs/user/project-yml.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,6 +147,24 @@ flowchart LR
YAML の解釈はホスト側の Python に閉じています。コンテナ側は復号して 1 行ずつ
clone するだけなので、イメージへ YAML パーサを持ち込みません。

## ライフサイクルフックへの渡り方

プロジェクトに `pre-up` / `deploy` フックを置いている場合、それらは**ホスト側**で
動くため `env` を読み込めません。フックがよく必要とする値は、devbase が
`project.yml` から解決して環境変数として渡します。

| 環境変数 | `project.yml` の由来 |
|---------|---------------------|
| `DEVBASE_PRIMARY_DIR` | primary リポジトリの `repos[].dir`(未指定ならリポジトリ名) |
| `DEVBASE_PRIMARY_URL` | primary リポジトリの `host` / `owner` / `repo` から組み立てた clone URL |
| `DEVBASE_WORK_DIR` | `work_dir`(未指定なら `/work/<primary の dir>`) |
| `DEVBASE_REPO_DIRS` | `repos[].dir` を宣言順に空白区切りで並べたもの |

旧 `env` 形式のフックが `source ./env` で読んでいた `GIT_REPO` / `WORK_DIR` は、
それぞれ `DEVBASE_PRIMARY_DIR` / `DEVBASE_WORK_DIR` に置き換えてください。詳細は
[プラグイン開発クイックスタート「フックへ渡る環境変数」](../plugin-dev/quickstart.md#フックへ渡る環境変数)
を参照してください。

> **Note:** `entrypoint.sh` はイメージに焼き込まれます。devbase 本体を更新して
> clone のふるまいが変わった場合は、`devbase build --no-cache` でベースイメージを
> 再ビルドしないと反映されません。
31 changes: 23 additions & 8 deletions lib/devbase/commands/container.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,24 +146,35 @@ def _compose_run(subcommand: str, *extra_args: str) -> int:
return subprocess.run(cmd).returncode


def _run_deploy_script_for_instances(deploy_script: Path, indices) -> None:
"""デプロイスクリプトをスケールされた各インスタンスに対して実行する"""
def _run_deploy_script_for_instances(deploy_script: Path, indices,
config=None) -> None:
"""デプロイスクリプトをスケールされた各インスタンスに対して実行する。

``config`` (``project.yml``) を渡すと、clone 先やリポジトリ URL をフックへ
環境変数で伝える (:func:`devbase.project.runtime.hook_env`)。
"""
hook_vars = project_runtime.hook_env(config) if config is not None else {}
for i in indices:
logger.info("[Bonus] Running deploy script for instance %d...", i)
env = {**os.environ, 'DEVBASE_INSTANCE_INDEX': str(i)}
env = {**os.environ, **hook_vars, 'DEVBASE_INSTANCE_INDEX': str(i)}
try:
subprocess.run(['bash', str(deploy_script)], check=True, env=env)
logger.info("Deploy script completed for instance %d", i)
except subprocess.CalledProcessError as e:
logger.warning("Deploy script failed for instance %d (exit code %d)", i, e.returncode)


def _run_pre_up_hook() -> bool:
def _run_pre_up_hook(config=None) -> bool:
"""`./pre-up` フックがあればコンテナ起動前に実行する。

ビルドコンテキスト用のリポジトリ clone など、`docker compose up` より前に
完了しておく必要のある準備処理をプロジェクト側で記述するためのフック。

``config`` (``project.yml``) を渡すと、clone 先やリポジトリ URL をフックへ
環境変数で伝える (:func:`devbase.project.runtime.hook_env`)。フックは以前
``source ./env`` で ``GIT_REPO`` / ``WORK_DIR`` を読んでいたが、これらは
``project.yml`` へ移ったため devbase 側から明示的に渡す。

Returns:
True: フックが存在しなかった、または成功した
False: フックが失敗した(呼び出し側で `cmd_up` を中断する)
Expand All@@ -173,8 +184,10 @@ def _run_pre_up_hook() -> bool:
return True

logger.info("Running pre-up hook: %s", pre_up_script)
hook_vars = project_runtime.hook_env(config) if config is not None else {}
try:
subprocess.run(['bash', str(pre_up_script)], check=True, env=os.environ.copy())
subprocess.run(['bash', str(pre_up_script)], check=True,
env={**os.environ, **hook_vars})
return True
except subprocess.CalledProcessError as e:
logger.error("pre-up hook failed (exit code %d)", e.returncode)
Expand DownExpand Up@@ -644,7 +657,7 @@ def cmd_up(project_name: str = None, scale: int = None,
return 1

# Pre-step: Run ./pre-up hook (e.g. clone source repos used as build contexts)
if not _run_pre_up_hook():
if not _run_pre_up_hook(config):
return 1

# Pre-check 2: Ensure container images exist
Expand DownExpand Up@@ -693,7 +706,8 @@ def cmd_up(project_name: str = None, scale: int = None,
# Run project-specific deploy script for each scaled instance
deploy_script = Path('./deploy')
if deploy_script.exists() and deploy_script.is_file():
_run_deploy_script_for_instances(deploy_script, range(1, scale + 1))
_run_deploy_script_for_instances(deploy_script, range(1, scale + 1),
config)

_maybe_open_editor(project_name, open_editor, open_index, scale,
config, compose_file=override_file)
Expand DownExpand Up@@ -840,7 +854,8 @@ def cmd_scale(new_scale: int, project_name: str = None) -> int:
# Run project-specific deploy script for newly added instances
deploy_script = Path('./deploy')
if deploy_script.exists() and deploy_script.is_file():
_run_deploy_script_for_instances(deploy_script, range(current_scale + 1, new_scale + 1))
_run_deploy_script_for_instances(
deploy_script, range(current_scale + 1, new_scale + 1), config)

logger.info("=== Scale completed successfully ===")
logger.info("Container scale: %d -> %d", current_scale, new_scale)
Expand Down
22 changes: 22 additions & 0 deletions lib/devbase/project/runtime.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,6 +65,27 @@ def container_env(config: ProjectConfig, project_name: str) -> Dict[str, str]:
return env


def hook_env(config: ProjectConfig) -> Dict[str, str]:
"""``pre-up`` / ``deploy`` フックへ渡す環境変数を組み立てる。
Comment thread
takemi-ohama marked this conversation as resolved.

フックはホスト側で動き、clone 先のパスやリポジトリ URL を必要とすることが
ある (共有ボリュームへの populate、Laravel の ``.env`` 配置など)。以前は
``env`` の ``GIT_REPO`` / ``WORK_DIR`` を ``source ./env`` で読んでいたが、
それらは ``project.yml`` へ移ったため devbase 側から明示的に渡す。

- ``DEVBASE_PRIMARY_DIR`` : primary repo の ``/work`` 配下ディレクトリ名
- ``DEVBASE_PRIMARY_URL`` : primary repo の clone URL
- ``DEVBASE_WORK_DIR`` : コンテナ内の既定の作業ディレクトリ
- ``DEVBASE_REPO_DIRS`` : 全 repo のディレクトリ名 (空白区切り、宣言順)
"""
return {
"DEVBASE_PRIMARY_DIR": config.primary.dir,
"DEVBASE_PRIMARY_URL": config.primary.url,
"DEVBASE_WORK_DIR": config.resolved_work_dir(),
"DEVBASE_REPO_DIRS": " ".join(repo.dir for repo in config.repos),
}


# ---------------------------------------------------------------------------
# scale (旧 CONTAINER_SCALE)
# ---------------------------------------------------------------------------
Expand DownExpand Up@@ -136,6 +157,7 @@ def current_project_config(project_dir: Path = None) -> ProjectConfig:
"build_workspace_document",
"container_env",
"current_project_config",
"hook_env",
"read_scale",
"workspace_path",
"write_scale",
Expand Down
2 changes: 1 addition & 1 deletion tests/commands/test_container_up_order.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ def up_harness(tmp_path, monkeypatch):
monkeypatch.setattr(container, 'get_project_name', lambda: 'proj')
monkeypatch.setattr(container, 'get_dev_service_name', lambda: 'dev')
monkeypatch.setattr(container, '_ensure_env_files', lambda: True)
monkeypatch.setattr(container, '_run_pre_up_hook', lambda: True)
monkeypatch.setattr(container, '_run_pre_up_hook', lambda config=None: True)
monkeypatch.setattr(container, '_ensure_images', lambda: True)
monkeypatch.setattr(container, '_auto_snapshot', lambda: None)
monkeypatch.setattr(container, 'ensure_volumes', lambda *a, **k: None)
Expand Down
Loading