CLIをホスト名ベースに刷新し、デプロイフラグを大幅拡充
once CLIのアプリケーション識別子をホスト名に統一し、deploy・update コマンドに環境変数・CPU/メモリ・バックアップ等の設定フラグを一括追加した。これにより、デプロイ時に必要な設定をコマンドライン一本で完結させられるようになった。
背景
これまでの once CLIは、アプリケーションの識別にコンテナイメージから生成した内部名(baseName)を使用していた。しかし、エンドユーザーが実際にアクセスするのはホスト名(例: myapp.example.com)であり、内部名との乖離がコマンド操作を直感的でないものにしていた。
同時に、デプロイ後に環境変数やリソース制限、SMTP設定を変更する手段がCLIに存在せず、設定変更のたびに別の手段に頼る必要があった。また、update コマンドはもともと once バイナリ自体の自己更新に使われており、「アプリケーションの更新」という用途は空白地帯だった。
これら3つの課題—識別子の直感性、設定フラグの不在、コマンド体系の混乱—を本PRが一括して解消している。
技術的な変更
アプリケーション識別子のホスト名統一
全コマンドの引数がアプリケーション内部名からホスト名に変更され、Namespace に ApplicationByHost メソッドが追加された。
変更前は ns.Application(name) で内部名検索していたが、変更後は以下のように ns.ApplicationByHost(host) によるホスト名検索に切り替わった:
// 変更後
func (n *Namespace) ApplicationByHost(host string) *Application {
for _, app := range n.applications {
if app.Settings.Host == host {
return app
}
}
return nil
}
func (n *Namespace) HostInUse(host string) bool {
return n.ApplicationByHost(host) != nil
}
既存の HostInUse は ApplicationByHost の結果を再利用する形にリファクタリングされ、重複ロジックが排除されている。エラーメッセージも application "missing" not found から no application found at host "missing.localhost" に変わり、ホスト名が識別子であることを明示する。
settingsFlags による設定フラグの集約
settingsFlags 構造体(internal/command/settings_flags.go)が新設され、deploy と update の両コマンドで共有される。登録されるフラグは以下の通り:
-
--host: アプリケーションのホスト名 -
--disable-tls: TLSの無効化 -
--env KEY=VALUE: 環境変数(複数指定可) -
--smtp-server/--smtp-port/--smtp-username/--smtp-password/--smtp-from: SMTP設定 -
--cpus/--memory: コンテナのリソース制限 -
--auto-update: 自動更新の有効/無効(デフォルト:true) -
--backup-path/--auto-backup: バックアップ設定
--env には strings.SplitN(s, "=", 2) による分割が使われており、DSN=postgres://host?opt=val のように値に = を含むケースも正しく処理される。deploy_test.go にこのエッジケースを含む単体テストが追加されている。
update コマンドの役割変更と self-update の分離
旧 update コマンド(バイナリ自己更新)は self-update に改名され、新たな update <host> コマンドがデプロイ済みアプリケーションの設定変更に充てられた。
update コマンドは settingsFlags に加えて --image フラグを持ち、イメージ自体の差し替えも可能。フラグが明示的に指定された項目のみを既存設定に上書きする applyChanges メソッドが実装されており、未指定のフラグは現在値を保持する。update_test.go の TestApplyChanges がこの「差分適用」動作を検証している。
ApplicationSettings のバリデーション追加
ApplicationSettings に Validate() メソッドが追加され、ErrImageRequired(image未設定)と ErrAutoBackupWithoutPath(auto-backup 有効なのに backup-path 未設定)の2つのエラー条件が設定時に検査される。
func (s ApplicationSettings) Validate() error {
if s.Image == "" {
return ErrImageRequired
}
if s.Backup.AutoBackup && s.Backup.Path == "" {
return ErrAutoBackupWithoutPath
}
return nil
}
これにより、不整合な設定がデプロイ実行前にブロックされる。
CLI出力の改善
cli_progress.go が新設され、bubbletea と lipgloss を使ったプログレス表示が実装された。list コマンドの出力も改善され、アプリ名のみの表示から「ホスト名(running/stopped)」形式になり、ホスト名はターミナルのハイパーリンク付きで表示される。
host := hostStyle.Hyperlink(app.URL()).Render(app.Settings.Host)
fmt.Printf("%s (%s)\n", host, status)
設計判断
ホスト名を第一級の識別子とする設計 が一貫して採用されている。
内部名は docker.NameFromImageRef から自動生成される実装詳細であり、ユーザーが意識すべきものではない。ホスト名はDNSやTLS証明書とも対応し、アプリケーションの「住所」として機能する。全コマンドがホスト名を引数に取ることで、CLIの操作対象とブラウザで開くURLが一致するという一貫性が生まれる。
settingsFlags の共有設計は、deploy と update の対称性を保証している。デプロイ時に指定できるフラグはそのまま更新時にも使えるため、ユーザーが両コマンドで別々のフラグ体系を覚える必要がない。applyChanges の差分適用ロジックにより、update は「すべてを再指定する」のではなく「変更したい項目だけを指定する」操作になっている。
まとめ
ホスト名への識別子統一と settingsFlags の共通化は、単なる機能追加にとどまらず、CLIの操作モデルを「内部状態の管理」から「ホスト名で識別されるサービスの宣言的操作」へとシフトさせる変更である。applyChanges による差分適用と Validate() によるガード設計が組み合わさることで、設定の一貫性を保ちながらインクリメンタルな変更が可能なCLIが実現されている。