[Kamal] Hook実行時の出力制御機能を追加 - グローバルおよびフック単位での詳細設定が可能に

basecamp/kamal

Context

Kamalではデプロイの各段階でカスタムフック(pre-deploy、post-deployなど)を実行できますが、これまでフックの出力をどのレベルで表示するかを制御する方法がありませんでした。#1243で指摘されていたように、フックの冗長な出力を抑制したい場合や、逆に詳細なデバッグ情報を確認したい場合に柔軟な制御ができませんでした。

この変更により、フックごとに出力レベルを設定できるようになり、デプロイプロセスのログをより適切に管理できるようになりました。

Technical Detail

設定方法

config/deploy.ymlhooks_output設定を追加することで、フックの出力制御が可能になります。設定方法は2パターンあります。

グローバル設定 - すべてのフックに同じ出力レベルを適用:

hooks_output: :verbose

フック単位の設定 - フックごとに異なる出力レベルを指定:

hooks_output:
  pre-deploy: :verbose
  pre-build: :quiet

出力レベル

設定可能な出力レベルは2種類です:

  • :verbose - フックの実行コマンドと出力をすべて表示
  • :quiet - 「Running the X hook...」メッセージを含むすべての出力を非表示

設定がない場合(デフォルト)は、SSHKitのinfoレベルとなり、「Running...」と「Finished...」のメッセージのみが表示されます。

CLI フラグとの優先順位

CLIの冗長性フラグ(-v-q)は設定ファイルのhooks_output設定よりも優先されます:

hook_verbosity = if KAMAL.verbosity == :info && hooks_output
  VERBOSITY.fetch(hooks_output)
else
  KAMAL.verbosity
end
  • -qフラグ指定時: すべてのフック出力を非表示
  • -vフラグ指定時: すべてのフック出力を表示
  • フラグなし: hooks_output設定に従う

実装の詳細

設定のバリデーションはKamal::Configurationで行われます:

HOOKS_OUTPUT_LEVELS = [ :quiet, :verbose ].freeze

def hooks_output_for(hook)
  case raw_config.hooks_output
  when Symbol, String
    raw_config.hooks_output.to_sym
  when Hash
    raw_config.hooks_output[hook]&.to_sym
  end
end

フック実行時は、設定された出力レベルに基づいてKAMAL.with_verbosityでコンテキストを切り替えます:

hook_verbosity = if KAMAL.verbosity == :info && hooks_output
  VERBOSITY.fetch(hooks_output)
else
  KAMAL.verbosity
end

KAMAL.with_verbosity(hook_verbosity) do
  run_locally do
    execute *KAMAL.hook.run(hook)
  end
end

バリデーション

無効な出力レベルが指定された場合は、設定の読み込み時にエラーが発生します:

def validate_hooks_output_level!(level, hook = nil)
  return if HOOKS_OUTPUT_LEVELS.include?(level)
  context = hook ? " for hook '#{hook}'" : ""
  raise Kamal::ConfigurationError, "Invalid hooks_output '#{level}'#{context}, must be one of: #{HOOKS_OUTPUT_LEVELS.join(', ')}"
end

エラーメッセージには、グローバル設定かフック単位設定かが明示されます。

テスト

統合テストでは、各出力レベルの動作が検証されています:

def assert_hook_output(output)
  # pre-deploy hook (hooks_output: :verbose) shows everything
  assert_match(/Running.*pre-deploy/, output)
  assert_match(/Deployed!/, output)
  # pre-build hook (hooks_output: :quiet) hides everything
  assert_no_match(/Running.*pre-build/, output)
  assert_no_match(/About to build and push/, output)
  # post-deploy hook (no hooks_output setting) shows Running but hides output
  assert_match(/Running.*post-deploy/, output)
  assert_no_match(/Finished deploy!/, output)
end

利用シーン

  • 詳細なデバッグが必要な場合: pre-deploy: :verboseで該当フックの詳細ログを確認
  • ノイズを減らしたい場合: pre-build: :quietでビルド関連の定型メッセージを非表示
  • CI/CDパイプライン: グローバルに:quietを設定し、必要なフックのみ:verboseで出力

失敗したフックは、出力レベルの設定に関わらず、エラーメッセージ内に常に出力が含まれます。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

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

Review Criteria:

ガイドライン準拠 ⚠ WARNING

記事構成とDiffDaily Styleへの準拠状況

記事構成、対象読者への適合性はPASS基準です。必須要素が網羅され、技術レベルも適切です。コードブロック前後の空行も正しく挿入されています。ただし、カスタムMarkdown構文において、PRリンクの記法がガイドラインの `[#123]` 形式とわずかに異なっているため(`[PR #1758]`)、WARNINGと評価しました。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

引用されているコードスニペットとファイルパスは論理的に一貫しており、PRの趣旨と整合しています。技術用語の選択も正確で、新機能の仕組み(設定方法、優先順位、バリデーション)に関する説明は技術的に正確かつ明快です。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

記事のすべての主張はPRのタイトルや内容と完全に一致しており、ハルシネーションは見られません。Issue番号やPR番号などの固有名詞も正確に記載されています。PRの目的を的確に読者に伝えている優れた記事です。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除