CLIをホスト名ベースに刷新し、デプロイフラグを大幅拡充

basecamp/once

once CLIのアプリケーション識別子をホスト名に統一し、deployupdate コマンドに環境変数・CPU/メモリ・バックアップ等の設定フラグを一括追加した。これにより、デプロイ時に必要な設定をコマンドライン一本で完結させられるようになった。

背景

これまでの once CLIは、アプリケーションの識別にコンテナイメージから生成した内部名(baseName)を使用していた。しかし、エンドユーザーが実際にアクセスするのはホスト名(例: myapp.example.com)であり、内部名との乖離がコマンド操作を直感的でないものにしていた。

同時に、デプロイ後に環境変数やリソース制限、SMTP設定を変更する手段がCLIに存在せず、設定変更のたびに別の手段に頼る必要があった。また、update コマンドはもともと once バイナリ自体の自己更新に使われており、「アプリケーションの更新」という用途は空白地帯だった。

これら3つの課題—識別子の直感性、設定フラグの不在、コマンド体系の混乱—を本PRが一括して解消している。

技術的な変更

アプリケーション識別子のホスト名統一

全コマンドの引数がアプリケーション内部名からホスト名に変更され、NamespaceApplicationByHost メソッドが追加された。

変更前は 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
}

既存の HostInUseApplicationByHost の結果を再利用する形にリファクタリングされ、重複ロジックが排除されている。エラーメッセージも application "missing" not found から no application found at host "missing.localhost" に変わり、ホスト名が識別子であることを明示する。

settingsFlags による設定フラグの集約

settingsFlags 構造体(internal/command/settings_flags.go)が新設され、deployupdate の両コマンドで共有される。登録されるフラグは以下の通り:

  • --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.goTestApplyChanges がこの「差分適用」動作を検証している。

ApplicationSettings のバリデーション追加

ApplicationSettingsValidate() メソッドが追加され、ErrImageRequired(image未設定)と ErrAutoBackupWithoutPathauto-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 が新設され、bubbletealipgloss を使ったプログレス表示が実装された。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 の共有設計は、deployupdate の対称性を保証している。デプロイ時に指定できるフラグはそのまま更新時にも使えるため、ユーザーが両コマンドで別々のフラグ体系を覚える必要がない。applyChanges の差分適用ロジックにより、update は「すべてを再指定する」のではなく「変更したい項目だけを指定する」操作になっている。

まとめ

ホスト名への識別子統一と settingsFlags の共通化は、単なる機能追加にとどまらず、CLIの操作モデルを「内部状態の管理」から「ホスト名で識別されるサービスの宣言的操作」へとシフトさせる変更である。applyChanges による差分適用と Validate() によるガード設計が組み合わさることで、設定の一貫性を保ちながらインクリメンタルな変更が可能なCLIが実現されている。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
20c179cf

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文→背景→技術的な変更→設計判断→まとめ」という理想的な「総論→各論→結論」の構成が明確に適用されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```go:filepath)とPR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

CLIの設計、Goのコード構造、Dockerの概念に言及しており、専門知識を持つエンジニアという対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフがガイドラインに準拠しています。特に各段落の冒頭にトピックセンテンスが配置されているため、要点を素早く把握できます。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコード引用(ApplicationByHost, Validate, list.goの出力部分など)は、提供されたDiffの内容とファイル名を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PRで導入された`settingsFlags`, `ApplicationByHost`, `self-update`などの固有名詞や、`bubbletea`, `lipgloss`といったライブラリ名が正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

アプリケーション識別子のホスト名への変更、`settingsFlags`による設定の共通化、`update`コマンドの役割変更といった技術的な説明が、Diffの内容と一致しており、論理的かつ正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(ホスト名への識別子変更、設定フラグの追加、self-updateへのリネーム等)は、PRのDescriptionやDiffによって裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#32)が正確に記載・リンクされています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「CLIをホスト名ベースに刷新し、デプロイフラグを大幅拡充」は、PRの主題である「CLIのインタラクション改善」を具体的かつ的確に要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれない外部知識(バージョンのサポート状況など)の言及はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「これまでの」といった過去の状態を示す表現と、PRによる変更内容を説明する表現が適切に使い分けられており、時間的な歪曲はありません。