once に `exec` コマンドを追加し、コンテナ内コマンド実行を可能に
once CLI が提供するサブコマンドに exec が加わり、アプリケーションコンテナ内で任意のコマンドを直接実行できるようになりました。TTY の自動付与と終了コードの透過が実装され、スクリプトからの利用がシームレスになります。
背景
once ではコンテナ内部での一時的な操作が必要になるケースが増えていました。Issue #59 で提案されたように、開発者はアプリケーション固有の TUI や CLI をコンテナ内で起動したいと求めていましたが、既存の once コマンドセットにはその手段がなく、docker exec 等を別途呼び出すしかありませんでした。
このギャップを埋めることで、開発フロー全体の一貫性が向上します。once exec が提供されれば、プロジェクト固有のツールを同じ CLI から直接呼び出せ、環境ごとのラッパーを書く必要がなくなります。
技術的な変更
once exec がコマンドツリーに追加されました。internal/command/root.go の NewRootCommand() で r.cmd.AddCommand(newExecCommand().cmd) が挿入され、既存のサブコマンドと同様に利用できる形になっています。
実装は internal/command/exec.go に集中しています。newExecCommand() が Cobra コマンドを生成し、splitExecArgs で <host> と実行コマンド列を分離します。run メソッドは名前空間とアプリケーションを解決し、app.Exec を呼び出すだけのシンプルなフローです。
コンテナ内部実行の本体は internal/docker/application.go に追加された Exec メソッドです。execAttachedInContainer を呼び出し、errdefs.IsConflict が返す場合は ErrApplicationNotRunning を返すことで、停止中コンテナへの実行試行を明示的にエラー化しています。
execAttachedInContainer の実装は internal/docker/container.go に拡張されました。TTY の自動付与や標準入出力の転送を行い、inspectExecExitCode で取得した終了コードを ExecResult に格納して返します。これにより、CLI が実際のコマンド結果を正確に取得できます。
CLI のエグジットコード伝搬が改善されました。cmd/once/main.go で command.ExitCode(err) をチェックし、取得できた場合はそのコードでプロセスを終了します。これにより once exec が内部コマンドの終了コードをそのまま呼び出し元に返せます。
エラー型とユーティリティが internal/command/root.go に導入されました。exitCoder インタフェースと ExitCode, IsExitError 関数により、任意のエラーから終了コードを抽出できる共通ロジックが確立され、他のサブコマンドでも再利用可能です。
テストスイートが拡充され、動作が検証されています。integration/docker_test.go では実際にコンテナを起動し、app.Exec で exit 0 と exit 7 を実行してコードが正しく伝搬することを確認しています。また、internal/command/exec_test.go で splitExecArgs の各パターン(フラグ付き引数や -- 区切り)を網羅的にテストしています。
設計判断
Cobra ベースのサブコマンドとして実装する選択が取られました。新たに外部バイナリを作るのではなく、既存 CLI の拡張として統一感を保ち、ユーザー体験を一貫させています。
終了コードをエラーに埋め込む設計が採用されています。exitCoder インタフェースにより Go のエラーハンドリングと UNIX のプロセス終了コードを橋渡しし、once exec が期待通りのステータスを返すことを保証しています。
コンテナが停止中の場合は専用エラー ErrApplicationNotRunning を返すことで、呼び出し側が状態判定しやすくなっています。エラー分類を細分化することで、スクリプト側でリトライロジックやユーザー向けメッセージを簡潔に実装できます。
まとめ
once exec コマンドの追加により、once CLI から直接コンテナ内部のコマンドを実行でき、TTY 自動付与と終了コード透過という重要な UX が実現しました。エラーハンドリングとコード構造が統一されたことで、今後の拡張やメンテナンスも容易になるでしょう。